Skip to content

Commit c461f1d

Browse files
Merge pull request #44 from altasoft/bugfix/TimeOnlyTimezone
Update TimeOnly ToXmlString to include time zone offset
2 parents 1c06339 + 79a6b3a commit c461f1d

3 files changed

Lines changed: 27 additions & 4 deletions

File tree

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.*" />
1616
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.*" />
17-
<PackageVersion Include="Verify.Xunit" Version="31.9.3" />
17+
<PackageVersion Include="Verify.Xunit" Version="31.12.5" />
1818
<PackageVersion Include="xunit" Version="2.9.3" />
1919
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
2020
<PackageVersion Include="coverlet.collector" Version="6.0.4" />

src/AltaSoft.DomainPrimitives/ToXmlStringExt.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,27 @@ public static class ToXmlStringExt
3131
public static string ToXmlString(this DateOnly value) => value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
3232

3333
/// <summary>
34-
/// Converts a <see cref="TimeOnly" /> value to its XML string representation in the format "HH:mm:ss".
34+
/// Converts a <see cref="TimeOnly" /> value to its XML string representation in the format "HH:mm:sszzz",
35+
/// where the time zone offset reflects the current local offset from UTC.
3536
/// </summary>
3637
/// <param name="value">The TimeOnly value to convert.</param>
3738
/// <returns>The XML string representation of the TimeOnly value.</returns>
39+
3840
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39-
public static string ToXmlString(this TimeOnly value) => value.ToString("HH:mm:ss", CultureInfo.InvariantCulture);
41+
public static string ToXmlString(this TimeOnly value)
42+
{
43+
// We avoid constructing a local DateTime for an arbitrary date to prevent
44+
// possible exceptions on DST transition days (invalid or ambiguous local times).
45+
var timePart = value.ToString("HH:mm:ss", CultureInfo.InvariantCulture);
46+
47+
// Derive the current local offset from UTC in a DST-safe way using the current instant.
48+
var offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow);
49+
var offsetSign = offset < TimeSpan.Zero ? "-" : "+";
50+
var absoluteOffset = offset.Duration();
51+
var offsetPart = absoluteOffset.ToString(@"hh\:mm", CultureInfo.InvariantCulture);
52+
53+
return $"{timePart}{offsetSign}{offsetPart}";
54+
}
4055

4156
/// <summary>
4257
/// Converts a <see cref="DateTimeOffset" /> value to its XML string representation

tests/AltaSoft.DomainPrimitives.XmlDataTypes.Tests/ToXmlStringExtTests.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ public void TimeOnly_ToXmlString_ReturnsExpectedFormat()
2424
{
2525
var t = new TimeOnly(13, 45, 30);
2626
var xml = t.ToXmlString();
27-
Assert.Equal("13:45:30", xml);
27+
28+
// Validate the overall shape: time followed by a timezone offset
29+
Assert.Matches(@"^13:45:30[+-]\d{2}:\d{2}$", xml);
30+
31+
// Validate the offset suffix matches the actual current local offset
32+
var offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow);
33+
var offsetSign = offset < TimeSpan.Zero ? "-" : "+";
34+
var expectedOffsetSuffix = $"{offsetSign}{offset.Duration():hh\\:mm}";
35+
Assert.EndsWith(expectedOffsetSuffix, xml);
2836
}
2937

3038
[Fact]

0 commit comments

Comments
 (0)