summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEric Erhardt <eric.erhardt@microsoft.com>2015-08-10 16:55:26 -0500
committerEric Erhardt <eric.erhardt@microsoft.com>2015-08-10 16:55:26 -0500
commit0930fefcb2adfa5745964f30237494ea32a87c69 (patch)
treece72ea8a989dc08956788e0497e3b3d71ad6e437 /src
parentc8265f00f53adef379531f6fc276a2038ff0cce1 (diff)
parent776381d2d7d7760b8f4d78174d7b68914824ca88 (diff)
downloadcoreclr-0930fefcb2adfa5745964f30237494ea32a87c69.tar.gz
coreclr-0930fefcb2adfa5745964f30237494ea32a87c69.tar.bz2
coreclr-0930fefcb2adfa5745964f30237494ea32a87c69.zip
Merge pull request #1352 from eerhardt/master
TimeZoneInfo parse v2 tzfile and create AdjustmentRules
Diffstat (limited to 'src')
-rw-r--r--src/mscorlib/src/System/TimeZoneInfo.cs1191
-rw-r--r--src/mscorlib/src/mscorlib.txt5
2 files changed, 798 insertions, 398 deletions
diff --git a/src/mscorlib/src/System/TimeZoneInfo.cs b/src/mscorlib/src/System/TimeZoneInfo.cs
index ef43bb602e..4694ce0072 100644
--- a/src/mscorlib/src/System/TimeZoneInfo.cs
+++ b/src/mscorlib/src/System/TimeZoneInfo.cs
@@ -90,12 +90,6 @@ namespace System {
#endif // FEATURE_WIN32_REGISTRY
#if PLATFORM_UNIX
- // use for generating multi-year DST periods
- private static readonly TransitionTime c_transition5_15 = TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1), 05, 15);
- private static readonly TransitionTime c_transition7_15 = TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1), 07, 15);
- private static readonly TransitionTime c_transition10_15 = TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1), 10, 15);
- private static readonly TransitionTime c_transition12_15 = TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1), 12, 15);
-
private const string c_defaultTimeZoneDirectory = "/usr/share/zoneinfo/";
private const string c_timeZoneEnvironmentVariable = "TZ";
private const string c_timeZoneDirectoryEnvironmentVariable = "TZDIR";
@@ -354,7 +348,7 @@ namespace System {
DateTime adjustedTime = (TimeZoneInfo.ConvertTime(dateTimeOffset, this)).DateTime;
Boolean isAmbiguous = false;
- AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime);
+ AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime);
if (rule != null && rule.HasDaylightSaving) {
DaylightTime daylightTime = GetDaylightTime(adjustedTime.Year, rule);
isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
@@ -402,7 +396,7 @@ namespace System {
}
Boolean isAmbiguous = false;
- AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime);
+ AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime);
if (rule != null && rule.HasDaylightSaving) {
DaylightTime daylightTime = GetDaylightTime(adjustedTime.Year, rule);
isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
@@ -428,6 +422,40 @@ namespace System {
return timeSpans;
}
+ // note the time is already adjusted
+ private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime)
+ {
+ AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime);
+ if (rule != null && rule.NoDaylightTransitions && !rule.HasDaylightSaving)
+ {
+ // When using NoDaylightTransitions rules, each rule is only for one offset.
+ // When looking for the Daylight savings rules, and we found the non-DST rule,
+ // then we get the rule right before this rule.
+ return GetPreviousAdjustmentRule(rule);
+ }
+
+ return rule;
+ }
+
+ /// <summary>
+ /// Gets the AdjustmentRule that is immediately preceeding the specified rule.
+ /// If the specified rule is the first AdjustmentRule, or it isn't in m_adjustmentRules,
+ /// then the specified rule is returned.
+ /// </summary>
+ private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule)
+ {
+ AdjustmentRule result = rule;
+ for (int i = 1; i < m_adjustmentRules.Length; i++)
+ {
+ if (rule.Equals(m_adjustmentRules[i]))
+ {
+ result = m_adjustmentRules[i - 1];
+ break;
+ }
+ }
+ return result;
+ }
+
//
// GetUtcOffset -
//
@@ -791,7 +819,7 @@ namespace System {
sourceOffset = sourceOffset + sourceRule.BaseUtcOffsetDelta;
if (sourceRule.HasDaylightSaving) {
Boolean sourceIsDaylightSavings = false;
- DaylightTime sourceDaylightTime = GetDaylightTime(dateTime.Year, sourceRule);
+ DaylightTime sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule);
// 'dateTime' might be in an invalid time range since it is in an AdjustmentRule
// period that supports DST
@@ -1129,9 +1157,10 @@ namespace System {
String zoneAbbreviations;
Boolean[] StandardTime;
Boolean[] GmtTime;
+ string futureTransitionsPosixFormat;
// parse the raw TZif bytes; this method can throw ArgumentException when the data is malformed.
- TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime);
+ TZif_ParseRaw(data, out t, out dts, out typeOfLocalTime, out transitionType, out zoneAbbreviations, out StandardTime, out GmtTime, out futureTransitionsPosixFormat);
m_id = id;
m_displayName = c_localId;
@@ -1173,7 +1202,7 @@ namespace System {
if (!dstDisabled) {
// only create the adjustment rule if DST is enabled
- TZif_GenerateAdjustmentRules(out m_adjustmentRules, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime);
+ TZif_GenerateAdjustmentRules(out m_adjustmentRules, m_baseUtcOffset, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
}
ValidateTimeZoneInfo(m_id, m_baseUtcOffset, m_adjustmentRules, out m_supportsDaylightSavingTime);
@@ -1341,28 +1370,140 @@ namespace System {
// ----- SECTION: internal instance utility methods ----------------*
- // assumes dateTime is in the current time zone's time
- private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime) {
+ private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc = false) {
if (m_adjustmentRules == null || m_adjustmentRules.Length == 0) {
return null;
}
- // Only check the whole-date portion of the dateTime -
+ // Only check the whole-date portion of the dateTime for DateTimeKind.Unspecified rules -
// This is because the AdjustmentRule DateStart & DateEnd are stored as
// Date-only values {4/2/2006 - 10/28/2006} but actually represent the
// time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999}
- DateTime date = dateTime.Date;
+ DateTime date;
+ if (dateTimeisUtc)
+ {
+ date = (dateTime + BaseUtcOffset).Date;
+ }
+ else
+ {
+ date = dateTime.Date;
+ }
for (int i = 0; i < m_adjustmentRules.Length; i++) {
- if (m_adjustmentRules[i].DateStart <= date && m_adjustmentRules[i].DateEnd >= date) {
- return m_adjustmentRules[i];
+ AdjustmentRule rule = m_adjustmentRules[i];
+ AdjustmentRule previousRule = i > 0 ? m_adjustmentRules[i - 1] : rule;
+ if (IsAdjustmentRuleValid(rule, previousRule, dateTime, date, dateTimeisUtc)) {
+ return rule;
}
}
return null;
}
+ /// <summary>
+ /// Determines if 'rule' is the correct AdjustmentRule for the given dateTime.
+ /// </summary>
+ private bool IsAdjustmentRuleValid(AdjustmentRule rule, AdjustmentRule previousRule,
+ DateTime dateTime, DateTime dateOnly, bool dateTimeisUtc)
+ {
+ bool isAfterStart;
+ if (rule.DateStart.Kind == DateTimeKind.Utc)
+ {
+ DateTime dateTimeToCompare;
+ if (dateTimeisUtc)
+ {
+ dateTimeToCompare = dateTime;
+ }
+ else
+ {
+ dateTimeToCompare = ConvertToUtc(dateTime,
+ // use the previous rule to compute the dateTimeToCompare, since the time daylight savings "switches"
+ // is based on the previous rule's offset
+ previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta);
+ }
+
+ isAfterStart = dateTimeToCompare >= rule.DateStart;
+ }
+ else
+ {
+ // if the rule's DateStart is Unspecified, then use the whole-date portion
+ isAfterStart = dateTime >= rule.DateStart;
+ }
+
+ if (!isAfterStart)
+ {
+ return false;
+ }
+
+ bool isBeforeEnd;
+ if (rule.DateEnd.Kind == DateTimeKind.Utc)
+ {
+ DateTime dateTimeToCompare;
+ if (dateTimeisUtc)
+ {
+ dateTimeToCompare = dateTime;
+ }
+ else
+ {
+ dateTimeToCompare = ConvertToUtc(dateTime, rule.DaylightDelta, rule.BaseUtcOffsetDelta);
+ }
+
+ isBeforeEnd = dateTimeToCompare <= rule.DateEnd;
+ }
+ else
+ {
+ // if the rule's DateEnd is Unspecified, then use the whole-date portion
+ isBeforeEnd = dateTime <= rule.DateEnd;
+ }
+
+ return isBeforeEnd;
+ }
+
+ /// <summary>
+ /// Converts the dateTime to UTC using the specified deltas.
+ /// </summary>
+ private DateTime ConvertToUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta)
+ {
+ return ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: true);
+ }
+
+ /// <summary>
+ /// Converts the dateTime from UTC using the specified deltas.
+ /// </summary>
+ private DateTime ConvertFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta)
+ {
+ return ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: false);
+ }
+
+ /// <summary>
+ /// Converts the dateTime to or from UTC using the specified deltas.
+ /// </summary>
+ private DateTime ConvertToFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta, bool convertToUtc)
+ {
+ TimeSpan offset = BaseUtcOffset + daylightDelta + baseUtcOffsetDelta;
+ if (convertToUtc)
+ {
+ offset = offset.Negate();
+ }
+
+ long ticks = dateTime.Ticks + offset.Ticks;
+
+ DateTime result;
+ if (ticks > DateTime.MaxValue.Ticks)
+ {
+ result = DateTime.MaxValue;
+ }
+ else if (ticks < DateTime.MinValue.Ticks)
+ {
+ result = DateTime.MinValue;
+ }
+ else
+ {
+ result = new DateTime(ticks);
+ }
+ return result;
+ }
// ----- SECTION: internal static utility methods ----------------*
@@ -1450,7 +1591,8 @@ namespace System {
TimeSpan.Zero, // no daylight saving transition
TransitionTime.CreateFixedDateRule(DateTime.MinValue, 1, 1),
TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(1), 1, 1),
- new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0)); // Bias delta is all what we need from this rule
+ new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), // Bias delta is all what we need from this rule
+ noDaylightTransitions: false);
}
//
@@ -1477,7 +1619,8 @@ namespace System {
new TimeSpan(0, -timeZoneInformation.DaylightBias, 0),
(TransitionTime)daylightTransitionStart,
(TransitionTime)daylightTransitionEnd,
- new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0));
+ new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0),
+ noDaylightTransitions: false);
return rule;
}
@@ -1530,10 +1673,27 @@ namespace System {
//
// Helper function that returns a DaylightTime from a year and AdjustmentRule
//
- static private DaylightTime GetDaylightTime(Int32 year, AdjustmentRule rule) {
+ private DaylightTime GetDaylightTime(Int32 year, AdjustmentRule rule) {
TimeSpan delta = rule.DaylightDelta;
- DateTime startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart);
- DateTime endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd);
+ DateTime startTime;
+ DateTime endTime;
+ if (rule.NoDaylightTransitions)
+ {
+ // NoDaylightTransitions rules don't use DaylightTransition Start and End, instead
+ // the DateStart and DateEnd are UTC times that represent when daylight savings time changes.
+ // Convert the UTC times into adjusted time zone times.
+
+ // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule
+ AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule);
+ startTime = ConvertFromUtc(rule.DateStart, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta);
+
+ endTime = ConvertFromUtc(rule.DateEnd, rule.DaylightDelta, rule.BaseUtcOffsetDelta);
+ }
+ else
+ {
+ startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart);
+ endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd);
+ }
return new DaylightTime(startTime, endTime, delta);
}
@@ -1596,6 +1756,32 @@ namespace System {
return isDst;
}
+ /// <summary>
+ /// Gets the offset that should be used to calculate DST start times from a UTC time.
+ /// </summary>
+ private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule)
+ {
+ if (rule.NoDaylightTransitions)
+ {
+ // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule
+ AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule);
+ return baseUtcOffset + previousRule.BaseUtcOffsetDelta + previousRule.DaylightDelta;
+ }
+ else
+ {
+ return baseUtcOffset + rule.BaseUtcOffsetDelta; /* FUTURE: + rule.StandardDelta; */
+ }
+ }
+
+ /// <summary>
+ /// Gets the offset that should be used to calculate DST end times from a UTC time.
+ /// </summary>
+ private TimeSpan GetDaylightSavingsEndOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule)
+ {
+ // NOTE: even NoDaylightTransitions rules use this logic since DST ends w.r.t. the current rule
+
+ return baseUtcOffset + rule.BaseUtcOffsetDelta + rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */
+ }
//
// GetIsDaylightSavingsFromUtc -
@@ -1610,9 +1796,9 @@ namespace System {
return false;
}
+
// Get the daylight changes for the year of the specified time.
- TimeSpan offset = utc + rule.BaseUtcOffsetDelta; /* FUTURE: + rule.StandardDelta; */
- DaylightTime daylightTime = GetDaylightTime(Year, rule);
+ DaylightTime daylightTime = zone.GetDaylightTime(Year, rule);
// The start and end times represent the range of universal times that are in DST for that year.
// Within that there is an ambiguous hour, usually right at the end, but at the beginning in
@@ -1626,20 +1812,22 @@ namespace System {
// Note we handle the similar case when rule year start with daylight saving and previous year end with daylight saving.
bool ignoreYearAdjustment = false;
+ TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule);
DateTime startTime;
if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year) {
AdjustmentRule previousYearRule = zone.GetAdjustmentRuleForTime(new DateTime(daylightTime.Start.Year - 1, 12, 31));
if (previousYearRule != null && previousYearRule.IsEndDateMarkerForEndOfYear()) {
- DaylightTime previousDaylightTime = GetDaylightTime(daylightTime.Start.Year - 1, previousYearRule);
+ DaylightTime previousDaylightTime = zone.GetDaylightTime(daylightTime.Start.Year - 1, previousYearRule);
startTime = previousDaylightTime.Start - utc - previousYearRule.BaseUtcOffsetDelta;
ignoreYearAdjustment = true;
} else {
- startTime = new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) - offset;
+ startTime = new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) - dstStartOffset;
}
} else {
- startTime = daylightTime.Start - offset;
+ startTime = daylightTime.Start - dstStartOffset;
}
+ TimeSpan dstEndOffset = zone.GetDaylightSavingsEndOffsetFromUtc(utc, rule);
DateTime endTime;
if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year) {
AdjustmentRule nextYearRule = zone.GetAdjustmentRuleForTime(new DateTime(daylightTime.End.Year + 1, 1, 1));
@@ -1647,15 +1835,15 @@ namespace System {
if (nextYearRule.IsEndDateMarkerForEndOfYear()) {// next year end with daylight saving on too
endTime = new DateTime(daylightTime.End.Year + 1, 12, 31) - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta;
} else {
- DaylightTime nextdaylightTime = GetDaylightTime(daylightTime.End.Year + 1, nextYearRule);
+ DaylightTime nextdaylightTime = zone.GetDaylightTime(daylightTime.End.Year + 1, nextYearRule);
endTime = nextdaylightTime.End - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta;
}
ignoreYearAdjustment = true;
} else {
- endTime = new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) - offset - rule.DaylightDelta; ;
+ endTime = new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) - dstEndOffset;
}
} else {
- endTime = daylightTime.End - offset - rule.DaylightDelta;
+ endTime = daylightTime.End - dstEndOffset;
}
DateTime ambiguousStart;
@@ -2317,7 +2505,7 @@ namespace System {
if (rule != null) {
baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
if (rule.HasDaylightSaving) {
- DaylightTime daylightTime = GetDaylightTime(time.Year, rule);
+ DaylightTime daylightTime = zone.GetDaylightTime(time.Year, rule);
Boolean isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags);
baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
}
@@ -2385,15 +2573,14 @@ namespace System {
year = 1;
}
else {
- DateTime targetTime = time + baseOffset;
+ rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true);
// As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset,
// sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases:
// “Libya Standard Time” when used with the date 2011-12-31T23:59:59.9999999Z
// "W. Australia Standard Time" used with date 2005-12-31T23:59:00.0000000Z
+ DateTime targetTime = time + baseOffset;
year = targetTime.Year;
-
- rule = zone.GetAdjustmentRuleForTime(targetTime);
}
if (rule != null) {
@@ -3353,371 +3540,493 @@ namespace System {
// BSD July 18, 2003 BSD
//
//
+ static private void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, TimeSpan baseUtcOffset, DateTime[] dts, Byte[] typeOfLocalTime,
+ TZifType[] transitionType, Boolean[] StandardTime, Boolean[] GmtTime, string futureTransitionsPosixFormat)
+ {
+ rules = null;
- //
- // TZif_CalculateTransitionTime -
- //
- // Example inputs:
- // -----------------
- // utc = 1918-03-31T10:00:00.0000000Z
- // transitionType = {-08:00:00 DST=False, Index 4}
- // standardTime = False
- // gmtTime = False
- //
- static private TransitionTime TZif_CalculateTransitionTime(DateTime utc, TimeSpan offset,
- TZifType transitionType, Boolean standardTime,
- Boolean gmtTime, out DateTime ruleDate) {
+ if (dts.Length > 0)
+ {
+ int index = 0;
+ List<AdjustmentRule> rulesList = new List<AdjustmentRule>();
- // convert from UTC to local clock time
- Int64 ticks = utc.Ticks + offset.Ticks;
- if (ticks > DateTime.MaxValue.Ticks) {
- utc = DateTime.MaxValue;
- }
- else if (ticks < DateTime.MinValue.Ticks) {
- utc = DateTime.MinValue;
+ while (index <= dts.Length)
+ {
+ TZif_GenerateAdjustmentRule(ref index, baseUtcOffset, rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime, futureTransitionsPosixFormat);
+ }
+
+ rules = rulesList.ToArray();
+ if (rules != null && rules.Length == 0)
+ {
+ rules = null;
+ }
}
- else {
- utc = new DateTime(ticks);
+ }
+
+ static private void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZoneBaseUtcOffset, List<AdjustmentRule> rulesList, DateTime[] dts,
+ Byte[] typeOfLocalTime, TZifType[] transitionTypes, Boolean[] StandardTime, Boolean[] GmtTime, string futureTransitionsPosixFormat)
+ {
+ // To generate AdjustmentRules, use the following approach:
+ // 1. The first AdjustmentRule goes from DateTime.Min to the first transition and uses the first standard transitionType
+ // (or the first transitionType if none of them are standard)
+ // 2. Create an AdjustmentRule for each transition, i.e. from dts[index - 1] to dts[index].
+ // This rule uses the transitionType[index - 1] and the whole AdjustmentRule only describes a single offset - either
+ // all daylight savings, or all stanard time.
+ // 3. After all the transitions are filled out, the last AdjustmentRule is created from either:
+ // a. a POSIX-style timezone description ("futureTransitionsPosixFormat"), if there is one or
+ // b. continue the last transition offset until DateTime.Max
+
+ if (index == 0)
+ {
+ TZifType transitionType = TZif_GetEarlyDateTransitionType(transitionTypes);
+ DateTime endTransitionDate = dts[index];
+
+ TimeSpan transitionOffset = TZif_CalculateTranistionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
+ TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+ TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+ AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+ DateTime.MinValue,
+ endTransitionDate.AddTicks(-1),
+ daylightDelta,
+ default(TransitionTime),
+ default(TransitionTime),
+ baseUtcDelta,
+ noDaylightTransitions: true);
+ rulesList.Add(r);
}
+ else if (index < dts.Length)
+ {
+ DateTime startTransitionDate = dts[index - 1];
+ TZifType startTransitionType = transitionTypes[typeOfLocalTime[index - 1]];
- DateTime timeOfDay = new DateTime(1, 1, 1, utc.Hour, utc.Minute, utc.Second, utc.Millisecond);
- int month = utc.Month;
- int day = utc.Day;
+ DateTime endTransitionDate = dts[index];
- ruleDate = new DateTime(utc.Year, month, day);
- // FUTURE: take standardTime/gmtTime into account
- return TransitionTime.CreateFixedDateRule(timeOfDay, month, day);
- }
+ TimeSpan transitionOffset = TZif_CalculateTranistionOffsetFromBase(startTransitionType.UtcOffset, timeZoneBaseUtcOffset);
+ TimeSpan daylightDelta = startTransitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+ TimeSpan baseUtcDelta = startTransitionType.IsDst ? TimeSpan.Zero : transitionOffset;
- static private void TZif_GenerateAdjustmentRules(out AdjustmentRule[] rules, DateTime[] dts, Byte[] typeOfLocalTime,
- TZifType[] transitionType, Boolean[] StandardTime, Boolean[] GmtTime) {
- rules = null;
+ TransitionTime dstStart;
+ if (startTransitionType.IsDst)
+ {
+ // the TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true.
+ // However, there are some cases in the past where DST = true, and the daylight savings offset
+ // now equals what the current BaseUtcOffset is. In that case, the AdjustmentRule.DaylightOffset
+ // is going to be TimeSpan.Zero. But we still need to return 'true' from AdjustmentRule.HasDaylightSaving.
+ // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic
+ // in HasDaylightSaving return true.
+ dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1);
+ }
+ else
+ {
+ dstStart = default(TransitionTime);
+ }
- int index = 0;
- List<AdjustmentRule> rulesList = new List<AdjustmentRule>(1);
- bool succeeded = true;
-
- while (succeeded && index < dts.Length) {
- succeeded = TZif_GenerateAdjustmentRule(ref index, ref rulesList, dts, typeOfLocalTime, transitionType, StandardTime, GmtTime);
+ AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ endTransitionDate.AddTicks(-1),
+ daylightDelta,
+ dstStart,
+ default(TransitionTime),
+ baseUtcDelta,
+ noDaylightTransitions: true);
+ rulesList.Add(r);
}
+ else
+ {
+ // create the AdjustmentRule that will be used for all DateTimes after the last transition
- rules = rulesList.ToArray();
- if (rules != null && rules.Length == 0) {
- rules = null;
+ // NOTE: index == dts.Length
+ DateTime startTransitionDate = dts[index - 1];
+
+ if (!string.IsNullOrEmpty(futureTransitionsPosixFormat))
+ {
+ AdjustmentRule r = TZif_CreateAdjustmentRuleForPosixFormat(futureTransitionsPosixFormat, startTransitionDate, timeZoneBaseUtcOffset);
+ if (r != null)
+ {
+ rulesList.Add(r);
+ }
+ }
+ else
+ {
+ // just use the last transition as the rule which will be used until the end of time
+
+ TZifType transitionType = transitionTypes[typeOfLocalTime[index - 1]];
+ TimeSpan transitionOffset = TZif_CalculateTranistionOffsetFromBase(transitionType.UtcOffset, timeZoneBaseUtcOffset);
+ TimeSpan daylightDelta = transitionType.IsDst ? transitionOffset : TimeSpan.Zero;
+ TimeSpan baseUtcDelta = transitionType.IsDst ? TimeSpan.Zero : transitionOffset;
+
+ AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ DateTime.MaxValue,
+ daylightDelta,
+ default(TransitionTime),
+ default(TransitionTime),
+ baseUtcDelta,
+ noDaylightTransitions: true);
+ rulesList.Add(r);
+ }
}
- }
+ index++;
+ }
- static private bool TZif_GenerateAdjustmentRule(ref int startIndex, ref List<AdjustmentRule> rulesList, DateTime[] dts, Byte[] typeOfLocalTime,
- TZifType[] transitionType, Boolean[] StandardTime, Boolean[] GmtTime) {
+ private static TimeSpan TZif_CalculateTranistionOffsetFromBase(TimeSpan transitionOffset, TimeSpan timeZoneBaseUtcOffset)
+ {
+ TimeSpan result = transitionOffset - timeZoneBaseUtcOffset;
- int index = startIndex;
- bool Dst = false;
- int DstStartIndex = -1;
- int DstEndIndex = -1;
- DateTime startDate = DateTime.MinValue.Date;
- DateTime endDate = DateTime.MaxValue.Date;
+ // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
+ // with DateTimeOffset, SQL Server, and the W3C XML Specification
+ if (result.Ticks % TimeSpan.TicksPerMinute != 0)
+ {
+ result = new TimeSpan(result.Hours, result.Minutes, 0);
+ }
+ return result;
+ }
- // find the next DST transition start time index
- while (!Dst && index < typeOfLocalTime.Length) {
- int typeIndex = typeOfLocalTime[index];
- if (typeIndex < transitionType.Length && transitionType[typeIndex].IsDst) {
- // found the next DST transition start time
- Dst = true;
- DstStartIndex = index;
- }
- else {
- index++;
+ /// <summary>
+ /// Gets the first standard-time transition type, or simply the first transition type
+ /// if there are no standard transition types.
+ /// </summary>>
+ /// <remarks>
+ /// from 'man tzfile':
+ /// localtime(3) uses the first standard-time ttinfo structure in the file
+ /// (or simply the first ttinfo structure in the absence of a standard-time
+ /// structure) if either tzh_timecnt is zero or the time argument is less
+ /// than the first transition time recorded in the file.
+ /// </remarks>
+ private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTypes)
+ {
+ for (int i = 0; i < transitionTypes.Length; i++)
+ {
+ TZifType transitionType = transitionTypes[i];
+ if (!transitionType.IsDst)
+ {
+ return transitionType;
}
}
- // find the next DST transition end time index
- while (Dst && index < typeOfLocalTime.Length) {
- int typeIndex = typeOfLocalTime[index];
- if (typeIndex < transitionType.Length && !transitionType[typeIndex].IsDst) {
- // found the next DST transition end time
- Dst = false;
- DstEndIndex = index;
- }
- else {
- index++;
- }
+ if (transitionTypes.Length > 0)
+ {
+ return transitionTypes[0];
}
+ throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_NoTTInfoStructures"));
+ }
- //
- // construct the adjustment rule from the two indices
- //
- if (DstStartIndex >= 0) {
- DateTime startTransitionDate = dts[DstStartIndex];
- DateTime endTransitionDate;
+ /// <summary>
+ /// Creates an AdjustmentRule given the POSIX TZ environment variable string.
+ /// </summary>
+ /// <remarks>
+ /// See http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html for the format and semantics of this POSX string.
+ /// </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))
+ {
+ // a valid posixFormat has at least standardName and standardOffset
+ TimeSpan? parsedBaseOffset = TZif_ParseOffsetString(standardOffset);
+ if (parsedBaseOffset.HasValue)
+ {
+ TimeSpan baseOffset = parsedBaseOffset.Value.Negate(); // offsets are backwards in POSIX notation
+ baseOffset = TZif_CalculateTranistionOffsetFromBase(baseOffset, timeZoneBaseUtcOffset);
- if (DstEndIndex == -1) {
- // we found a DST start but no DST end; in this case use the
- // prior non-DST entry if it exists, else use the current entry for both start and end (e.g., zero daylightDelta)
- if (DstStartIndex > 0) {
- DstEndIndex = DstStartIndex - 1;
+ // having a daylightSavingsName means there is a DST rule
+ if (!string.IsNullOrEmpty(daylightSavingsName))
+ {
+ TimeSpan? parsedDaylightSavings = TZif_ParseOffsetString(daylightSavingsOffset);
+ TimeSpan daylightSavingsTimeSpan;
+ if (!parsedDaylightSavings.HasValue)
+ {
+ // default DST to 1 hour if it isn't specified
+ daylightSavingsTimeSpan = new TimeSpan(1, 0, 0);
+ }
+ else
+ {
+ daylightSavingsTimeSpan = parsedDaylightSavings.Value.Negate(); // offsets are backwards in POSIX notation
+ daylightSavingsTimeSpan = TZif_CalculateTranistionOffsetFromBase(daylightSavingsTimeSpan, timeZoneBaseUtcOffset);
+ daylightSavingsTimeSpan = TZif_CalculateTranistionOffsetFromBase(daylightSavingsTimeSpan, baseOffset);
+ }
+
+ TransitionTime dstStart = TZif_CreateTransitionTimeFromPosixRule(start, startTime);
+ TransitionTime dstEnd = TZif_CreateTransitionTimeFromPosixRule(end, endTime);
+
+ return AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ DateTime.MaxValue,
+ daylightSavingsTimeSpan,
+ dstStart,
+ dstEnd,
+ baseOffset,
+ noDaylightTransitions: false);
}
- else {
- DstEndIndex = DstStartIndex;
+ else
+ {
+ // if there is no daylightSavingsName, the whole AdjustmentRule should be with no transitions - just the baseOffset
+ return AdjustmentRule.CreateAdjustmentRule(
+ startTransitionDate,
+ DateTime.MaxValue,
+ TimeSpan.Zero,
+ default(TransitionTime),
+ default(TransitionTime),
+ baseOffset,
+ noDaylightTransitions: true);
}
- endTransitionDate = DateTime.MaxValue;
- }
- else {
- endTransitionDate = dts[DstEndIndex];
}
+ }
+
+ return null;
+ }
- int dstStartTypeIndex = typeOfLocalTime[DstStartIndex];
- int dstEndTypeIndex = typeOfLocalTime[DstEndIndex];
+ private static TimeSpan? TZif_ParseOffsetString(string offset)
+ {
+ TimeSpan? result = null;
- TimeSpan daylightBias = transitionType[dstStartTypeIndex].UtcOffset - transitionType[dstEndTypeIndex].UtcOffset;
- // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns
- // with DateTimeOffset, SQL Server, and the W3C XML Specification
- if (daylightBias.Ticks % TimeSpan.TicksPerMinute != 0) {
- daylightBias = new TimeSpan(daylightBias.Hours, daylightBias.Minutes, 0);
+ if (!string.IsNullOrEmpty(offset))
+ {
+ bool negative = offset[0] == '-';
+ if (negative || offset[0] == '+')
+ {
+ offset = offset.Substring(1);
}
- //
- // the normal case is less than 12 months between transition times. However places like America/Catamarca
- // have DST from 1946-1963 straight without a gap. In that case we need to create a series of Adjustment
- // Rules to fudge the multi-year DST period
- //
- if ((endTransitionDate - startTransitionDate).Ticks <= TimeSpan.TicksPerDay * 364) {
- TransitionTime dstStart;
- TransitionTime dstEnd;
- TimeSpan startTransitionOffset = (DstStartIndex > 0 ? transitionType[typeOfLocalTime[DstStartIndex - 1]].UtcOffset : transitionType[dstEndTypeIndex].UtcOffset);
- TimeSpan endTransitionOffset = (DstEndIndex > 0 ? transitionType[typeOfLocalTime[DstEndIndex - 1]].UtcOffset : transitionType[dstStartTypeIndex].UtcOffset);
-
+ // Try parsing just hours first.
+ // Note, TimeSpan.TryParseExact "%h" can't be used here because some time zones using values
+ // like "26" or "144" and TimeSpan parsing would turn that into 26 or 144 *days* instead of hours.
+ int hours;
+ if (int.TryParse(offset, out hours))
+ {
+ result = new TimeSpan(hours, 0, 0);
+ }
+ else
+ {
+ TimeSpan parsedTimeSpan;
+ if (TimeSpan.TryParseExact(offset, "g", CultureInfo.InvariantCulture, out parsedTimeSpan))
+ {
+ result = parsedTimeSpan;
+ }
+ }
- dstStart = TZif_CalculateTransitionTime(startTransitionDate,
- startTransitionOffset,
- transitionType[dstStartTypeIndex],
- StandardTime[dstStartTypeIndex],
- GmtTime[dstStartTypeIndex],
- out startDate);
+ if (result.HasValue && negative)
+ {
+ result = result.Value.Negate();
+ }
+ }
- dstEnd = TZif_CalculateTransitionTime(endTransitionDate,
- endTransitionOffset,
- transitionType[dstEndTypeIndex],
- StandardTime[dstEndTypeIndex],
- GmtTime[dstEndTypeIndex],
- out endDate);
+ return result;
+ }
+ private static TransitionTime TZif_CreateTransitionTimeFromPosixRule(string date, string time)
+ {
+ if (string.IsNullOrEmpty(date))
+ {
+ return default(TransitionTime);
+ }
+ if (date[0] == 'M')
+ {
+ // Mm.w.d
+ // This specifies day d of week w of month m. The day d must be between 0(Sunday) and 6.The week w must be between 1 and 5;
+ // week 1 is the first week in which day d occurs, and week 5 specifies the last d day in the month. The month m should be between 1 and 12.
+
+ int month;
+ int week;
+ DayOfWeek day;
+ if (!TZif_ParseMDateRule(date, out month, out week, out day))
+ {
+ throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_UnparseablePosixMDateString", date));
+ }
- // calculate the AdjustmentRule end date
- if (DstStartIndex >= DstEndIndex) {
- // we found a DST start but no DST end
- endDate = DateTime.MaxValue.Date;
+ 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);
}
- AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(startDate, endDate, daylightBias, dstStart, dstEnd);
- rulesList.Add(r);
+ timeOfDay += timeOffset.Value;
}
- else {
- // create the multi-year DST rule series:
- //
- // For example America/Catamarca:
- // 1946-10-01T04:00:00.0000000Z {-03:00:00 DST=True}
- // 1963-10-01T03:00:00.0000000Z {-04:00:00 DST=False}
- //
- // gets converted into a series of overlapping 5/7month adjustment rules:
- //
- // [AdjustmentRule #0] // start rule
- // [1946/09/31 - 1947/06/15] // * starts 1 day prior to startTransitionDate
- // [start: 10/01 @4:00 ] // * N months long, stopping at month 6 or 11
- // [end : 07/15 ] // notice how the _end_ is outside the range
- //
- // [AdjustmentRule #1] // middle-year all-DST rule
- // [1947/06/16 - 1947/11/15] // * starts 1 day after last day in previous rule
- // [start: 05/15 ] // * 5 months long, stopping at month 6 or 11
- // [end : 12/15 ] // notice how the _start and end_ are outside the range
- //
- // [AdjustmentRule #2] // middle-year all-DST rule
- // [1947/11/16 - 1947/06/15] // * starts 1 day after last day in previous rule
- // [start: 10/01 ] // * 7 months long, stopping at month 6 or 11
- // [end : 07/15 ] // notice how the _start and end_ are outside the range
- //
- // .........................
- //
- // [AdjustmentRule #N] // end rule
- // [1963/06/16 - 1946/10/02] // * starts 1 day after last day in previous rule
- // [start: 05/15 ] // * N months long, stopping 1 day after endTransitionDate
- // [end : 10/01 ] // notice how the _start_ is outside the range
- //
-
- // create the first rule from N to either 06/15 or 11/15
- TZif_CreateFirstMultiYearRule(ref rulesList, daylightBias, startTransitionDate, DstStartIndex, dstStartTypeIndex, dstEndTypeIndex,
- dts, transitionType, typeOfLocalTime, StandardTime, GmtTime);
-
- // create the filler rules
- TZif_CreateMiddleMultiYearRules(ref rulesList, daylightBias, endTransitionDate);
-
- // create the last rule
- TZif_CreateLastMultiYearRule(ref rulesList, daylightBias, endTransitionDate, DstStartIndex, dstStartTypeIndex, DstEndIndex, dstEndTypeIndex,
- dts, transitionType, typeOfLocalTime, StandardTime, GmtTime);
+ else
+ {
+ // default to 2AM.
+ timeOfDay = new DateTime(1, 1, 1, 2, 0, 0);
}
- startIndex = index + 1;
- return true;
+ return TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, day);
}
+ else
+ {
+ // Jn
+ // This specifies the Julian day, with n between 1 and 365.February 29 is never counted, even in leap years.
- // setup the start values for the next call to TZif_GenerateAdjustmentRule(...)
- startIndex = index + 1;
- return false; // did not create a new AdjustmentRule
- }
-
- static private void TZif_CreateFirstMultiYearRule(ref List<AdjustmentRule> rulesList, TimeSpan daylightBias, DateTime startTransitionDate,
- int DstStartIndex, int dstStartTypeIndex, int dstEndTypeIndex, DateTime[] dts, TZifType[] transitionType,
- Byte[] typeOfLocalTime, bool[] StandardTime, bool[] GmtTime) {
-
- // [AdjustmentRule #0] // start rule
- // [1946/09/31 - 1947/06/15] // * starts 1 day prior to startTransitionDate
- // [start: 10/01 @4:00 ] // * N months long, stopping at month 6 or 11
- // [end : 07/15 ] // notice how the _end_ is outside the range
+ // n
+ // This specifies the Julian day, with n between 0 and 365.February 29 is counted in leap years.
- DateTime startDate;
- DateTime endDate;
- TransitionTime dstStart;
- TransitionTime dstEnd;
+ // 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
- TimeSpan startTransitionOffset = (DstStartIndex > 0 ? transitionType[typeOfLocalTime[DstStartIndex - 1]].UtcOffset : transitionType[dstEndTypeIndex].UtcOffset);
+ throw new InvalidTimeZoneException(Environment.GetResourceString("InvalidTimeZone_JulianDayNotSupported"));
+ }
+ }
- dstStart = TZif_CalculateTransitionTime(startTransitionDate,
- startTransitionOffset,
- transitionType[dstStartTypeIndex],
- StandardTime[dstStartTypeIndex],
- GmtTime[dstStartTypeIndex],
- out startDate);
+ /// <summary>
+ /// Parses a string like Mm.w.d into month, week and DayOfWeek values.
+ /// </summary>
+ /// <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)
+ {
+ month = 0;
+ week = 0;
+ dayOfWeek = default(DayOfWeek);
- //
- // Choosing the endDate based on the startDate:
- //
- // startTransitionDate.Month -> end
- // 1 4|5 8|9 12
- // [-> 06/15]|[-> 11/15]|[-> 06/15]
- //
- int startDateMonth = startDate.Month;
- int startDateYear = startDate.Year;
+ if (dateRule[0] == 'M')
+ {
+ int firstDotIndex = dateRule.IndexOf('.');
+ if (firstDotIndex > 0)
+ {
+ int secondDotIndex = dateRule.IndexOf('.', firstDotIndex + 1);
+ if (secondDotIndex > 0)
+ {
+ string monthString = dateRule.Substring(1, firstDotIndex - 1);
+ string weekString = dateRule.Substring(firstDotIndex + 1, secondDotIndex - firstDotIndex - 1);
+ string dayString = dateRule.Substring(secondDotIndex + 1);
- if (startDateMonth <= 4) {
- endDate = new DateTime(startDateYear, 06, 15);
- dstEnd = c_transition7_15;
- } else if (startDateMonth <= 8) {
- endDate = new DateTime(startDateYear, 11, 15);
- dstEnd = c_transition12_15;
- }
- else if (startDateYear < 9999) {
- endDate = new DateTime(startDateYear+1, 06, 15);
- dstEnd = c_transition7_15;
- }
- else {
- endDate = DateTime.MaxValue;
- dstEnd = c_transition7_15;
+ if (int.TryParse(monthString, out month))
+ {
+ if (int.TryParse(weekString, out week))
+ {
+ int day;
+ if (int.TryParse(dayString, out day))
+ {
+ dayOfWeek = (DayOfWeek)day;
+ return true;
+ }
+ }
+ }
+ }
+ }
}
- AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(startDate, endDate, daylightBias, dstStart, dstEnd);
- rulesList.Add(r);
- }
+ return false;
+ }
+ 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)
+ {
+ standardName = null;
+ standardOffset = null;
+ daylightSavingsName = null;
+ daylightSavingsOffset = null;
+ start = null;
+ startTime = null;
+ end = null;
+ endTime = null;
- static private void TZif_CreateLastMultiYearRule(ref List<AdjustmentRule> rulesList, TimeSpan daylightBias, DateTime endTransitionDate,
- int DstStartIndex, int dstStartTypeIndex, int DstEndIndex, int dstEndTypeIndex, DateTime[] dts, TZifType[] transitionType,
- Byte[] typeOfLocalTime, bool[] StandardTime, bool[] GmtTime) {
+ int index = 0;
+ standardName = TZif_ParsePosixName(posixFormat, ref index);
+ standardOffset = TZif_ParsePosixOffset(posixFormat, ref index);
- // [AdjustmentRule #N] // end rule
- // [1963/06/16 - 1946/10/02] // * starts 1 day after last day in previous rule
- // [start: 05/15 ] // * N months long, stopping 1 day after endTransitionDate
- // [end : 10/01 ] // notice how the _start_ is outside the range
+ daylightSavingsName = TZif_ParsePosixName(posixFormat, ref index);
+ if (!string.IsNullOrEmpty(daylightSavingsName))
+ {
+ daylightSavingsOffset = TZif_ParsePosixOffset(posixFormat, ref index);
- DateTime endDate;
- TransitionTime dstEnd;
+ if (index < posixFormat.Length && posixFormat[index] == ',')
+ {
+ index++;
+ TZif_ParsePosixDateTime(posixFormat, ref index, out start, out startTime);
- TimeSpan endTransitionOffset = (DstEndIndex > 0 ? transitionType[typeOfLocalTime[DstEndIndex - 1]].UtcOffset : transitionType[dstStartTypeIndex].UtcOffset);
+ if (index < posixFormat.Length && posixFormat[index] == ',')
+ {
+ index++;
+ TZif_ParsePosixDateTime(posixFormat, ref index, out end, out endTime);
+ }
+ }
+ }
+ return !string.IsNullOrEmpty(standardName) && !string.IsNullOrEmpty(standardOffset);
+ }
- dstEnd = TZif_CalculateTransitionTime(endTransitionDate,
- endTransitionOffset,
- transitionType[dstEndTypeIndex],
- StandardTime[dstEndTypeIndex],
- GmtTime[dstEndTypeIndex],
- out endDate);
+ private static string TZif_ParsePosixName(string posixFormat, ref int index)
+ {
+ return TZif_ParsePosixString(posixFormat, ref index, c => char.IsDigit(c) || c == '+' || c == '-' || c == ',');
+ }
- if (DstStartIndex >= DstEndIndex) {
- // we found a DST start but no DST end
- endDate = DateTime.MaxValue.Date;
- }
+ private static string TZif_ParsePosixOffset(string posixFormat, ref int index)
+ {
+ return TZif_ParsePosixString(posixFormat, ref index, c => !char.IsDigit(c) && c != '+' && c != '-' && c != ':');
+ }
- AdjustmentRule prevRule = rulesList[ rulesList.Count - 1]; // grab the last element of the MultiYearRule sequence
- int y = prevRule.DateEnd.Year;
- if (prevRule.DateEnd.Month <= 6) {
- // create a rule from 06/16/YYYY to endDate
- AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(new DateTime(y, 06, 16), endDate, daylightBias, c_transition5_15, dstEnd);
- rulesList.Add(r);
- }
- else {
- // create a rule from 11/16/YYYY to endDate
- AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(new DateTime(y, 11, 16), endDate, daylightBias, c_transition10_15, dstEnd);
- rulesList.Add(r);
+ private static void TZif_ParsePosixDateTime(string posixFormat, ref int index, out string date, out string time)
+ {
+ time = null;
+
+ date = TZif_ParsePosixDate(posixFormat, ref index);
+ if (index < posixFormat.Length && posixFormat[index] == '/')
+ {
+ index++;
+ time = TZif_ParsePosixTime(posixFormat, ref index);
}
- }
+ }
-
- static private void TZif_CreateMiddleMultiYearRules(ref List<AdjustmentRule> rulesList, TimeSpan daylightBias, DateTime endTransitionDate) {
- //
- // [AdjustmentRule #1] // middle-year all-DST rule
- // [1947/06/16 - 1947/11/15] // * starts 1 day after last day in previous rule
- // [start: 05/15 ] // * 5 months long, stopping at month 6 or 11
- // [end : 12/15 ] // notice how the _start and end_ are outside the range
- //
- // [AdjustmentRule #2] // middle-year all-DST rule
- // [1947/11/16 - 1947/06/15] // * starts 1 day after last day in previous rule
- // [start: 10/01 ] // * 7 months long, stopping at month 6 or 11
- // [end : 07/15 ] // notice how the _start and end_ are outside the range
- //
- // .........................
+ private static string TZif_ParsePosixDate(string posixFormat, ref int index)
+ {
+ return TZif_ParsePosixString(posixFormat, ref index, c => c == '/' || c == ',');
+ }
- AdjustmentRule prevRule = rulesList[ rulesList.Count - 1]; // grab the first element of the MultiYearRule sequence
- DateTime endDate;
+ private static string TZif_ParsePosixTime(string posixFormat, ref int index)
+ {
+ return TZif_ParsePosixString(posixFormat, ref index, c => c == ',');
+ }
- //
- // Choosing the last endDate based on the endTransitionDate
- //
- // endTransitionDate.Month -> end
- // 1 4|5 8|9 12
- // [11/15 <-]|[11/15 <-]|[06/15 <-]
- //
- if (endTransitionDate.Month <= 8) {
- // set the end date to 11/15/YYYY-1
- endDate = new DateTime(endTransitionDate.Year - 1, 11, 15);
- }
- else {
- // set the end date to 06/15/YYYY
- endDate = new DateTime(endTransitionDate.Year, 06, 15);
- }
-
- while (prevRule.DateEnd < endDate) {
- // the last endDate will be on either 06/15 or 11/15
- int y = prevRule.DateEnd.Year;
- if (prevRule.DateEnd.Month <= 6) {
- // create a rule from 06/16/YYYY to 11/15/YYYY
- AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(new DateTime(y, 06, 16), new DateTime(y, 11, 15),
- daylightBias, c_transition5_15, c_transition12_15);
- prevRule = r;
- rulesList.Add(r);
- }
- else {
- // create a rule from 11/16/YYYY to 06/15/YYYY+1
- AdjustmentRule r = AdjustmentRule.CreateAdjustmentRule(new DateTime(y, 11, 16), new DateTime(y+1, 06, 15),
- daylightBias, c_transition10_15, c_transition7_15);
- prevRule = r;
- rulesList.Add(r);
+ private static string TZif_ParsePosixString(string posixFormat, ref int index, Func<char, bool> breakCondition)
+ {
+ int startIndex = index;
+ for (; index < posixFormat.Length; index++)
+ {
+ char current = posixFormat[index];
+ if (breakCondition(current))
+ {
+ break;
}
}
- }
+ return posixFormat.Substring(startIndex, index - startIndex);
+ }
// Returns the Substring from zoneAbbreviations starting at index and ending at '\0'
// zoneAbbreviations is expected to be in the form: "PST\0PDT\0PWT\0\PPT"
@@ -3731,21 +4040,6 @@ namespace System {
}
}
- // verify the 'index' is referenced from the typeOfLocalTime byte array.
- //
- static private Boolean TZif_ValidTransitionType(int index, Byte[] typeOfLocalTime) {
- Boolean result = false;
-
- if (typeOfLocalTime != null) {
- for (int i = 0; !result && i < typeOfLocalTime.Length; i++) {
- if (index == typeOfLocalTime[i]) {
- result = true;
- }
- }
- }
- return result;
- }
-
// Converts an array of bytes into an int - always using standard byte order (Big Endian)
// per TZif file standard
[System.Security.SecuritySafeCritical] // auto-generated
@@ -3755,9 +4049,34 @@ namespace System {
}
}
- static private void TZif_ParseRaw(Byte[] data, out TZifHead t, out DateTime[] dts, out Byte[] typeOfLocalTime, out TZifType[] transitionType,
- out String zoneAbbreviations, out Boolean[] StandardTime, out Boolean[] GmtTime) {
+ // Converts an array of bytes into a long - always using standard byte order (Big Endian)
+ // per TZif file standard
+ [System.Security.SecuritySafeCritical] // auto-generated
+ static private unsafe long TZif_ToInt64(byte[] value, int startIndex)
+ {
+ fixed (byte* pbyte = &value[startIndex])
+ {
+ int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3));
+ int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7));
+ return (uint)i2 | ((long)i1 << 32);
+ }
+ }
+
+ static private long TZif_ToUnixTime(byte[] value, int startIndex, TZVersion version)
+ {
+ if (version != TZVersion.V1)
+ {
+ return TZif_ToInt64(value, startIndex);
+ }
+ else
+ {
+ return TZif_ToInt32(value, startIndex);
+ }
+ }
+ static private void TZif_ParseRaw(Byte[] data, out TZifHead t, out DateTime[] dts, out Byte[] typeOfLocalTime, out TZifType[] transitionType,
+ out String zoneAbbreviations, out Boolean[] StandardTime, out Boolean[] GmtTime, out string futureTransitionsPosixFormat)
+ {
// initialize the out parameters in case the TZifHead ctor throws
dts = null;
typeOfLocalTime = null;
@@ -3765,11 +4084,25 @@ namespace System {
zoneAbbreviations = String.Empty;
StandardTime = null;
GmtTime = null;
+ futureTransitionsPosixFormat = null;
// read in the 44-byte TZ header containing the count/length fields
//
- t = new TZifHead(data, 0);
- int index = TZifHead.Length;
+ int index = 0;
+ t = new TZifHead(data, index);
+ index += TZifHead.Length;
+
+ int timeValuesLength = 4; // the first version uses 4-bytes to specify times
+ if (t.Version != TZVersion.V1)
+ {
+ // move index past the V1 information to read the V2 information
+ index += (int)((timeValuesLength * t.TimeCount) + t.TimeCount + (6 * t.TypeCount) + ((timeValuesLength + 4) * t.LeapCount) + t.IsStdCount + t.IsGmtCount + t.CharCount);
+
+ // read the V2 header
+ t = new TZifHead(data, index);
+ index += TZifHead.Length;
+ timeValuesLength = 8; // the second version uses 8-bytes
+ }
// initialize the containers for the rest of the TZ data
dts = new DateTime[t.TimeCount];
@@ -3779,13 +4112,12 @@ namespace System {
StandardTime = new Boolean[t.TypeCount];
GmtTime = new Boolean[t.TypeCount];
-
- // read in the 4-byte UTC transition points and convert them to Windows
+ // read in the UTC transition points and convert them to Windows
//
for (int i = 0; i < t.TimeCount; i++) {
- int unixTime = TZif_ToInt32(data, index);
- dts[i] = TZif_UnixTimeToWindowsTime(unixTime);
- index += 4;
+ long unixTime = TZif_ToUnixTime(data, index, t.Version);
+ dts[i] = DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime;
+ index += timeValuesLength;
}
// read in the Type Indices; there is a 1:1 mapping of UTC transition points to Type Indices
@@ -3816,7 +4148,7 @@ namespace System {
// skip ahead of the Leap-Seconds Adjustment data. In a future release, consider adding
// support for Leap-Seconds
//
- index += (int)(t.LeapCount * 8); // skip the leap second transition times
+ index += (int)(t.LeapCount * (timeValuesLength + 4)); // skip the leap second transition times
// read in the Standard Time table. There should be a 1:1 mapping between Type-Index and Standard
// Time table entries.
@@ -3839,17 +4171,15 @@ namespace System {
for (int i = 0; i < t.IsGmtCount && i < t.TypeCount && index < data.Length; i++) {
GmtTime[i] = (data[index++] != 0);
}
- }
-
- // Windows NT time is specified as the number of 100 nanosecond intervals since January 1, 1601.
- // UNIX time is specified as the number of seconds since January 1, 1970. There are 134,774 days
- // (or 11,644,473,600 seconds) between these dates.
- //
- private static DateTime TZif_UnixTimeToWindowsTime(int unixTime) {
- // Add 11,644,473,600 and multiply by 10,000,000.
- Int64 ntTime = (((Int64)unixTime) + 11644473600) * 10000000;
- return DateTime.FromFileTimeUtc(ntTime);
+ if (t.Version != TZVersion.V1)
+ {
+ // read the POSIX-style format, which should be wrapped in newlines with the last newline at the end of the file
+ if (data[index++] == '\n' && data[data.Length - 1] == '\n')
+ {
+ futureTransitionsPosixFormat = enc.GetString(data, index, data.Length - index - 1);
+ }
+ }
}
#endif // PLATFORM_UNIX
@@ -3922,8 +4252,7 @@ namespace System {
if (UtcOffsetOutOfRange(baseUtcOffset + current.DaylightDelta)) {
throw new InvalidTimeZoneException(Environment.GetResourceString("ArgumentOutOfRange_UtcOffsetAndDaylightDelta"));
- }
-
+ }
if (prev != null && current.DateStart <= prev.DateEnd) {
// verify the rules are in chronological order and the DateStart/DateEnd do not overlap
@@ -3958,7 +4287,7 @@ namespace System {
private TransitionTime m_daylightTransitionStart;
private TransitionTime m_daylightTransitionEnd;
private TimeSpan m_baseUtcOffsetDelta; // delta from the default Utc offset (utcOffset = defaultUtcOffset + m_baseUtcOffsetDelta)
-
+ private bool m_noDaylightTransitions;
// ---- SECTION: public properties --------------*
public DateTime DateStart {
@@ -3999,11 +4328,25 @@ namespace System {
}
}
+ /// <summary>
+ /// Gets a value indicating that this AdjustmentRule fixes the time zone offset
+ /// from DateStart to DateEnd without any daylight transitions in between.
+ /// </summary>
+ internal bool NoDaylightTransitions {
+ get {
+ return this.m_noDaylightTransitions;
+ }
+ }
+
internal bool HasDaylightSaving {
get {
- return this.DaylightDelta != TimeSpan.Zero ||
- this.DaylightTransitionStart.TimeOfDay != DateTime.MinValue ||
- this.DaylightTransitionEnd.TimeOfDay != DateTime.MinValue.AddMilliseconds(1);
+ return this.DaylightDelta != TimeSpan.Zero
+ ||
+ (this.DaylightTransitionStart != default(TransitionTime)
+ && this.DaylightTransitionStart.TimeOfDay != DateTime.MinValue)
+ ||
+ (this.DaylightTransitionEnd != default(TransitionTime)
+ && this.DaylightTransitionEnd.TimeOfDay != DateTime.MinValue.AddMilliseconds(1));
}
}
@@ -4037,15 +4380,15 @@ namespace System {
// -------- SECTION: factory methods -----------------*
- static public AdjustmentRule CreateAdjustmentRule(
+ static internal AdjustmentRule CreateAdjustmentRule(
DateTime dateStart,
DateTime dateEnd,
TimeSpan daylightDelta,
TransitionTime daylightTransitionStart,
- TransitionTime daylightTransitionEnd) {
-
+ TransitionTime daylightTransitionEnd,
+ bool noDaylightTransitions) {
ValidateAdjustmentRule(dateStart, dateEnd, daylightDelta,
- daylightTransitionStart, daylightTransitionEnd);
+ daylightTransitionStart, daylightTransitionEnd, noDaylightTransitions);
AdjustmentRule rule = new AdjustmentRule();
@@ -4055,22 +4398,37 @@ namespace System {
rule.m_daylightTransitionStart = daylightTransitionStart;
rule.m_daylightTransitionEnd = daylightTransitionEnd;
rule.m_baseUtcOffsetDelta = TimeSpan.Zero;
+ rule.m_noDaylightTransitions = noDaylightTransitions;
return rule;
}
+ static public AdjustmentRule CreateAdjustmentRule(
+ DateTime dateStart,
+ DateTime dateEnd,
+ TimeSpan daylightDelta,
+ TransitionTime daylightTransitionStart,
+ TransitionTime daylightTransitionEnd)
+ {
+ return CreateAdjustmentRule(dateStart, dateEnd, daylightDelta,
+ daylightTransitionStart, daylightTransitionEnd, noDaylightTransitions: false);
+ }
+
static internal AdjustmentRule CreateAdjustmentRule(
DateTime dateStart,
DateTime dateEnd,
TimeSpan daylightDelta,
TransitionTime daylightTransitionStart,
TransitionTime daylightTransitionEnd,
- TimeSpan baseUtcOffsetDelta) {
- AdjustmentRule rule = CreateAdjustmentRule(dateStart, dateEnd, daylightDelta, daylightTransitionStart, daylightTransitionEnd);
+ TimeSpan baseUtcOffsetDelta,
+ bool noDaylightTransitions) {
+ AdjustmentRule rule = CreateAdjustmentRule(dateStart, dateEnd, daylightDelta,
+ daylightTransitionStart, daylightTransitionEnd, noDaylightTransitions);
+
rule.m_baseUtcOffsetDelta = baseUtcOffsetDelta;
return rule;
}
-
+
// ----- SECTION: internal utility methods ----------------*
//
@@ -4078,7 +4436,8 @@ namespace System {
// We have to special case this value and not adjust it when checking if any date is in the daylight saving period.
//
internal bool IsStartDateMarkerForBeginningOfYear() {
- return DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 && DaylightTransitionStart.TimeOfDay.Hour == 0 &&
+ return !NoDaylightTransitions &&
+ DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 && DaylightTransitionStart.TimeOfDay.Hour == 0 &&
DaylightTransitionStart.TimeOfDay.Minute == 0 && DaylightTransitionStart.TimeOfDay.Second == 0 &&
m_dateStart.Year == m_dateEnd.Year;
}
@@ -4088,7 +4447,8 @@ namespace System {
// We have to special case this value and not adjust it when checking if any date is in the daylight saving period.
//
internal bool IsEndDateMarkerForEndOfYear() {
- return DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 && DaylightTransitionEnd.TimeOfDay.Hour == 0 &&
+ return !NoDaylightTransitions &&
+ DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 && DaylightTransitionEnd.TimeOfDay.Hour == 0 &&
DaylightTransitionEnd.TimeOfDay.Minute == 0 && DaylightTransitionEnd.TimeOfDay.Second == 0 &&
m_dateStart.Year == m_dateEnd.Year;
}
@@ -4104,18 +4464,19 @@ namespace System {
DateTime dateEnd,
TimeSpan daylightDelta,
TransitionTime daylightTransitionStart,
- TransitionTime daylightTransitionEnd) {
+ TransitionTime daylightTransitionEnd,
+ bool noDaylightTransitions) {
- if (dateStart.Kind != DateTimeKind.Unspecified) {
- throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeKindMustBeUnspecified"), "dateStart");
+ if (dateStart.Kind != DateTimeKind.Unspecified && dateStart.Kind != DateTimeKind.Utc) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeKindMustBeUnspecifiedOrUtc"), "dateStart");
}
- if (dateEnd.Kind != DateTimeKind.Unspecified) {
- throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeKindMustBeUnspecified"), "dateEnd");
+ if (dateEnd.Kind != DateTimeKind.Unspecified && dateEnd.Kind != DateTimeKind.Utc) {
+ throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeKindMustBeUnspecifiedOrUtc"), "dateEnd");
}
- if (daylightTransitionStart.Equals(daylightTransitionEnd)) {
+ if (daylightTransitionStart.Equals(daylightTransitionEnd) && !noDaylightTransitions) {
throw new ArgumentException(Environment.GetResourceString("Argument_TransitionTimesAreIdentical"),
"daylightTransitionEnd");
}
@@ -4125,7 +4486,11 @@ namespace System {
throw new ArgumentException(Environment.GetResourceString("Argument_OutOfOrderDateTimes"), "dateStart");
}
- if (TimeZoneInfo.UtcOffsetOutOfRange(daylightDelta)) {
+ // This cannot use UtcOffsetOutOfRange to account for the scenario where Samoa moved across the International Date Line,
+ // which caused their current BaseUtcOffset to be +13. But on the other side of the line it was UTC-11 (+1 for daylight).
+ // So when trying to describe DaylightDeltas for those times, the DaylightDelta needs
+ // to be -23 (what it takes to go from UTC+13 to UTC-10)
+ if (daylightDelta.TotalHours < -23.0 || daylightDelta.TotalHours > 14.0) {
throw new ArgumentOutOfRangeException("daylightDelta", daylightDelta,
Environment.GetResourceString("ArgumentOutOfRange_UtcOffset"));
}
@@ -4135,12 +4500,12 @@ namespace System {
"daylightDelta");
}
- if (dateStart.TimeOfDay != TimeSpan.Zero) {
+ if (dateStart != DateTime.MinValue && dateStart.Kind == DateTimeKind.Unspecified && dateStart.TimeOfDay != TimeSpan.Zero) {
throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeHasTimeOfDay"),
"dateStart");
}
- if (dateEnd.TimeOfDay != TimeSpan.Zero) {
+ if (dateEnd != DateTime.MaxValue && dateEnd.Kind == DateTimeKind.Unspecified && dateEnd.TimeOfDay != TimeSpan.Zero) {
throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeHasTimeOfDay"),
"dateEnd");
}
@@ -4158,7 +4523,7 @@ namespace System {
try {
ValidateAdjustmentRule(m_dateStart, m_dateEnd, m_daylightDelta,
- m_daylightTransitionStart, m_daylightTransitionEnd);
+ m_daylightTransitionStart, m_daylightTransitionEnd, m_noDaylightTransitions);
}
catch (ArgumentException e) {
throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e);
@@ -4178,6 +4543,7 @@ namespace System {
info.AddValue("DaylightTransitionStart", m_daylightTransitionStart);
info.AddValue("DaylightTransitionEnd", m_daylightTransitionEnd);
info.AddValue("BaseUtcOffsetDelta", m_baseUtcOffsetDelta);
+ info.AddValue("NoDaylightTransitions", m_noDaylightTransitions);
}
AdjustmentRule(SerializationInfo info, StreamingContext context) {
@@ -4195,10 +4561,15 @@ namespace System {
if (o != null) {
m_baseUtcOffsetDelta = (TimeSpan) o;
}
+
+ o = info.GetValueNoThrow("NoDaylightTransitions", typeof(bool));
+ if (o != null) {
+ m_noDaylightTransitions = (bool)o;
+ }
}
#endif
}
-
+
/*============================================================
**
@@ -4547,6 +4918,10 @@ namespace System {
serializedText.Append(SerializeSubstitute(rule.BaseUtcOffsetDelta.TotalMinutes.ToString(CultureInfo.InvariantCulture)));
serializedText.Append(sep);
}
+ if (rule.NoDaylightTransitions) { // Serialize it only when NoDaylightTransitions is true to reduce the impact of adding rule.NoDaylightTransitions
+ serializedText.Append(SerializeSubstitute("1"));
+ serializedText.Append(sep);
+ }
serializedText.Append(rhs);
}
}
@@ -4935,6 +5310,8 @@ namespace System {
TransitionTime daylightStart = GetNextTransitionTimeValue(false);
TransitionTime daylightEnd = GetNextTransitionTimeValue(false);
TimeSpan baseUtcOffsetDelta = TimeSpan.Zero;
+ Int32 noDaylightTransitions = 0;
+
// verify that the string is now at the right-hand-side marker ("]") ...
if (m_state == State.EndOfLine || m_currentTokenStartIndex >= m_serializedText.Length) {
@@ -4947,6 +5324,11 @@ namespace System {
baseUtcOffsetDelta = GetNextTimeSpanValue(false);
}
+ // Check if we have NoDaylightTransitions in the serialized string and then deserialize it
+ if ((m_serializedText[m_currentTokenStartIndex] >= '0' && m_serializedText[m_currentTokenStartIndex] <= '1')) {
+ noDaylightTransitions = GetNextInt32Value(false);
+ }
+
if (m_state == State.EndOfLine || m_currentTokenStartIndex >= m_serializedText.Length) {
throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"));
}
@@ -4967,7 +5349,7 @@ namespace System {
AdjustmentRule rule;
try {
- rule = AdjustmentRule.CreateAdjustmentRule(dateStart, dateEnd, daylightDelta, daylightStart, daylightEnd, baseUtcOffsetDelta);
+ rule = AdjustmentRule.CreateAdjustmentRule(dateStart, dateEnd, daylightDelta, daylightStart, daylightEnd, baseUtcOffsetDelta, noDaylightTransitions > 0);
}
catch (ArgumentException e) {
throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e);
@@ -5157,13 +5539,19 @@ namespace System {
throw new ArgumentException(Environment.GetResourceString("Argument_TimeZoneInfoBadTZif"), "data");
}
+ byte version = data[index + 04];
+ Version = version == '2' ? TZVersion.V2 :
+ version == '3' ? TZVersion.V3 :
+ TZVersion.V1; // default/fallback to V1 to guard against future, unsupported version numbers
+
+ // skip the 15 byte reserved field
+
// don't use the BitConverter class which parses data
// based on the Endianess of the machine architecture.
// this data is expected to always be in "standard byte order",
// regardless of the machine it is being processed on.
IsGmtCount = (uint)TZif_ToInt32(data, index + 20);
- // skip the 16 byte reserved field
IsStdCount = (uint)TZif_ToInt32(data, index + 24);
LeapCount = (uint)TZif_ToInt32(data, index + 28);
TimeCount = (uint)TZif_ToInt32(data, index + 32);
@@ -5172,7 +5560,8 @@ namespace System {
}
public UInt32 Magic; // TZ_MAGIC "TZif"
- // public Byte[16] Reserved; // reserved for future use
+ public TZVersion Version; // 1 byte for a \0 or 2 or 3
+ // public Byte[15] Reserved; // reserved for future use
public UInt32 IsGmtCount; // number of transition time flags
public UInt32 IsStdCount; // number of transition time flags
public UInt32 LeapCount; // number of leap seconds
@@ -5180,6 +5569,14 @@ namespace System {
public UInt32 TypeCount; // number of local time types
public UInt32 CharCount; // number of abbreviated characters
}
+
+ private enum TZVersion : byte
+ {
+ V1 = 0,
+ V2,
+ V3,
+ // when adding more versions, ensure all the logic using TZVersion is still correct
+ }
#endif // PLATFORM_UNIX
} // TimezoneInfo
diff --git a/src/mscorlib/src/mscorlib.txt b/src/mscorlib/src/mscorlib.txt
index 6e3df77ccd..39a40d2b0f 100644
--- a/src/mscorlib/src/mscorlib.txt
+++ b/src/mscorlib/src/mscorlib.txt
@@ -2304,7 +2304,7 @@ Argument_DateTimeHasTimeOfDay = The supplied DateTime includes a TimeOfDay setti
Argument_DateTimeIsInvalid = The supplied DateTime represents an invalid time. For example, when the clock is adjusted forward, any time in the period that is skipped is invalid.
Argument_DateTimeIsNotAmbiguous = The supplied DateTime is not in an ambiguous time range.
Argument_DateTimeOffsetIsNotAmbiguous = The supplied DateTimeOffset is not in an ambiguous time range.
-Argument_DateTimeKindMustBeUnspecified = The supplied DateTime must have the Kind property set to DateTimeKind.Unspecified.
+Argument_DateTimeKindMustBeUnspecifiedOrUtc = The supplied DateTime must have the Kind property set to DateTimeKind.Unspecified or DateTimeKind.Utc.
Argument_DateTimeHasTicks = The supplied DateTime must have the Year, Month, and Day properties set to 1. The time cannot be specified more precisely than whole milliseconds.
Argument_InvalidId = The specified ID parameter '{0}' is not supported.
Argument_InvalidSerializedString = The specified serialized string '{0}' is not supported.
@@ -2329,6 +2329,9 @@ ArgumentOutOfRange_Week = The Week parameter must be in the range 1 through 5.
InvalidTimeZone_InvalidRegistryData = The time zone ID '{0}' was found on the local computer, but the registry information was corrupt.
InvalidTimeZone_InvalidFileData = The time zone ID '{0}' was found on the local computer, but the file at '{1}' was corrupt.
InvalidTimeZone_InvalidWin32APIData = The Local time zone was found on the local computer, but the data was corrupt.
+InvalidTimeZone_NoTTInfoStructures = There are no ttinfo structures in the tzfile. At least one ttinfo structure is required in order to construct a TimeZoneInfo object.
+InvalidTimeZone_UnparseablePosixMDateString = '{0}' is not a valid POSIX-TZ-environment-variable MDate rule. A valid rule has the format 'Mm.w.d'.
+InvalidTimeZone_JulianDayNotSupported = Julian dates in POSIX strings are unsupported.
;
; begin System.TimeZoneInfo SecurityException's
;