differenceBetweenDateTime static method

Period differenceBetweenDateTime(
  1. LocalDateTime start,
  2. LocalDateTime end, [
  3. PeriodUnits units = PeriodUnits.dateAndTime
])

Returns the exact difference between two date/times or returns the period between a start and an end date/time, using only the given units.

If end is before 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 start will give a value between start and end. In other words, any rounding is 'towards start'; this is true whether the resulting period is negative or positive.

  • start: Start date/time
  • end: End date/time
  • units: Units to use for calculations

Returns: The period between the given date/times, using the given units.

Implementation

static Period differenceBetweenDateTime(LocalDateTime start, LocalDateTime end, [PeriodUnits units = PeriodUnits.dateAndTime]) {
  Preconditions.checkArgument(units != PeriodUnits.none, 'units', "Units must not be empty");
  Preconditions.checkArgument((units.value & ~PeriodUnits.allUnits.value) == 0, 'units', "Units contains an unknown value: $units");
  CalendarSystem calendar = start.calendar;
  Preconditions.checkArgument(calendar == end.calendar, '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.calendarDate;
  if (start < end) {
    if (start.clockTime > end.clockTime) {
      endDate = endDate.addDays(-1);
    }
  }
  else if (start > end && start.clockTime < end.clockTime) {
    endDate = endDate.addDays(1);
  }

  // Optimization for single field
  // todo: optimize me?
  Map _betweenFunctionMap = {
    PeriodUnits.years:  () => Period(years: DatePeriodFields.yearsField.unitsBetween(start.calendarDate, endDate)),
    PeriodUnits.months: () => Period(months: DatePeriodFields.monthsField.unitsBetween(start.calendarDate, endDate)),
    PeriodUnits.weeks: () => Period(weeks: DatePeriodFields.weeksField.unitsBetween(start.calendarDate, endDate)),
    PeriodUnits.days: () => Period(days: _daysBetween(start.calendarDate, endDate)),
    PeriodUnits.hours: () => Period(hours: TimePeriodField.hours.unitsBetween(start, end)),
    PeriodUnits.minutes: () => Period(minutes: TimePeriodField.minutes.unitsBetween(start, end)),
    PeriodUnits.seconds: () => Period(seconds: TimePeriodField.seconds.unitsBetween(start, end)),
    PeriodUnits.milliseconds: () => Period(milliseconds: TimePeriodField.milliseconds.unitsBetween(start, end)),
    PeriodUnits.microseconds: () => Period(microseconds: TimePeriodField.microseconds.unitsBetween(start, end)),
    PeriodUnits.nanoseconds: () => Period(nanoseconds: TimePeriodField.nanoseconds.unitsBetween(start, end))
  };

  if (_betweenFunctionMap.containsKey(units)) return _betweenFunctionMap[units]();

//    switch (units) {
//      case PeriodUnits.years:
//        return new Period.fromYears(DatePeriodFields.YearsField.UnitsBetween(start.Date, endDate));
//      case PeriodUnits.months:
//        return new Period.fromMonths(DatePeriodFields.MonthsField.UnitsBetween(start.Date, endDate));
//      case PeriodUnits.weeks:
//        return new Period.fromWeeks(DatePeriodFields.WeeksField.UnitsBetween(start.Date, endDate));
//      case PeriodUnits.days:
//        return new Period.fromDays(DaysBetween(start.Date, endDate));
//      case PeriodUnits.hours:
//        return new Period.fromHours(TimePeriodField.Hours.UnitsBetween(start, end));
//      case PeriodUnits.minutes:
//        return new Period.fromMinutes(TimePeriodField.Minutes.UnitsBetween(start, end));
//      case PeriodUnits.seconds:
//        return new Period.fromSeconds(TimePeriodField.Seconds.UnitsBetween(start, end));
//      case PeriodUnits.milliseconds:
//        return new Period.fromMilliseconds(TimePeriodField.Milliseconds.UnitsBetween(start, end));
//      case PeriodUnits.ticks:
//        return new Period.fromTicks(TimePeriodField.Ticks.UnitsBetween(start, end));
//      case PeriodUnits.nanoseconds:
//        return new Period.fromNanoseconds(TimePeriodField.Nanoseconds.UnitsBetween(start, end));
//    }

  // Multiple fields
  LocalDateTime remaining = start;
  int years = 0,
      months = 0,
      weeks = 0,
      days = 0;
  if ((units.value & PeriodUnits.allDateUnits.value) != 0) {
    // LocalDate remainingDate = DateComponentsBetween(
    //  start.Date, endDate, units, out years, out months, out weeks, out days);
    var result = _dateComponentsBetween(start.calendarDate, endDate, units);
    years = result.years;
    months = result.months;
    weeks = result.weeks;
    days = result.days;

    var remainingDate = result.date;
    remaining = LocalDateTime.localDateAtTime(remainingDate, start.clockTime);
  }
  if ((units.value & PeriodUnits.allTimeUnits.value) == 0) {
    return Period(years: years, months: months, weeks: weeks, days: 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 int arithmetic (as we do for Between(LocalTime, LocalTime)).
  // Otherwise (rare case), use duration arithmetic.
  int hours, minutes, seconds, milliseconds, microseconds, nanoseconds;
  var span = ILocalDateTime.toLocalInstant(end).timeSinceLocalEpoch
      - ILocalDateTime.toLocalInstant(remaining).timeSinceLocalEpoch;
  if (span.canNanosecondsBeInteger) {
    var result = _timeComponentsBetween(span.inNanoseconds, units);
    hours = result.hours;
    minutes = result.minutes;
    seconds = result.seconds;
    milliseconds = result.milliseconds;
    microseconds = result.microseconds;
    nanoseconds = result.nanoseconds;

    // TimeComponentsBetween(duration.ToInt64Nanoseconds(), units, out hours, out minutes, out seconds, out milliseconds, out ticks, out nanoseconds);
  }
  else {
    int unitsBetween(PeriodUnits mask, TimePeriodField timeField) {
      if ((mask.value & units.value) == 0) {
        return 0;
      }
      int value = timeField.getUnitsInDuration(span);
      span -= timeField.toSpan(value);
      return value;
    }

    hours = unitsBetween(PeriodUnits.hours, TimePeriodField.hours);
    minutes = unitsBetween(PeriodUnits.minutes, TimePeriodField.minutes);
    seconds = unitsBetween(PeriodUnits.seconds, TimePeriodField.seconds);
    milliseconds = unitsBetween(PeriodUnits.milliseconds, TimePeriodField.milliseconds);
    microseconds = unitsBetween(PeriodUnits.microseconds, TimePeriodField.microseconds);
    nanoseconds = unitsBetween(PeriodUnits.microseconds, TimePeriodField.nanoseconds);
  }

  return Period(years: years,
      months: months,
      weeks: weeks,
      days: days,
      hours: hours,
      minutes: minutes,
      seconds: seconds,
      milliseconds: milliseconds,
      microseconds: microseconds,
      nanoseconds: nanoseconds);
}