Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 3b3ef2a

Browse files
authored
Fix reading Time zone rules using Julian days (#17672)
1 parent fa89b5a commit 3b3ef2a

File tree

2 files changed

+138
-62
lines changed

2 files changed

+138
-62
lines changed

src/mscorlib/Resources/Strings.resx

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
4-
Microsoft ResX Schema
5-
3+
<!--
4+
Microsoft ResX Schema
5+
66
Version 2.0
7-
8-
The primary goals of this format is to allow a simple XML format
9-
that is mostly human readable. The generation and parsing of the
10-
various data types are done through the TypeConverter classes
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
1111
associated with the data types.
12-
12+
1313
Example:
14-
14+
1515
... ado.net/XML headers & schema ...
1616
<resheader name="resmimetype">text/microsoft-resx</resheader>
1717
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
2626
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
2727
<comment>This is a comment</comment>
2828
</data>
29-
30-
There are any number of "resheader" rows that contain simple
29+
30+
There are any number of "resheader" rows that contain simple
3131
name/value pairs.
32-
33-
Each data row contains a name, and value. The row also contains a
34-
type or mimetype. Type corresponds to a .NET class that support
35-
text/value conversion through the TypeConverter architecture.
36-
Classes that don't support this are serialized and stored with the
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
3737
mimetype set.
38-
39-
The mimetype is used for serialized objects, and tells the
40-
ResXResourceReader how to depersist the object. This is currently not
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
4141
extensible. For a given mimetype the value must be set accordingly:
42-
43-
Note - application/x-microsoft.net.object.binary.base64 is the format
44-
that the ResXResourceWriter will generate, however the reader can
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
4545
read any of the formats listed below.
46-
46+
4747
mimetype: application/x-microsoft.net.object.binary.base64
48-
value : The object must be serialized with
48+
value : The object must be serialized with
4949
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
5050
: and then encoded with base64 encoding.
51-
51+
5252
mimetype: application/x-microsoft.net.object.soap.base64
53-
value : The object must be serialized with
53+
value : The object must be serialized with
5454
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
5555
: and then encoded with base64 encoding.
5656
5757
mimetype: application/x-microsoft.net.object.bytearray.base64
58-
value : The object must be serialized into a byte array
58+
value : The object must be serialized into a byte array
5959
: using a System.ComponentModel.TypeConverter
6060
: and then encoded with base64 encoding.
6161
-->
@@ -2734,8 +2734,11 @@
27342734
<data name="InvalidTimeZone_InvalidRegistryData" xml:space="preserve">
27352735
<value>The time zone ID '{0}' was found on the local computer, but the registry information was corrupt.</value>
27362736
</data>
2737-
<data name="InvalidTimeZone_JulianDayNotSupported" xml:space="preserve">
2738-
<value>Julian dates in POSIX strings are unsupported.</value>
2737+
<data name="InvalidTimeZone_InvalidJulianDay" xml:space="preserve">
2738+
<value>Invalid Julian day in POSIX strings.</value>
2739+
</data>
2740+
<data name="InvalidTimeZone_NJulianDayNotSupported" xml:space="preserve">
2741+
<value>Julian n day in POSIX strings is not supported.</value>
27392742
</data>
27402743
<data name="InvalidTimeZone_NoTTInfoStructures" xml:space="preserve">
27412744
<value>There are no ttinfo structures in the tzfile. At least one ttinfo structure is required in order to construct a TimeZoneInfo object.</value>

src/mscorlib/shared/System/TimeZoneInfo.Unix.cs

Lines changed: 106 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,37 @@ private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string pos
11171117
return result;
11181118
}
11191119

1120+
private static DateTime ParseTimeOfDay(string time)
1121+
{
1122+
DateTime timeOfDay;
1123+
TimeSpan? timeOffset = TZif_ParseOffsetString(time);
1124+
if (timeOffset.HasValue)
1125+
{
1126+
// This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
1127+
// Some time zones use time values like, "26", "144", or "-2".
1128+
// This allows the week to sometimes be week 4 and sometimes week 5 in the month.
1129+
// For now, strip off any 'days' in the offset, and just get the time of day correct
1130+
timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
1131+
if (timeOffset.Value < TimeSpan.Zero)
1132+
{
1133+
timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
1134+
}
1135+
else
1136+
{
1137+
timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
1138+
}
1139+
1140+
timeOfDay += timeOffset.Value;
1141+
}
1142+
else
1143+
{
1144+
// default to 2AM.
1145+
timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
1146+
}
1147+
1148+
return timeOfDay;
1149+
}
1150+
11201151
private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time)
11211152
{
11221153
if (string.IsNullOrEmpty(date))
@@ -1138,48 +1169,90 @@ private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date
11381169
throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_UnparseablePosixMDateString, date));
11391170
}
11401171

