/// <summary> /// Adds the contents of this period to the given date and time, with the given scale (either 1 or -1, usually). /// </summary> internal LocalDateTime AddTo(LocalDate date, LocalTime time, int scalar) { date = AddTo(date, scalar); int extraDays = 0; time = TimePeriodField.Hours.Add(time, Hours * scalar, ref extraDays); time = TimePeriodField.Minutes.Add(time, Minutes * scalar, ref extraDays); time = TimePeriodField.Seconds.Add(time, Seconds * scalar, ref extraDays); time = TimePeriodField.Milliseconds.Add(time, Milliseconds * scalar, ref extraDays); time = TimePeriodField.Ticks.Add(time, Ticks * scalar, ref extraDays); time = TimePeriodField.Nanoseconds.Add(time, Nanoseconds * scalar, ref extraDays); // TODO(optimization): Investigate the performance impact of us calling PlusDays twice. // Could optimize by including that in a single call... return new LocalDateTime(date.PlusDays(extraDays), time); }
/// <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; } }
/// <summary> /// Add days.<br /> /// 添加天数。 /// </summary> /// <param name="ld"></param> /// <param name="days"></param> /// <returns></returns> public static LocalDate AddDays(this LocalDate ld, int days) => ld.PlusDays(days);