diff --git a/Directory.Packages.props b/Directory.Packages.props index cf90f96..41cd5cf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,7 +14,7 @@ - + diff --git a/src/AltaSoft.DomainPrimitives/ToXmlStringExt.cs b/src/AltaSoft.DomainPrimitives/ToXmlStringExt.cs index d3024cb..38236bd 100644 --- a/src/AltaSoft.DomainPrimitives/ToXmlStringExt.cs +++ b/src/AltaSoft.DomainPrimitives/ToXmlStringExt.cs @@ -31,12 +31,27 @@ public static class ToXmlStringExt public static string ToXmlString(this DateOnly value) => value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); /// - /// Converts a value to its XML string representation in the format "HH:mm:ss". + /// Converts a value to its XML string representation in the format "HH:mm:sszzz", + /// where the time zone offset reflects the current local offset from UTC. /// /// The TimeOnly value to convert. /// The XML string representation of the TimeOnly value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToXmlString(this TimeOnly value) => value.ToString("HH:mm:ss", CultureInfo.InvariantCulture); + public static string ToXmlString(this TimeOnly value) + { + // We avoid constructing a local DateTime for an arbitrary date to prevent + // possible exceptions on DST transition days (invalid or ambiguous local times). + var timePart = value.ToString("HH:mm:ss", CultureInfo.InvariantCulture); + + // Derive the current local offset from UTC in a DST-safe way using the current instant. + var offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow); + var offsetSign = offset < TimeSpan.Zero ? "-" : "+"; + var absoluteOffset = offset.Duration(); + var offsetPart = absoluteOffset.ToString(@"hh\:mm", CultureInfo.InvariantCulture); + + return $"{timePart}{offsetSign}{offsetPart}"; + } /// /// Converts a value to its XML string representation diff --git a/tests/AltaSoft.DomainPrimitives.XmlDataTypes.Tests/ToXmlStringExtTests.cs b/tests/AltaSoft.DomainPrimitives.XmlDataTypes.Tests/ToXmlStringExtTests.cs index eef5e8d..abe5315 100644 --- a/tests/AltaSoft.DomainPrimitives.XmlDataTypes.Tests/ToXmlStringExtTests.cs +++ b/tests/AltaSoft.DomainPrimitives.XmlDataTypes.Tests/ToXmlStringExtTests.cs @@ -24,7 +24,15 @@ public void TimeOnly_ToXmlString_ReturnsExpectedFormat() { var t = new TimeOnly(13, 45, 30); var xml = t.ToXmlString(); - Assert.Equal("13:45:30", xml); + + // Validate the overall shape: time followed by a timezone offset + Assert.Matches(@"^13:45:30[+-]\d{2}:\d{2}$", xml); + + // Validate the offset suffix matches the actual current local offset + var offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow); + var offsetSign = offset < TimeSpan.Zero ? "-" : "+"; + var expectedOffsetSuffix = $"{offsetSign}{offset.Duration():hh\\:mm}"; + Assert.EndsWith(expectedOffsetSuffix, xml); } [Fact]