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))); } }
private ZoneInterval GetIntervalAfterGap(LocalInstant localInstant) { Instant guess = localInstant.MinusZeroOffset(); ZoneInterval guessInterval = GetZoneInterval(guess); // If the local interval occurs before the zone interval we're looking at starts, // it's the one we're looking for. Otherwise, we need to find the next interval. if (localInstant.Minus(guessInterval.WallOffset) < guessInterval.RawStart) { return(guessInterval); } else { // Will definitely be valid - there can't be a gap after an infinite interval. return(GetZoneInterval(guessInterval.End)); } }
private ZoneInterval GetIntervalBeforeGap(LocalInstant localInstant) { Instant guess = localInstant.MinusZeroOffset(); ZoneInterval guessInterval = GetZoneInterval(guess); // If the local interval occurs before the zone interval we're looking at starts, // we need to find the earlier one; otherwise this interval must come after the gap, and // it's therefore the one we want. if (localInstant.Minus(guessInterval.WallOffset) < guessInterval.RawStart) { return(GetZoneInterval(guessInterval.Start - Duration.Epsilon)); } else { return(guessInterval); } }
/// <summary> /// Returns the period between a start and an end date/time, using only the given units. /// </summary> /// <remarks> /// If <paramref name="end"/> is before <paramref name="start" />, each property in the returned period /// will be negative. If the given set of units cannot exactly reach the end point (e.g. finding /// the difference between 1am and 3:15am in hours) the result will be such that adding it to <paramref name="start"/> /// will give a value between <paramref name="start"/> and <paramref name="end"/>. In other words, /// any rounding is "towards start"; this is true whether the resulting period is negative or positive. /// </remarks> /// <param name="start">Start date/time</param> /// <param name="end">End date/time</param> /// <param name="units">Units to use for calculations</param> /// <exception cref="ArgumentException"><paramref name="units"/> is empty or contained unknown values.</exception> /// <exception cref="ArgumentException"><paramref name="start"/> and <paramref name="end"/> use different calendars.</exception> /// <returns>The period between the given date/times, using the given units.</returns> public static Period Between(LocalDateTime start, LocalDateTime end, PeriodUnits units) { Preconditions.CheckArgument(units != 0, "units", "Units must not be empty"); Preconditions.CheckArgument((units & ~PeriodUnits.AllUnits) == 0, "units", "Units contains an unknown value: {0}", units); CalendarSystem calendar = start.Calendar; Preconditions.CheckArgument(calendar.Equals(end.Calendar), "end", "start and end must use the same calendar system"); LocalInstant startLocalInstant = start.LocalInstant; LocalInstant endLocalInstant = end.LocalInstant; if (startLocalInstant == endLocalInstant) { return(Zero); } PeriodFieldSet fields = calendar.PeriodFields; // Optimization for single field var singleField = GetSingleField(fields, units); if (singleField != null) { long value = singleField.Subtract(end.LocalInstant, start.LocalInstant); return(new Period(units, value)); } // Multiple fields long[] values = new long[ValuesArraySize]; LocalInstant remaining = startLocalInstant; int numericFields = (int)units; for (int i = 0; i < ValuesArraySize; i++) { if ((numericFields & (1 << i)) != 0) { var field = GetFieldForIndex(fields, i); values[i] = field.Subtract(endLocalInstant, remaining); remaining = field.Add(remaining, values[i]); } } return(new Period(values)); }
/// <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); }
internal int GetMinuteOfDay(LocalInstant localInstant) { return(TimeOfDayCalculator.GetMinuteOfDay(localInstant)); }
/// <summary> /// Constructs a <see cref="DateTime"/> from this <see cref="ZonedDateTime"/> which has a /// <see cref="DateTime.Kind"/> of <see cref="DateTimeKind.Unspecified"/> and represents the same local time as /// this value rather than the same instant in time. /// </summary> /// <remarks> /// <see cref="DateTimeKind.Unspecified"/> is slightly odd - it can be treated as UTC if you use <see cref="DateTime.ToLocalTime"/> /// or as system local time if you use <see cref="DateTime.ToUniversalTime"/>, but it's the only kind which allows /// you to construct a <see cref="DateTimeOffset"/> with an arbitrary offset. /// </remarks> /// <returns>A <see cref="DateTime"/> representation of this value with an "unspecified" kind, with the same /// local date and time as this value.</returns> public DateTime ToDateTimeUnspecified() { return(LocalInstant.ToDateTimeUnspecified()); }
/// <summary> /// Returns the local date corresponding to the given "week year", "week of week year", and "day of week" /// in the ISO calendar system. /// </summary> /// <param name="weekYear">ISO-8601 week year of value to return</param> /// <param name="weekOfWeekYear">ISO-8601 week of week year of value to return</param> /// <param name="dayOfWeek">ISO-8601 day of week to return</param> /// <returns>The date corresponding to the given week year / week of week year / day of week.</returns> public static LocalDate FromWeekYearWeekAndDay(int weekYear, int weekOfWeekYear, IsoDayOfWeek dayOfWeek) { LocalInstant localInstant = CalendarSystem.Iso.GetLocalInstantFromWeekYearWeekAndDayOfWeek(weekYear, weekOfWeekYear, dayOfWeek); return(new LocalDate(new LocalDateTime(localInstant))); }
internal int GetYearOfCentury(LocalInstant localInstant) { return(yearMonthDayCalculator.GetYearOfCentury(localInstant)); }
internal int GetEra(LocalInstant localInstant) { return(yearMonthDayCalculator.GetEra(localInstant)); }
internal int GetWeekYear(LocalInstant localInstant) { return(weekYearCalculator.GetWeekYear(localInstant)); }
internal int GetMonthOfYear(LocalInstant localInstant) { return(yearMonthDayCalculator.GetMonthOfYear(localInstant)); }
internal int GetDayOfWeek(LocalInstant localInstant) { return(WeekYearCalculator.GetDayOfWeek(localInstant)); }
/// <summary> /// Constructor only called from other parts of Noda Time - trusted to be within January 1st 1970 UTC. /// </summary> internal LocalTime(LocalInstant localInstant) { this.localInstant = localInstant; }
internal long GetTickOfDay(LocalInstant localInstant) { return(TimeOfDayCalculator.GetTickOfDay(localInstant)); }
internal int GetSecondOfMinute(LocalInstant localInstant) { return(TimeOfDayCalculator.GetSecondOfMinute(localInstant)); }
internal int GetMillisecondOfSecond(LocalInstant localInstant) { return(TimeOfDayCalculator.GetMillisecondOfSecond(localInstant)); }
internal int GetClockHourOfHalfDay(LocalInstant localInstant) { return(TimeOfDayCalculator.GetClockHourOfHalfDay(localInstant)); }
/// <summary> /// Initializes a new instance of the <see cref="LocalDateTime"/> struct using the ISO /// calendar system. /// </summary> /// <param name="localInstant">The local instant.</param> /// <returns>The resulting date/time.</returns> internal LocalDateTime([Trusted] LocalInstant localInstant) { date = new LocalDate(localInstant.DaysSinceEpoch); time = new LocalTime(localInstant.NanosecondOfDay); }