DateTime _correctForErrors()

in lib/src/intl/date_builder.dart [234:325]


  DateTime _correctForErrors(DateTime result, int retries) {
    // There are 3 kinds of errors that we know of
    //
    // 1 - Issue 15560, sometimes we get UTC even when we asked for local, or
    // they get constructed as if in UTC and then have the offset subtracted.
    // Retry, possibly several times, until we get something that looks valid,
    // or we give up.
    //
    // 1a) - It appears that sometimes we get incorrect timezone offsets that
    // are not directly related to UTC. Also check for those and retry or
    // compensate.
    //
    // 2 - Timezone transitions. If we ask for the time during a timezone
    // transition then it will offset it by that transition. This is
    // particularly a problem if the timezone transition happens at midnight,
    // and we're looking for a date with no time component. This happens in
    // Brazil, and we can end up with 11:00pm the previous day. Add time to
    // compensate.
    //
    // 3 - Invalid input which the constructor nevertheless accepts. Just return
    // what it created, and verify will catch it if we're in strict mode.

    // If we've exhausted our retries, just return the input - it's not just a
    // flaky result.
    if (retries <= 0) {
      return result;
    }

    var leapYear = date_computation.isLeapYear(result);
    var resultDayOfYear =
        date_computation.dayOfYear(result.month, result.day, leapYear);

    // Check for the UTC failure. Are we expecting to produce a local time, but
    // the result is UTC. However, the local time might happen to be the same as
    // UTC. To be thorough, check if either the hour/day don't agree with what
    // we expect, or is a new DateTime in a non-UTC timezone.
    if (!utc &&
        result.isUtc &&
        (result.hour != hour24 ||
            result.day != resultDayOfYear ||
            !DateTime.now().isUtc)) {
      // This may be a UTC failure. Retry and if the result doesn't look
      // like it's in the UTC time zone, use that instead.
      _retried++;
      return asDate(retries: retries - 1);
    }

    if (dateOnly && result.hour != 0) {
      // This could be a flake, try again.
      var tryAgain = asDate(retries: retries - 1);
      if (tryAgain != result) {
        // Trying again gave a different answer, so presumably it worked.
        return tryAgain;
      }

      // Trying again didn't work, try to force the offset.
      var expectedDayOfYear = dayOfYear == 0
          ? date_computation.dayOfYear(month, day, leapYear)
          : dayOfYear;

      // If we're _dateOnly, then hours should be zero, but might have been
      // offset to e.g. 11:00pm the previous day. Add that time back in. This
      // might be because of an erratic error, but it might also be because of a
      // time zone (Brazil) where there is no midnight at a daylight savings
      // time transition. In that case we will retry, but eventually give up and
      // return 1:00am on the correct date.
      var daysPrevious = expectedDayOfYear - resultDayOfYear;
      // For example, if it's the day before at 11:00pm, we offset by (24 - 23),
      // so +1. If it's the same day at 1:00am, we offset by (0 - 1), so -1.
      var offset = (daysPrevious * 24) - result.hour;
      var adjusted = result.add(Duration(hours: offset));
      // Check if the adjustment worked. This can fail on a time zone transition
      // where midnight doesn't exist.
      if (adjusted.hour == 0) {
        return adjusted;
      }
      // Adjusting did not work. Just check if the adjusted date is right. And
      // if it's not, just give up and return [result]. The scenario where this
      // might correctly happen is if we're in a Brazil time zone, jump forward
      // to 1:00 am because of a DST transition, and trying to go backwards 1
      // hour takes us back to 11:00pm the day before. In that case the 1:00am
      // answer on the correct date is preferable.
      var adjustedDayOfYear =
          date_computation.dayOfYear(adjusted.month, adjusted.day, leapYear);
      if (adjustedDayOfYear != expectedDayOfYear) {
        return result;
      }
      return adjusted;
    }
    // None of our corrections applied, just return the uncorrected date.
    return result;
  }