# # # patch "cmd_ws_commit.cc" # from [e49a513234dcd5dd6c137e1c83a28a4d751d9546] # to [3d1822f23fac03063c6eaaa4cf414c744ecc971c] # # patch "dates.cc" # from [c9c15e57ebd09330e4a6a5b2d8abe324b4f6b179] # to [f212e00a503f5256fdf5a321c824965a2e49a835] # # patch "unit-tests/dates.cc" # from [78271d63f77ab5d3e25ae95260b67f9a038e6b31] # to [4b2e6adef983df5508957aa5642f67e00c7e97ce] # ============================================================ --- cmd_ws_commit.cc e49a513234dcd5dd6c137e1c83a28a4d751d9546 +++ cmd_ws_commit.cc 3d1822f23fac03063c6eaaa4cf414c744ecc971c @@ -719,10 +719,15 @@ CMD(status, "status", "", CMD_REF(inform parsed = date_t::from_formatted_localtime(formatted, date_fmt); } catch (recoverable_failure const & e) - { } + { + L(FL("date check failed: %s") % e.what()); + } if (parsed != now) - W(F("date format '%s' cannot be used for commit") % date_fmt); + { + L(FL("date check failed: %s != %s") % now % parsed); + W(F("date format '%s' cannot be used for commit") % date_fmt); + } work.get_parent_rosters(db, old_rosters); work.get_current_roster_shape(db, nis, new_roster); @@ -1370,8 +1375,15 @@ CMD(commit, "commit", "ci", CMD_REF(work parsed = date_t::from_formatted_localtime(formatted, date_fmt); } catch (recoverable_failure const & e) - { } + { + L(FL("date check failed: %s") % e.what()); + } + if (parsed != now) + { + L(FL("date check failed: %s != %s") % now % parsed); + } + E(parsed == now, origin::user, F("date format '%s' cannot be used for commit") % date_fmt); ============================================================ --- dates.cc c9c15e57ebd09330e4a6a5b2d8abe324b4f6b179 +++ dates.cc f212e00a503f5256fdf5a321c824965a2e49a835 @@ -367,6 +367,8 @@ date_t::as_formatted_localtime(string co string date_t::as_formatted_localtime(string const & fmt) const { + L(FL("formatting date '%s' with format '%s'") % *this % fmt); + // note that the time_t value here may underflow or overflow if our date // is outside of the representable range. for 32 bit time_t's the earliest // representable time is 1901-12-13 20:45:52 UTC and the latest @@ -376,6 +378,8 @@ date_t::as_formatted_localtime(string co s64 seconds = d/1000 - get_epoch_offset(); + L(FL("%s seconds UTC since unix epoch") % seconds); + E(seconds >= numeric_limits::min(), origin::user, F("date '%s' is out of range and cannot be formatted") % as_iso_8601_extended()); @@ -387,6 +391,17 @@ date_t::as_formatted_localtime(string co time_t t(seconds); // seconds since unix epoch in UTC tm tb(*localtime(&t)); // converted to local timezone values + L(FL("localtime %4s/%02s/%02s %02s:%02s:%02s WD %s YD %s DST %d") + % (tb.tm_year + 1900) + % (tb.tm_mon + 1) + % tb.tm_mday + % tb.tm_hour + % tb.tm_min + % tb.tm_sec + % tb.tm_wday + % tb.tm_yday + % tb.tm_isdst); + char buf[128]; // Poison the buffer so we can tell whether strftime() produced @@ -396,7 +411,11 @@ date_t::as_formatted_localtime(string co size_t wrote = strftime(buf, sizeof buf, fmt.c_str(), &tb); if (wrote > 0) - return string(buf); // yay, it worked + { + string formatted(buf); + L(FL("formatted date '%s'") % formatted); + return formatted; // yay, it worked + } if (wrote == 0 && buf[0] == '\0') // no output { @@ -420,6 +439,9 @@ date_t::from_formatted_localtime(string { tm tb; memset(&tb, 0, sizeof(tb)); + + L(FL("parsing date '%s' with format '%s'") % s % fmt); + char *p = strptime(s.c_str(), fmt.c_str(), &tb); // local timezone values E(p, origin::user, // strptime failed to match all of the format string @@ -428,19 +450,38 @@ date_t::from_formatted_localtime(string E(*p == 0, origin::user, // extraneous characters in input string F("invalid date '%s' not matched by format '%s'") % s % fmt); + // strptime does *not* set the tm_isdst field in the broken down time + // struct. setting it to -1 is apparently the way to tell mktime to + // determine out whether DST is in effect or not. + + tb.tm_isdst = -1; + + L(FL("localtime %4s/%02s/%02s %02s:%02s:%02s WD %s YD %s DST %d") + % (tb.tm_year + 1900) + % (tb.tm_mon + 1) + % tb.tm_mday + % tb.tm_hour + % tb.tm_min + % tb.tm_sec + % tb.tm_wday + % tb.tm_yday + % tb.tm_isdst); + // note that the time_t value here may underflow or overflow if our date // is outside of the representable range. for 32 bit time_t's the earliest // representable time is 1901-12-13 20:45:52 UTC and the latest - // representable time is 2038-01-19 03:14:07 UTC. mktime seems to detect this - // and return -1 for values it cannot handle. + // representable time is 2038-01-19 03:14:07 UTC. mktime seems to detect + // this and return -1 for values it cannot handle, which strptime will + // happily produce. - time_t t = mktime(&tb); // converted to seconds since unix epoch in UTC + time_t t = mktime(&tb); // convert to seconds since unix epoch in UTC - // -1 is also 1960-12-31 23:59:59 but mktime uses it to indicate errors + L(FL("%s seconds UTC since unix epoch") % t); - E(t != -1, origin::user, - F("date '%s' is out of range and cannot be parsed") - % s); + // mktime may return a time_t that has the value -1 to indicate an error. + // however this is also the valid date 1969-12-31 23:59:59. so we ignore this + // error indication and convert the resulting time_t back to a struct tm + // for comparison with the input to mktime to detect out of range errors. tm check(*localtime(&t)); // back to local timezone values @@ -457,7 +498,11 @@ date_t::from_formatted_localtime(string F("date '%s' is out of range and cannot be parsed") % s); - return date_t(MILLISEC(t) + get_epoch_offset()); + date_t date(MILLISEC(t) + get_epoch_offset()); + + L(FL("parsed date '%s'") % date); + + return date; } s64 ============================================================ --- unit-tests/dates.cc 78271d63f77ab5d3e25ae95260b67f9a038e6b31 +++ unit-tests/dates.cc 4b2e6adef983df5508957aa5642f67e00c7e97ce @@ -235,15 +235,16 @@ UNIT_TEST(roundtrip_localtimes) } // this date represents 1 second before the unix epoch which has a time_t - // value of -1. mktime returns -1 to indicate that it was unable to - // convert a struct tm into a valid time_t value even though dates - // before/after this date are valid. - date_t mktime1("1969-12-31T23:59:59"); + // value of -1. conveniently, mktime returns -1 to indicate that it was + // unable to convert a struct tm into a valid time_t value even though + // dates before/after this are valid. our date parsing code ignores this + // "error" from mktime, does a conversion back to localtime and compares + // this with the localtime value it called mktime with in the first place. + // allowing conversion of this date to succeed while still detecting dates + // that are out of range. - // this can be formatted but not parsed. 64 bit time_t probably doesn't help - mktime1.as_formatted_localtime("%c"); - UNIT_TEST_CHECK_THROW(date_t::from_formatted_localtime("Wed Dec 31 23:59:59 1969", "%c"), - recoverable_failure); + OK(date_t("1969-12-31T23:59:59")); + #undef OK }