UTCTimestampsForLocalTime _systemTimezoneLocalPTimeToUTCTimestamps()

in bistro/cron/utils/date_time.cpp [73:171]


UTCTimestampsForLocalTime _systemTimezoneLocalPTimeToUTCTimestamps(
  ptime local_pt
) {
  UTCTimestampsForLocalTime res;
  struct tm tm = to_tm(local_pt);

  auto save_timestamp_if_valid = [tm, &local_pt](
      time_t t, int is_dst, time_t *out) {
    // Convert the timestamp to a local time to see if the guess was right.
    struct tm new_tm;
    auto out_tm = localtime_r(&t, &new_tm);
    if (out_tm == nullptr) {  // Not sure if such errors can be handled.
      throw logic_error(folly::format(
        "{}: localtime_r error {}", to_simple_string(local_pt), errno
      ).str());
    }

    // Does the original tm argree with the tm generated from the mktime()
    // UTC timestamp?  (We'll check tm_isdst separately.)
    //
    // This test never passes when we have a local time label that is
    // skipped when a DST change moves the clock forward.
    //
    // A valid local time label always has one or two valid DST values.
    // When the timezone has not DST, that value is "false".
    //
    // This test always passes when:
    //  - The DST value is ambiguous (due to the local clock moving back).
    //  - We guessed the uniquely valid DST value.
    //
    // The test may or may not always pass (implementation-dependent) when
    // we did not guess a valid DST value.
    //  (a) If it does not pass, we are good, because we also try the other
    //      DST value, which will make the test pass, and then res will have
    //      a unique timestamp.
    //  (b) If it does pass, we're in more trouble, because it means that
    //      the implementation ignored our is_dst value. Then, the timestamp
    //      t is the same as for the other is_dst value.  But, we don't want
    //      res to be labeled ambiguous, and we don't want to randomly pick
    //      a DST value to set to kNotATime, because clients may want to
    //      know the real DST value.  The solution is the extra test below.
    if (
      tm.tm_sec == new_tm.tm_sec && tm.tm_min == new_tm.tm_min &&
      tm.tm_hour == new_tm.tm_hour && tm.tm_mday == new_tm.tm_mday &&
      tm.tm_mon == new_tm.tm_mon && tm.tm_year == new_tm.tm_year &&
      // To fix problem (b) above, we must assume that localtime_r returns
      // the correct tm_isdst (if not, it's a system bug anyhow).  Then, we
      // can just check our DST guess against the truth.  If our guess was
      // invalid, we shouldn't store the result, avoiding (b).
      !(  // tm_isdst can also be negative but we'll check that later
        (new_tm.tm_isdst == 0 && is_dst) || (new_tm.tm_isdst > 0 && !is_dst)
      )
    ) {
      *out = t;
    }
    return new_tm.tm_isdst < 0;  // Used for a sanity-check below.
  };

  int num_negative_isdst = 0;
  int num_missing_timestamps = 0;
  for (int is_dst = 0; is_dst <= 1; ++is_dst) {
    // Try to make a UTC timestamp based on our DST guess and local time.
    struct tm tmp_tm = tm;  // Make a copy since mktime changes the tm
    tmp_tm.tm_isdst = is_dst;
    time_t t = mktime(&tmp_tm);
    if (t == -1) {  // Not sure of the error cause or how to handle it.
      ++num_missing_timestamps;
      continue;
    }
    num_negative_isdst += save_timestamp_if_valid(
        t, is_dst, is_dst ? &res.dst_time : &res.non_dst_time);
  }
  // The only legitimate way for localtime_r() to give back a negative
  // tm_isdst is if the input local time label is ambiguous due to DST.
  //
  // FWIW, we could also error on `isAmbiguous && !num_negative_isdst`,
  // but it shouldn't affect our correctness.
  if (num_negative_isdst && !res.isAmbiguous()) {
    throw logic_error(folly::format(
      "{}: negative tm_isdst but time label is unambiguous",
      to_simple_string(local_pt)
    ).str());
  }
  // On older glibc versions, `mktime` would succeed even when `is_dst` was
  // invalid.
  if (num_missing_timestamps == 0) {
    if (num_negative_isdst == 1) {  // Can't be ambiguous half the time
      throw logic_error(folly::format(
        "{}: one tm_isdst negative but not both", to_simple_string(local_pt)
      ).str());
    }
  } else if (num_missing_timestamps == 2) {
    throw logic_error(folly::format(
      "{}: mktime failed for both is_dst choices", to_simple_string(local_pt)
    ).str());
  }

  return res;
}