Пример #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ZonedDateTime"/> struct in the specified time zone
        /// from a given local time and offset. The offset is validated to be correct as part of initialization.
        /// In most cases a local time can only map to a single instant anyway, but the offset is included here for cases
        /// where the local time is ambiguous, usually due to daylight saving transitions.
        /// </summary>
        /// <param name="localDateTime">The local date and time.</param>
        /// <param name="zone">The time zone.</param>
        /// <param name="offset">The offset between UTC and local time at the desired instant.</param>
        /// <exception cref="ArgumentException"><paramref name="offset"/> is not a valid offset at the given
        /// local date and time.</exception>
        public ZonedDateTime(LocalDateTime localDateTime, DateTimeZone zone, Offset offset)
        {
            this.zone = Preconditions.CheckNotNull(zone, nameof(zone));
            Instant candidateInstant = localDateTime.ToLocalInstant().Minus(offset);
            Offset  correctOffset    = zone.GetUtcOffset(candidateInstant);

            // Not using Preconditions, to avoid building the string unnecessarily.
            if (correctOffset != offset)
            {
                throw new ArgumentException($"Offset {offset} is invalid for local date and time {localDateTime} in time zone {zone.Id}", nameof(offset));
            }
            offsetDateTime = new OffsetDateTime(localDateTime, offset);
        }
Пример #2
0
        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)));
            }
        }
Пример #3
0
        /// <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));
            }
        }
Пример #4
0
        /// <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, nameof(units), "Units must not be empty");
            Preconditions.CheckArgument((units & ~PeriodUnits.AllUnits) == 0, nameof(units), "Units contains an unknown value: {0}", units);
            CalendarSystem calendar = start.Calendar;
            Preconditions.CheckArgument(calendar.Equals(end.Calendar), nameof(end), "start and end must use the same calendar system");

            if (start == end)
            {
                return Zero;
            }

            // Adjust for situations like "days between 5th January 10am and 7th Janary 5am" which should be one
            // day, because if we actually reach 7th January with date fields, we've overshot.
            // The date adjustment will always be valid, because it's just moving it towards start.
            // We need this for all date-based period fields. We could potentially optimize by not doing this
            // in cases where we've only got time fields...
            LocalDate endDate = end.Date;
            if (start < end)
            {
                if (start.TimeOfDay > end.TimeOfDay)
                {
                    endDate = endDate.PlusDays(-1);
                }
            }
            else if (start > end && start.TimeOfDay < end.TimeOfDay)
            {
                endDate = endDate.PlusDays(1);
            }

            // Optimization for single field
            switch (units)
            {
                case PeriodUnits.Years: return FromYears(DatePeriodFields.YearsField.UnitsBetween(start.Date, endDate));
                case PeriodUnits.Months: return FromMonths(DatePeriodFields.MonthsField.UnitsBetween(start.Date, endDate));
                case PeriodUnits.Weeks: return FromWeeks(DatePeriodFields.WeeksField.UnitsBetween(start.Date, endDate));
                case PeriodUnits.Days: return FromDays(DaysBetween(start.Date, endDate));
                case PeriodUnits.Hours: return FromHours(TimePeriodField.Hours.UnitsBetween(start, end));
                case PeriodUnits.Minutes: return FromMinutes(TimePeriodField.Minutes.UnitsBetween(start, end));
                case PeriodUnits.Seconds: return FromSeconds(TimePeriodField.Seconds.UnitsBetween(start, end));
                case PeriodUnits.Milliseconds: return FromMilliseconds(TimePeriodField.Milliseconds.UnitsBetween(start, end));
                case PeriodUnits.Ticks: return FromTicks(TimePeriodField.Ticks.UnitsBetween(start, end));
                case PeriodUnits.Nanoseconds: return FromNanoseconds(TimePeriodField.Nanoseconds.UnitsBetween(start, end));
            }

            // Multiple fields
            LocalDateTime remaining = start;
            int years = 0, months = 0, weeks = 0, days = 0;
            if ((units & PeriodUnits.AllDateUnits) != 0)
            {
                LocalDate remainingDate = DateComponentsBetween(
                    start.Date, endDate, units, out years, out months, out weeks, out days);
                remaining = new LocalDateTime(remainingDate, start.TimeOfDay);
            }
            if ((units & PeriodUnits.AllTimeUnits) == 0)
            {
                return new Period(years, months, weeks, days);
            }

            // The remainder of the computation is with fixed-length units, so we can do it all with
            // Duration instead of Local* values. We don't know for sure that this is small though - we *could*
            // be trying to find the difference between 9998 BC and 9999 CE in nanoseconds...
            // Where we can optimize, do everything with long arithmetic (as we do for Between(LocalTime, LocalTime)).
            // Otherwise (rare case), use duration arithmetic.
            long hours, minutes, seconds, milliseconds, ticks, nanoseconds;
            var duration = end.ToLocalInstant().TimeSinceLocalEpoch - remaining.ToLocalInstant().TimeSinceLocalEpoch;
            if (duration.IsInt64Representable)
            {
                TimeComponentsBetween(duration.ToInt64Nanoseconds(), units, out hours, out minutes, out seconds, out milliseconds, out ticks, out nanoseconds);
            }
            else
            {
                hours = UnitsBetween(PeriodUnits.Hours, TimePeriodField.Hours);
                minutes = UnitsBetween(PeriodUnits.Minutes, TimePeriodField.Minutes);
                seconds = UnitsBetween(PeriodUnits.Seconds, TimePeriodField.Seconds);
                milliseconds = UnitsBetween(PeriodUnits.Milliseconds, TimePeriodField.Milliseconds);
                ticks = UnitsBetween(PeriodUnits.Ticks, TimePeriodField.Ticks);
                nanoseconds = UnitsBetween(PeriodUnits.Ticks, TimePeriodField.Nanoseconds);
            }
            return new Period(years, months, weeks, days, hours, minutes, seconds, milliseconds, ticks, nanoseconds);

            long UnitsBetween(PeriodUnits mask, TimePeriodField timeField)
            {
                if ((mask & units) == 0)
                {
                    return 0;
                }
                long value = timeField.GetUnitsInDuration(duration);
                duration -= timeField.ToDuration(value);
                return value;
            }
        }