diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs | 262 |
1 files changed, 179 insertions, 83 deletions
diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs index 410eaf3ff..5645c2a38 100644 --- a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs @@ -384,8 +384,8 @@ namespace System string symlinkPath = Interop.Sys.ReadLink(tzFilePath); if (symlinkPath != null) { - // Use Path.Combine to resolve links that contain a relative path (e.g. /etc/localtime). - symlinkPath = Path.Combine(tzFilePath, symlinkPath); + // symlinkPath can be relative path, use Path to get the full absolute path. + symlinkPath = Path.GetFullPath(symlinkPath, Path.GetDirectoryName(tzFilePath)); string timeZoneDirectory = GetTimeZoneDirectory(); if (symlinkPath.StartsWith(timeZoneDirectory, StringComparison.Ordinal)) @@ -398,7 +398,7 @@ namespace System } /// <summary> - /// Enumerate files + /// Enumerate files /// </summary> private static IEnumerable<string> EnumerateFilesRecursively(string path) { @@ -595,6 +595,7 @@ namespace System } catch (ArgumentException) { } catch (InvalidTimeZoneException) { } + try { return new TimeZoneInfo(rawData, id, dstDisabled: true); // create a TimeZoneInfo instance from the TZif data w/o DST support @@ -602,7 +603,6 @@ namespace System catch (ArgumentException) { } catch (InvalidTimeZoneException) { } } - return null; } @@ -866,7 +866,7 @@ namespace System index++; } - if (index == 0) + if (rulesList.Count == 0 && index < dts.Length) { TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes); DateTime endTransitionDate = dts[index]; @@ -883,6 +883,12 @@ namespace System default(TransitionTime), baseUtcDelta, noDaylightTransitions: true); + + if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r)) + { + NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r); + } + rulesList.Add(r); } else if (index < dts.Length) @@ -920,6 +926,12 @@ namespace System default(TransitionTime), baseUtcDelta, noDaylightTransitions: true); + + if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r)) + { + NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r); + } + rulesList.Add(r); } else @@ -932,8 +944,14 @@ namespace System if (!string.IsNullOrEmpty(futureTransitionsPosixFormat)) { AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset); + if (r != null) { + if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r)) + { + NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r); + } + rulesList.Add(r); } } @@ -954,6 +972,12 @@ namespace System default(TransitionTime), baseUtcDelta, noDaylightTransitions: true); + + if (!IsValidAdjustmentRuleOffest(timeZoneBaseUtcOffset, r)) + { + NormalizeAdjustmentRuleOffset(timeZoneBaseUtcOffset, ref r); + } + rulesList.Add(r); } } @@ -1012,17 +1036,15 @@ namespace System /// </remarks> private static AdjustmentRule TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset) { - string standardName; - string standardOffset; - string daylightSavingsName; - string daylightSavingsOffset; - string start; - string startTime; - string end; - string endTime; - - if (TZif_ParsePosixFormat(posixFormat, out standardName, out standardOffset, out daylightSavingsName, - out daylightSavingsOffset, out start, out startTime, out end, out endTime)) + if (TZif_ParsePosixFormat(posixFormat, + out ReadOnlySpan<char> standardName, + out ReadOnlySpan<char> standardOffset, + out ReadOnlySpan<char> daylightSavingsName, + out ReadOnlySpan<char> daylightSavingsOffset, + out ReadOnlySpan<char> start, + out ReadOnlySpan<char> startTime, + out ReadOnlySpan<char> end, + out ReadOnlySpan<char> endTime)) { // a valid posixFormat has at least standardName and standardOffset @@ -1033,7 +1055,7 @@ namespace System baseOffset = TZif_CalculateTransitionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset); // having a daylightSavingsName means there is a DST rule - if (!string.IsNullOrEmpty(daylightSavingsName)) + if (!daylightSavingsName.IsEmpty) { TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset); TimeSpan daylightSavingsTimeSpan; @@ -1079,16 +1101,16 @@ namespace System return null; } - private static TimeSpan? TZif_ParseOffsetString(string offset) + private static TimeSpan? TZif_ParseOffsetString(ReadOnlySpan<char> offset) { TimeSpan? result = null; - if (!string.IsNullOrEmpty(offset)) + if (offset.Length > 0) { bool negative = offset[0] == '-'; if (negative || offset[0] == '+') { - offset = offset.Substring(1); + offset = offset.Slice(1); } // Try parsing just hours first. @@ -1117,9 +1139,40 @@ namespace System return result; } - private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time) + private static DateTime ParseTimeOfDay(ReadOnlySpan<char> time) + { + DateTime timeOfDay; + TimeSpan? timeOffset = TZif_ParseOffsetString(time); + if (timeOffset.HasValue) + { + // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed. + // Some time zones use time values like, "26", "144", or "-2". + // This allows the week to sometimes be week 4 and sometimes week 5 in the month. + // For now, strip off any 'days' in the offset, and just get the time of day correct + timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds); + if (timeOffset.Value < TimeSpan.Zero) + { + timeOfDay = new DateTime(1, 1, 2, 0, 0, 0); + } + else + { + timeOfDay = new DateTime(1, 1, 1, 0, 0, 0); + } + + timeOfDay += timeOffset.Value; + } + else + { + // default to 2AM. + timeOfDay = new DateTime(1, 1, 1, 2, 0, 0); + } + + return timeOfDay; + } + + private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(ReadOnlySpan<char> date, ReadOnlySpan<char> time) { - if (string.IsNullOrEmpty(date)) + if (date.IsEmpty) { return default(TransitionTime); } @@ -1135,51 +1188,93 @@ namespace System DayOfWeek day; if (!TZif_ParseMDateRule(date, out month, out week, out day)) { - throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_UnparseablePosixMDateString, date)); + throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_UnparseablePosixMDateString, date.ToString())); } - DateTime timeOfDay; - TimeSpan? timeOffset = TZif_ParseOffsetString(time); - if (timeOffset.HasValue) - { - // This logic isn't correct and can't be corrected until https://github.com/dotnet/corefx/issues/2618 is fixed. - // Some time zones use time values like, "26", "144", or "-2". - // This allows the week to sometimes be week 4 and sometimes week 5 in the month. - // For now, strip off any 'days' in the offset, and just get the time of day correct - timeOffset = new TimeSpan(timeOffset.Value.Hours, timeOffset.Value.Minutes, timeOffset.Value.Seconds); - if (timeOffset.Value < TimeSpan.Zero) - { - timeOfDay = new DateTime(1, 1, 2, 0, 0, 0); - } - else - { - timeOfDay = new DateTime(1, 1, 1, 0, 0, 0); - } - - timeOfDay += timeOffset.Value; - } - else + return TransitionTime.CreateFloatingDateRule(ParseTimeOfDay(time), month, week, day); + } + else + { + if (date[0] != 'J') { - // default to 2AM. - timeOfDay = new DateTime(1, 1, 1, 2, 0, 0); + // should be n Julian day format which we don't support. + // + // This specifies the Julian day, with n between 0 and 365. February 29 is counted in leap years. + // + // n would be a relative number from the begining of the year. which should handle if the + // the year is a leap year or not. + // + // In leap year, n would be counted as: + // + // 0 30 31 59 60 90 335 365 + // |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------| + // + // while in non leap year we'll have + // + // 0 30 31 58 59 89 334 364 + // |-------Jan--------|-------Feb--------|-------Mar--------|....|-------Dec--------| + // + // + // For example if n is specified as 60, this means in leap year the rule will start at Mar 1, + // while in non leap year the rule will start at Mar 2. + // + // If we need to support n format, we'll have to have a floating adjustment rule support this case. + + throw new InvalidTimeZoneException(SR.InvalidTimeZone_NJulianDayNotSupported); } - return TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, day); + // Julian day + TZif_ParseJulianDay(date, out int month, out int day); + return TransitionTime.CreateFixedDateRule(ParseTimeOfDay(time), month, day); } - else + } + + /// <summary> + /// Parses a string like Jn or n into month and day values. + /// </summary> + /// <returns> + /// true if the parsing succeeded; otherwise, false. + /// </returns> + private static void TZif_ParseJulianDay(ReadOnlySpan<char> date, out int month, out int day) + { + // Jn + // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years. + Debug.Assert(!date.IsEmpty); + Debug.Assert(date[0] == 'J'); + month = day = 0; + + int index = 1; + + if (index >= date.Length || ((uint)(date[index] - '0') > '9'-'0')) + { + throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay); + } + + int julianDay = 0; + + do { - // Jn - // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years. + julianDay = julianDay * 10 + (int) (date[index] - '0'); + index++; + } while (index < date.Length && ((uint)(date[index] - '0') <= '9'-'0')); - // n - // This specifies the Julian day, with n between 0 and 365.February 29 is counted in leap years. + int[] days = GregorianCalendarHelper.DaysToMonth365; - // These two rules cannot be expressed with the current AdjustmentRules - // One of them *could* be supported if we relaxed the TransitionTime validation rules, and allowed - // "IsFixedDateRule = true, Month = 0, Day = n" to mean the nth day of the year, picking one of the rules above + if (julianDay == 0 || julianDay > days[days.Length - 1]) + { + throw new InvalidTimeZoneException(SR.InvalidTimeZone_InvalidJulianDay); + } - throw new InvalidTimeZoneException(SR.InvalidTimeZone_JulianDayNotSupported); + int i = 1; + while (i < days.Length && julianDay > days[i]) + { + i++; } + + Debug.Assert(i > 0 && i < days.Length); + + month = i; + day = julianDay - days[i - 1]; } /// <summary> @@ -1188,19 +1283,20 @@ namespace System /// <returns> /// true if the parsing succeeded; otherwise, false. /// </returns> - private static bool TZif_ParseMDateRule(string dateRule, out int month, out int week, out DayOfWeek dayOfWeek) + private static bool TZif_ParseMDateRule(ReadOnlySpan<char> dateRule, out int month, out int week, out DayOfWeek dayOfWeek) { if (dateRule[0] == 'M') { - int firstDotIndex = dateRule.IndexOf('.'); - if (firstDotIndex > 0) + int monthWeekDotIndex = dateRule.IndexOf('.'); + if (monthWeekDotIndex > 0) { - int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1); - if (secondDotIndex > 0) + ReadOnlySpan<char> weekDaySpan = dateRule.Slice(monthWeekDotIndex + 1); + int weekDayDotIndex = weekDaySpan.IndexOf('.'); + if (weekDayDotIndex > 0) { - if (int.TryParse(dateRule.AsSpan().Slice(1, firstDotIndex - 1), out month) && - int.TryParse(dateRule.AsSpan().Slice(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1), out week) && - int.TryParse(dateRule.AsSpan().Slice(secondDotIndex + 1), out int day)) + if (int.TryParse(dateRule.Slice(1, monthWeekDotIndex - 1), out month) && + int.TryParse(weekDaySpan.Slice(0, weekDayDotIndex), out week) && + int.TryParse(weekDaySpan.Slice(weekDayDotIndex + 1), out int day)) { dayOfWeek = (DayOfWeek)day; return true; @@ -1216,15 +1312,15 @@ namespace System } private static bool TZif_ParsePosixFormat( - string posixFormat, - out string standardName, - out string standardOffset, - out string daylightSavingsName, - out string daylightSavingsOffset, - out string start, - out string startTime, - out string end, - out string endTime) + ReadOnlySpan<char> posixFormat, + out ReadOnlySpan<char> standardName, + out ReadOnlySpan<char> standardOffset, + out ReadOnlySpan<char> daylightSavingsName, + out ReadOnlySpan<char> daylightSavingsOffset, + out ReadOnlySpan<char> start, + out ReadOnlySpan<char> startTime, + out ReadOnlySpan<char> end, + out ReadOnlySpan<char> endTime) { standardName = null; standardOffset = null; @@ -1240,7 +1336,7 @@ namespace System standardOffset = TZif_ParsePosixOffset(posixFormat, ref index); daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index); - if (!string.IsNullOrEmpty(daylightSavingsName)) + if (!daylightSavingsName.IsEmpty) { daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index); @@ -1257,10 +1353,10 @@ namespace System } } - return !string.IsNullOrEmpty(standardName) && !string.IsNullOrEmpty(standardOffset); + return !standardName.IsEmpty && !standardOffset.IsEmpty; } - private static string TZif_ParsePosixName(string posixFormat, ref int index) + private static ReadOnlySpan<char> TZif_ParsePosixName(ReadOnlySpan<char> posixFormat, ref int index) { bool isBracketEnclosed = index < posixFormat.Length && posixFormat[index] == '<'; if (isBracketEnclosed) @@ -1268,7 +1364,7 @@ namespace System // move past the opening bracket index++; - string result = TZif_ParsePosixString(posixFormat, ref index, c => c == '>'); + ReadOnlySpan<char> result = TZif_ParsePosixString(posixFormat, ref index, c => c == '>'); // move past the closing bracket if (index < posixFormat.Length && posixFormat[index] == '>') @@ -1287,10 +1383,10 @@ namespace System } } - private static string TZif_ParsePosixOffset(string posixFormat, ref int index) => + private static ReadOnlySpan<char> TZif_ParsePosixOffset(ReadOnlySpan<char> posixFormat, ref int index) => TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':'); - private static void TZif_ParsePosixDateTime(string posixFormat, ref int index, out string date, out string time) + private static void TZif_ParsePosixDateTime(ReadOnlySpan<char> posixFormat, ref int index, out ReadOnlySpan<char> date, out ReadOnlySpan<char> time) { time = null; @@ -1302,13 +1398,13 @@ namespace System } } - private static string TZif_ParsePosixDate(string posixFormat, ref int index) => + private static ReadOnlySpan<char> TZif_ParsePosixDate(ReadOnlySpan<char> posixFormat, ref int index) => TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ','); - private static string TZif_ParsePosixTime(string posixFormat, ref int index) => + private static ReadOnlySpan<char> TZif_ParsePosixTime(ReadOnlySpan<char> posixFormat, ref int index) => TZif_ParsePosixString(posixFormat, ref index, c => c == ','); - private static string TZif_ParsePosixString(string posixFormat, ref int index, Func<char, bool> breakCondition) + private static ReadOnlySpan<char> TZif_ParsePosixString(ReadOnlySpan<char> posixFormat, ref int index, Func<char, bool> breakCondition) { int startIndex = index; for (; index < posixFormat.Length; index++) @@ -1320,7 +1416,7 @@ namespace System } } - return posixFormat.Substring(startIndex, index - startIndex); + return posixFormat.Slice(startIndex, index - startIndex); } // Returns the Substring from zoneAbbreviations starting at index and ending at '\0' |