public void Contains_Instant_Normal() { Assert.IsTrue(SampleInterval.Contains(SampleStart)); Assert.IsFalse(SampleInterval.Contains(SampleEnd)); Assert.IsFalse(SampleInterval.Contains(Instant.MinValue)); Assert.IsFalse(SampleInterval.Contains(Instant.MaxValue)); }
public void Contains_Instant_WholeOfTime_ViaSpecialInstants() { ZoneInterval interval = new ZoneInterval("All Time", Instant.BeforeMinValue, Instant.AfterMaxValue, Offset.FromHours(9), Offset.FromHours(1)); Assert.IsTrue(interval.Contains(SampleStart)); Assert.IsTrue(interval.Contains(Instant.MinValue)); Assert.IsTrue(interval.Contains(Instant.MaxValue)); }
public void Contains_LocalInstant_WholeOfTime() { ZoneInterval interval = new ZoneInterval("All Time", Instant.BeforeMinValue, Instant.AfterMaxValue, Offset.FromHours(9), Offset.FromHours(1)); Assert.IsTrue(interval.Contains(SampleStart.Plus(Offset.Zero))); Assert.IsTrue(interval.Contains(Instant.MinValue.Plus(Offset.Zero))); Assert.IsTrue(interval.Contains(Instant.MaxValue.Plus(Offset.Zero))); }
public void Contains_Instant_WholeOfTime_ViaNullity() { ZoneInterval interval = new ZoneInterval("All Time", null, null, Offset.FromHours(9), Offset.FromHours(1)); Assert.IsTrue(interval.Contains(SampleStart)); Assert.IsTrue(interval.Contains(Instant.MinValue)); Assert.IsTrue(interval.Contains(Instant.MaxValue)); }
public void Contains_LocalInstant_WholeOfTime() { ZoneInterval interval = new ZoneInterval("All Time", Instant.MinValue, Instant.MaxValue, Offset.FromHours(9), Offset.FromHours(1)); Assert.IsTrue(interval.Contains(SampleStart.Plus(Offset.Zero))); Assert.IsTrue(interval.Contains(LocalInstant.MinValue)); Assert.IsTrue(interval.Contains(LocalInstant.MaxValue)); }
public void Contains_OutsideLocalInstantange() { ZoneInterval veryEarly = new ZoneInterval("Very early", Instant.BeforeMinValue, Instant.MinValue + Duration.FromHours(8), Offset.FromHours(-9), Offset.Zero); ZoneInterval veryLate = new ZoneInterval("Very late", Instant.MaxValue - Duration.FromHours(8), Instant.AfterMaxValue, Offset.FromHours(9), Offset.Zero); // The instants are contained... Assert.IsTrue(veryEarly.Contains(Instant.MinValue + Duration.FromHours(4))); Assert.IsTrue(veryLate.Contains(Instant.MaxValue - Duration.FromHours(4))); // But there are no valid local instants Assert.IsFalse(veryEarly.Contains(Instant.MinValue.Plus(Offset.Zero))); Assert.IsFalse(veryLate.Contains(Instant.MaxValue.Plus(Offset.Zero))); }
internal ZonedDateTime ResolveLocal(LocalDateTime localDateTime, [Trusted][NotNull] AmbiguousTimeResolver ambiguousResolver, [Trusted][NotNull] SkippedTimeResolver skippedResolver) { Preconditions.DebugCheckNotNull(ambiguousResolver, nameof(ambiguousResolver)); Preconditions.DebugCheckNotNull(skippedResolver, nameof(skippedResolver)); LocalInstant localInstant = localDateTime.ToLocalInstant(); Instant firstGuess = localInstant.MinusZeroOffset(); ZoneInterval interval = GetZoneInterval(firstGuess); // Most of the time we'll go into here... the local instant and the instant // are close enough that we've found the right instant. if (interval.Contains(localInstant)) { ZonedDateTime guessZoned = new ZonedDateTime(localDateTime.WithOffset(interval.WallOffset), this); ZoneInterval earlier = GetEarlierMatchingInterval(interval, localInstant); if (earlier != null) { ZonedDateTime earlierZoned = new ZonedDateTime(localDateTime.WithOffset(earlier.WallOffset), this); return(ambiguousResolver(earlierZoned, guessZoned)); } ZoneInterval later = GetLaterMatchingInterval(interval, localInstant); if (later != null) { ZonedDateTime laterZoned = new ZonedDateTime(localDateTime.WithOffset(later.WallOffset), this); return(ambiguousResolver(guessZoned, laterZoned)); } return(guessZoned); } else { // Our first guess was wrong. Either we need to change interval by one (either direction) // or we're in a gap. ZoneInterval earlier = GetEarlierMatchingInterval(interval, localInstant); if (earlier != null) { return(new ZonedDateTime(localDateTime.WithOffset(earlier.WallOffset), this)); } ZoneInterval later = GetLaterMatchingInterval(interval, localInstant); if (later != null) { return(new ZonedDateTime(localDateTime.WithOffset(later.WallOffset), this)); } return(skippedResolver(localDateTime, this, GetIntervalBeforeGap(localInstant), GetIntervalAfterGap(localInstant))); } }
/// <summary> /// Returns the next interval after this one, if it contains the given local instant, or null otherwise. /// </summary> private ZoneInterval GetLaterMatchingInterval(ZoneInterval interval, LocalInstant localInstant) { // Micro-optimization to avoid fetching interval.End multiple times. Seems // to give a performance improvement on x86 at least... Instant intervalEnd = interval.End; if (intervalEnd == Instant.MaxValue) { return(null); } if (intervalEnd.Ticks + minOffsetTicks <= localInstant.Ticks) { ZoneInterval candidate = GetZoneInterval(intervalEnd); if (candidate.Contains(localInstant)) { return(candidate); } } return(null); }
/// <summary> /// Returns the interval before this one, if it contains the given local instant, or null otherwise. /// </summary> private ZoneInterval?GetEarlierMatchingInterval(ZoneInterval interval, LocalInstant localInstant) { // Micro-optimization to avoid fetching interval.Start multiple times. Seems // to give a performance improvement on x86 at least... // If the zone interval extends to the start of time, the next check will definitely evaluate to false. Instant intervalStart = interval.RawStart; // This allows for a maxOffset of up to +1 day, and the "truncate towards beginning of time" // nature of the Days property. if (localInstant.DaysSinceEpoch <= intervalStart.DaysSinceEpoch + 1) { // We *could* do a more accurate check here based on the actual maxOffset, but it's probably // not worth it. ZoneInterval candidate = GetZoneInterval(intervalStart - Duration.Epsilon); if (candidate.Contains(localInstant)) { return(candidate); } } return(null); }
/// <summary> /// Returns complete information about how the given <see cref="LocalDateTime" /> is mapped in this time zone. /// </summary> /// <remarks> /// <para> /// Mapping a local date/time to a time zone can give an unambiguous, ambiguous or impossible result, depending on /// time zone transitions. Use the return value of this method to handle these cases in an appropriate way for /// your use case. /// </para> /// <para> /// As an alternative, consider <see cref="ResolveLocal(LocalDateTime, ZoneLocalMappingResolver)"/>, which uses a caller-provided strategy to /// convert the <see cref="ZoneLocalMapping"/> returned here to a <see cref="ZonedDateTime"/>. /// </para> /// </remarks> /// <param name="localDateTime">The local date and time to map in this time zone.</param> /// <returns>A mapping of the given local date and time to zero, one or two zoned date/time values.</returns> public virtual ZoneLocalMapping MapLocal(LocalDateTime localDateTime) { LocalInstant localInstant = localDateTime.ToLocalInstant(); Instant firstGuess = localInstant.MinusZeroOffset(); ZoneInterval interval = GetZoneInterval(firstGuess); // Most of the time we'll go into here... the local instant and the instant // are close enough that we've found the right instant. if (interval.Contains(localInstant)) { ZoneInterval?earlier = GetEarlierMatchingInterval(interval, localInstant); if (earlier != null) { return(new ZoneLocalMapping(this, localDateTime, earlier, interval, 2)); } ZoneInterval?later = GetLaterMatchingInterval(interval, localInstant); if (later != null) { return(new ZoneLocalMapping(this, localDateTime, interval, later, 2)); } return(new ZoneLocalMapping(this, localDateTime, interval, interval, 1)); } else { // Our first guess was wrong. Either we need to change interval by one (either direction) // or we're in a gap. ZoneInterval?earlier = GetEarlierMatchingInterval(interval, localInstant); if (earlier != null) { return(new ZoneLocalMapping(this, localDateTime, earlier, earlier, 1)); } ZoneInterval?later = GetLaterMatchingInterval(interval, localInstant); if (later != null) { return(new ZoneLocalMapping(this, localDateTime, later, later, 1)); } return(new ZoneLocalMapping(this, localDateTime, GetIntervalBeforeGap(localInstant), GetIntervalAfterGap(localInstant), 0)); } }
/// <summary> /// Finds all zone intervals for the given local instant. Usually there's one (i.e. only a single /// instant is mapped to the given local instant within the time zone) but during DST transitions /// there can be either 0 (the given local instant doesn't exist, e.g. local time skipped from 1am to /// 2am, but you gave us 1.30am) or 2 (the given local instant is ambiguous, e.g. local time skipped /// from 2am to 1am, but you gave us 1.30am). /// </summary> /// <remarks> /// This method is implemented in terms of GetZoneInterval(Instant) within DateTimeZone, /// and should work for any zone. However, internal derived classes may override this method /// for optimization purposes, e.g. if the zone interval is always ambiguous with /// a fixed value. /// </remarks> /// <param name="localInstant">The local instant to find matching zone intervals for</param> /// <returns>The struct containing up to two ZoneInterval references.</returns> internal virtual ZoneIntervalPair GetZoneIntervalPair(LocalInstant localInstant) { Instant firstGuess = new Instant(localInstant.Ticks); ZoneInterval interval = GetZoneInterval(firstGuess); // Most of the time we'll go into here... the local instant and the instant // are close enough that we've found the right instant. if (interval.Contains(localInstant)) { ZoneInterval earlier = GetEarlierMatchingInterval(interval, localInstant); if (earlier != null) { return(ZoneIntervalPair.Ambiguous(earlier, interval)); } ZoneInterval later = GetLaterMatchingInterval(interval, localInstant); if (later != null) { return(ZoneIntervalPair.Ambiguous(interval, later)); } return(ZoneIntervalPair.Unambiguous(interval)); } else { // Our first guess was wrong. Either we need to change interval by one (either direction) // or we're in a gap. ZoneInterval earlier = GetEarlierMatchingInterval(interval, localInstant); if (earlier != null) { return(ZoneIntervalPair.Unambiguous(earlier)); } ZoneInterval later = GetLaterMatchingInterval(interval, localInstant); if (later != null) { return(ZoneIntervalPair.Unambiguous(later)); } return(ZoneIntervalPair.NoMatch); } }
/// <summary> /// Returns the next interval after this one, if it contains the given local instant, or null otherwise. /// </summary> private ZoneInterval?GetLaterMatchingInterval(ZoneInterval interval, LocalInstant localInstant) { // Micro-optimization to avoid fetching interval.End multiple times. Seems // to give a performance improvement on x86 at least... // If the zone interval extends to the end of time, the next check will // definitely evaluate to false. Instant intervalEnd = interval.RawEnd; // Crude but cheap first check to see whether there *might* be a later interval. // This allows for a minOffset of up to -1 day, and the "truncate towards beginning of time" // nature of the Days property. if (localInstant.DaysSinceEpoch >= intervalEnd.DaysSinceEpoch - 1) { // We *could* do a more accurate check here based on the actual maxOffset, but it's probably // not worth it. ZoneInterval candidate = GetZoneInterval(intervalEnd); if (candidate.Contains(localInstant)) { return(candidate); } } return(null); }
/// <summary> /// Returns the interval before this one, if it contains the given local instant, or null otherwise. /// </summary> private ZoneInterval GetEarlierMatchingInterval(ZoneInterval interval, LocalInstant localInstant) { // Micro-optimization to avoid fetching interval.Start multiple times. Seems // to give a performance improvement on x86 at least... Instant intervalStart = interval.Start; if (intervalStart == Instant.MinValue) { return(null); } // If the tick before this interval started *could* map to a later local instant, let's // get the interval and check whether it actually includes the one we want. Instant endOfPrevious = intervalStart; if (endOfPrevious.Ticks + maxOffsetTicks > localInstant.Ticks) { ZoneInterval candidate = GetZoneInterval(endOfPrevious - Duration.Epsilon); if (candidate.Contains(localInstant)) { return(candidate); } } return(null); }
/// <inheritdoc /> /// <remarks> /// This returns either the zone interval before or after the transition, based on the instant provided. /// </remarks> public override ZoneInterval GetZoneInterval(Instant instant) { return(earlyInterval.Contains(instant) ? earlyInterval : lateInterval); }