1141-
DateTime timeOfDay;
1142-
TimeSpan? timeOffset = TZif_ParseOffsetString(time);
1143-
if (timeOffset.HasValue)
1144-
{
1145-
// This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed.
1146-
// Some time zones use time values like, "26", "144", or "-2".
1147-
// This allows the week to sometimes be week 4 and sometimes week 5 in the month.
1148-
// For now, strip off any 'days' in the offset, and just get the time of day correct
1149-
timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds);
1150-
if (timeOffset.Value < TimeSpan.Zero)
1151-
{
1152-
timeOfDay = new DateTime(1, 1, 2, 0, 0, 0);
1153-
}
1154-
else
1155-
{
1156-
timeOfDay = new DateTime(1, 1, 1, 0, 0, 0);
1157-
}
1158-
1159-
timeOfDay += timeOffset.Value;
1160-
}
1161-
else
1172+
return TransitionTime.CreateFloatingDateRule(ParseTimeOfDay(time), month, week, day);
1173+
}
1174+
else
1175+
{
1176+
if (date[0] != 'J')
11621177
{
1163-
// default to 2AM.
1164-
timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
1178+
// should be n Julian day format which we don't support.
1179+
//
1180+
// This specifies the Julian day, with n between 0 and 365. February 29 is counted in leap years.
1181+
//
1182+
// n would be a relative number from the begining of the year. which should handle if the
1183+
// the year is a leap year or not.
1184+
//
1185+
// In leap year, n would be counted as:
1186+
//
1187+
// 0 30 31 59 60 90 335 365
1188+
// |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------|
1189+
//
1190+
// while in non leap year we'll have
1191+
//
1192+
// 0 30 31 58 59 89 334 364
1193+
// |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------|
1194+
//
1195+
//
1196+
// For example if n is specified as 60, this means in leap year the rule will start at Mar 1,
1197+
// while in non leap year the rule will start at Mar 2.
1198+
//
1199+
// If we need to support n format, we'll have to have a floating adjustment rule support this case.
1200+
1201+
throw new InvalidTimeZoneException(SR.InvalidTimeZone_NJulianDayNotSupported);
11651202
}
11661203

1167-
return TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, day);
1204+
// Julian day
1205+
TZif_ParseJulianDay(date, out int month, out int day);
1206+
return TransitionTime.CreateFixedDateRule(ParseTimeOfDay(time), month, day);
11681207
}
1169-
else
1208+
}
1209+
1210+
/// <summary>
1211+
/// Parses a string like Jn or n into month and day values.
1212+
/// </summary>
1213+
/// <returns>
1214+
/// true if the parsing succeeded; otherwise, false.
1215+
/// </returns>
1216+
private static void TZif_ParseJulianDay(string date, out int month, out int day)
1217+
{
1218+
// Jn
1219+
// This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
1220+
Debug.Assert(date[0] == 'J');
1221+
Debug.Assert(!String.IsNullOrEmpty(date));
1222+
month = day = 0;
1223+
1224+
int index = 1;
1225+
1226+
if (index >= date.Length || ((uint)(date[index] - '0') > '9'-'0'))
11701227
{
1171-
// Jn
1172-
// This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
1228+
throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay);
1229+
}
1230+
1231+
int julianDay = 0;
11731232

1174-
// n
1175-
// This specifies the Julian day, with n between 0 and 365.February 29 is counted in leap years.
1233+
do
1234+
{
1235+
julianDay = julianDay * 10 + (int) (date[index] - '0');
1236+
index++;
1237+
} while (index < date.Length && ((uint)(date[index] - '0') <= '9'-'0'));
11761238

1177-
// These two rules cannot be expressed with the current AdjustmentRules
1178-
// One of them *could* be supported if we relaxed the TransitionTime validation rules, and allowed
1179-
// "IsFixedDateRule = true, Month = 0, Day = n" to mean the nth day of the year, picking one of the rules above
1239+
int[] days = GregorianCalendarHelper.DaysToMonth365;
11801240

1181-
throw new InvalidTimeZoneException(SR.InvalidTimeZone_JulianDayNotSupported);
1241+
if (julianDay == 0 || julianDay > days[days.Length - 1])
1242+
{
1243+
throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay);
11821244
}
1245+
1246+
int i = 1;
1247+
while (i < days.Length && julianDay > days[i])
1248+
{
1249+
i++;
1250+
}
1251+
1252+
Debug.Assert(i > 0 && i < days.Length);
1253+
1254+
month = i;
1255+
day = julianDay - days[i - 1];
11831256
}
11841257

11851258
/// <summary>

0 commit comments

Comments
 (0)