@@ -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
0 commit comments