internal static BclAdjustmentRule FromWindowsAdjustmentRule(TimeZoneInfo zone, TimeZoneInfo.AdjustmentRule rule) { // With .NET 4.6, adjustment rules can have their own standard offsets, allowing // a much more reasonable set of time zone data. Unfortunately, this isn't directly // exposed, but we can detect it by just finding the UTC offset for an arbitrary // time within the rule - the start, in this case - and then take account of the // possibility of that being in daylight saving time. Fortunately, we only need // to do this during the setup. var ruleStandardOffset = zone.GetUtcOffset(rule.DateStart); if (zone.IsDaylightSavingTime(rule.DateStart)) { ruleStandardOffset -= rule.DaylightDelta; } var standardOffset = ruleStandardOffset.ToOffset(); // Although the rule may have its own standard offset, the start/end is still determined // using the zone's standard offset. var zoneStandardOffset = zone.BaseUtcOffset.ToOffset(); // Note: this extends back from DateTime.MinValue to start of time, even though the BCL can represent // as far back as 1AD. This is in the *spirit* of a rule which goes back that far. var start = rule.DateStart == DateTime.MinValue ? Instant.BeforeMinValue : rule.DateStart.ToLocalDateTime().WithOffset(zoneStandardOffset).ToInstant(); // The end instant (exclusive) is the end of the given date, so we need to add a day. var end = rule.DateEnd == MaxDate ? Instant.AfterMaxValue : rule.DateEnd.ToLocalDateTime().PlusDays(1).WithOffset(zoneStandardOffset).ToInstant(); var savings = rule.DaylightDelta.ToOffset(); PartialZoneIntervalMap partialMap; // Some rules have DST start/end of "January 1st", to indicate that they're just in standard time. This is important // for rules which have a standard offset which is different to the standard offset of the zone itself. if (IsStandardOffsetOnlyRule(rule)) { partialMap = PartialZoneIntervalMap.ForZoneInterval(zone.StandardName, start, end, standardOffset, Offset.Zero); } else { var daylightRecurrence = new ZoneRecurrence(zone.DaylightName, savings, ConvertTransition(rule.DaylightTransitionStart), int.MinValue, int.MaxValue); var standardRecurrence = new ZoneRecurrence(zone.StandardName, Offset.Zero, ConvertTransition(rule.DaylightTransitionEnd), int.MinValue, int.MaxValue); IZoneIntervalMap recurringMap = new StandardDaylightAlternatingMap(standardOffset, standardRecurrence, daylightRecurrence); // Fake 1 hour savings if the adjustment rule claims to be 0 savings. See DaylightFakingZoneIntervalMap documentation below for more details. if (savings == Offset.Zero) { recurringMap = new DaylightFakingZoneIntervalMap(recurringMap, zone.DaylightName); } partialMap = new PartialZoneIntervalMap(start, end, recurringMap); } return(new BclAdjustmentRule(start, end, standardOffset, savings, partialMap)); }
/// <summary> /// Reads a time zone from the specified reader. /// </summary> /// <param name="reader">The reader.</param> /// <param name="id">The id.</param> /// <returns>The time zone.</returns> internal static DateTimeZone Read([Trusted][NotNull] IDateTimeZoneReader reader, [Trusted][NotNull] string id) { Preconditions.DebugCheckNotNull(reader, nameof(reader)); Preconditions.DebugCheckNotNull(id, nameof(id)); int size = reader.ReadCount(); var periods = new ZoneInterval[size]; // It's not entirely clear why we don't just assume that the first zone interval always starts at Instant.BeforeMinValue // (given that we check that later) but we don't... and changing that now could cause compatibility issues. var start = reader.ReadZoneIntervalTransition(null); for (int i = 0; i < size; i++) { var name = reader.ReadString(); var offset = reader.ReadOffset(); var savings = reader.ReadOffset(); var nextStart = reader.ReadZoneIntervalTransition(start); periods[i] = new ZoneInterval(name, start, nextStart, offset, savings); start = nextStart; } var tailZone = reader.ReadByte() == 1 ? StandardDaylightAlternatingMap.Read(reader) : null; return(new PrecalculatedDateTimeZone(id, periods, tailZone)); }