summaryrefslogtreecommitdiff
path: root/absl/time/internal
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2020-09-18 15:55:15 -0700
committerGravatar Derek Mauro <dmauro@google.com>2020-09-24 13:47:15 -0400
commitb56cbdd23834a65682c0b46f367f8679e83bc894 (patch)
treedacab9a64dd1a9e9668737e511d1a5420ff96001 /absl/time/internal
parentb832dce8489ef7b6231384909fd9b68d5a5ff2b7 (diff)
Abseil LTS 2020092320200923
What's New: * `absl::StatusOr<T>` has been released. See our [blog post](https://abseil.io/blog/2020-091021-status) for more information. * Abseil Flags reflection interfaces have been released. * Abseil Flags memory usage has been significantly optimized. * Abseil now supports a "hardened" build mode. This build mode enables runtime checks that guard against programming errors that may lead to security vulnerabilities. Notable Fixes: * Sanitizer dynamic annotations like `AnnotateRWLockCreate` that are also defined by the compiler sanitizer implementation are no longer also defined by Abseil. * Sanitizer macros are now prefixed with `ABSL_` to avoid naming collisions. * Sanitizer usage is now automatically detected and no longer requires macros like `ADDRESS_SANITIZER` to be defined on the command line. Breaking Changes: * Abseil no longer contains a `dynamic_annotations` library. Users using a supported build system (Bazel or CMake) are unaffected by this, but users manually specifying link libraries may get an error about a missing linker input. Baseline: 7680a5f8efe32de4753baadbd63e74e59d95bac1 Cherry picks: None
Diffstat (limited to 'absl/time/internal')
-rw-r--r--absl/time/internal/cctz/BUILD.bazel7
-rw-r--r--absl/time/internal/cctz/include/cctz/civil_time_detail.h42
-rw-r--r--absl/time/internal/cctz/include/cctz/time_zone.h6
-rw-r--r--absl/time/internal/cctz/include/cctz/zone_info_source.h2
-rw-r--r--absl/time/internal/cctz/src/cctz_benchmark.cc17
-rw-r--r--absl/time/internal/cctz/src/civil_time_test.cc10
-rw-r--r--absl/time/internal/cctz/src/time_zone_format.cc165
-rw-r--r--absl/time/internal/cctz/src/time_zone_format_test.cc113
-rw-r--r--absl/time/internal/cctz/src/time_zone_impl.cc34
-rw-r--r--absl/time/internal/cctz/src/time_zone_impl.h2
-rw-r--r--absl/time/internal/cctz/src/time_zone_info.cc265
-rw-r--r--absl/time/internal/cctz/src/time_zone_info.h9
-rw-r--r--absl/time/internal/cctz/src/time_zone_libc.cc15
-rw-r--r--absl/time/internal/cctz/src/time_zone_lookup_test.cc19
-rw-r--r--absl/time/internal/cctz/src/tzfile.h12
-rw-r--r--absl/time/internal/cctz/src/zone_info_source.cc3
-rw-r--r--absl/time/internal/cctz/testdata/version2
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablancabin2429 -> 2429 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiunbin2295 -> 2295 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/America/Dawsonbin2084 -> 1600 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/America/Nuukbin0 -> 1878 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorsebin2084 -> 1600 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Asia/Chongqingbin533 -> 561 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Asia/Chungkingbin533 -> 561 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Asia/Harbinbin533 -> 561 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Asia/Shanghaibin533 -> 561 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukonbin2084 -> 1600 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/PRCbin533 -> 561 bytes
-rw-r--r--absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab12
-rw-r--r--absl/time/internal/test_util.cc1
30 files changed, 490 insertions, 246 deletions
diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel
index 7a53c815..45a95292 100644
--- a/absl/time/internal/cctz/BUILD.bazel
+++ b/absl/time/internal/cctz/BUILD.bazel
@@ -16,7 +16,7 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
package(features = ["-parse_headers"])
-licenses(["notice"]) # Apache License
+licenses(["notice"])
filegroup(
name = "zoneinfo",
@@ -92,6 +92,11 @@ cc_library(
### tests
+test_suite(
+ name = "all_tests",
+ visibility = ["//visibility:public"],
+)
+
cc_test(
name = "civil_time_test",
size = "small",
diff --git a/absl/time/internal/cctz/include/cctz/civil_time_detail.h b/absl/time/internal/cctz/include/cctz/civil_time_detail.h
index 4cde96f1..d1b4222b 100644
--- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h
+++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h
@@ -106,54 +106,64 @@ CONSTEXPR_F int days_per_month(year_t y, month_t m) noexcept {
CONSTEXPR_F fields n_day(year_t y, month_t m, diff_t d, diff_t cd, hour_t hh,
minute_t mm, second_t ss) noexcept {
- y += (cd / 146097) * 400;
+ year_t ey = y % 400;
+ const year_t oey = ey;
+ ey += (cd / 146097) * 400;
cd %= 146097;
if (cd < 0) {
- y -= 400;
+ ey -= 400;
cd += 146097;
}
- y += (d / 146097) * 400;
+ ey += (d / 146097) * 400;
d = d % 146097 + cd;
if (d > 0) {
if (d > 146097) {
- y += 400;
+ ey += 400;
d -= 146097;
}
} else {
if (d > -365) {
// We often hit the previous year when stepping a civil time backwards,
// so special case it to avoid counting up by 100/4/1-year chunks.
- y -= 1;
- d += days_per_year(y, m);
+ ey -= 1;
+ d += days_per_year(ey, m);
} else {
- y -= 400;
+ ey -= 400;
d += 146097;
}
}
if (d > 365) {
- for (int n = days_per_century(y, m); d > n; n = days_per_century(y, m)) {
+ for (;;) {
+ int n = days_per_century(ey, m);
+ if (d <= n) break;
d -= n;
- y += 100;
+ ey += 100;
}
- for (int n = days_per_4years(y, m); d > n; n = days_per_4years(y, m)) {
+ for (;;) {
+ int n = days_per_4years(ey, m);
+ if (d <= n) break;
d -= n;
- y += 4;
+ ey += 4;
}
- for (int n = days_per_year(y, m); d > n; n = days_per_year(y, m)) {
+ for (;;) {
+ int n = days_per_year(ey, m);
+ if (d <= n) break;
d -= n;
- ++y;
+ ++ey;
}
}
if (d > 28) {
- for (int n = days_per_month(y, m); d > n; n = days_per_month(y, m)) {
+ for (;;) {
+ int n = days_per_month(ey, m);
+ if (d <= n) break;
d -= n;
if (++m > 12) {
- ++y;
+ ++ey;
m = 1;
}
}
}
- return fields(y, m, static_cast<day_t>(d), hh, mm, ss);
+ return fields(y + (ey - oey), m, static_cast<day_t>(d), hh, mm, ss);
}
CONSTEXPR_F fields n_mon(year_t y, diff_t m, diff_t d, diff_t cd, hour_t hh,
minute_t mm, second_t ss) noexcept {
diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h
index d05147a1..5562a37b 100644
--- a/absl/time/internal/cctz/include/cctz/time_zone.h
+++ b/absl/time/internal/cctz/include/cctz/time_zone.h
@@ -209,7 +209,7 @@ class time_zone {
// version() and description() provide additional information about the
// time zone. The content of each of the returned strings is unspecified,
// however, when the IANA Time Zone Database is the underlying data source
- // the version() std::string will be in the familar form (e.g, "2018e") or
+ // the version() string will be in the familar form (e.g, "2018e") or
// empty when unavailable.
//
// Note: These functions are for informational or testing purposes only.
@@ -292,6 +292,7 @@ bool parse(const std::string&, const std::string&, const time_zone&,
// - %E#f - Fractional seconds with # digits of precision
// - %E*f - Fractional seconds with full precision (a literal '*')
// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
+// - %ET - The RFC3339 "date-time" separator "T"
//
// Note that %E0S behaves like %S, and %E0f produces no characters. In
// contrast %E*f always produces at least one digit, which may be '0'.
@@ -321,7 +322,8 @@ inline std::string format(const std::string& fmt, const time_point<D>& tp,
// returns the corresponding time_point. Uses strftime()-like formatting
// options, with the same extensions as cctz::format(), but with the
// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez
-// and %E*z also accept the same inputs.
+// and %E*z also accept the same inputs, which (along with %z) includes
+// 'z' and 'Z' as synonyms for +00:00. %ET accepts either 'T' or 't'.
//
// %Y consumes as many numeric characters as it can, so the matching data
// should always be terminated with a non-numeric. %E4Y always consumes
diff --git a/absl/time/internal/cctz/include/cctz/zone_info_source.h b/absl/time/internal/cctz/include/cctz/zone_info_source.h
index 912b44ba..012eb4ec 100644
--- a/absl/time/internal/cctz/include/cctz/zone_info_source.h
+++ b/absl/time/internal/cctz/include/cctz/zone_info_source.h
@@ -37,7 +37,7 @@ class ZoneInfoSource {
// Until the zoneinfo data supports versioning information, we provide
// a way for a ZoneInfoSource to indicate it out-of-band. The default
- // implementation returns an empty std::string.
+ // implementation returns an empty string.
virtual std::string Version() const;
};
diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc
index d30a644e..4e39188f 100644
--- a/absl/time/internal/cctz/src/cctz_benchmark.cc
+++ b/absl/time/internal/cctz/src/cctz_benchmark.cc
@@ -97,8 +97,8 @@ void BM_PrevWeekday(benchmark::State& state) {
}
BENCHMARK(BM_PrevWeekday);
-const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez";
-const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez";
+const char RFC3339_full[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez";
+const char RFC3339_sec[] = "%Y-%m-%d%ET%H:%M:%S%Ez";
const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z";
const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z";
@@ -280,6 +280,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
+ "America/Nuuk",
"America/Ojinaga",
"America/Panama",
"America/Pangnirtung",
@@ -990,12 +991,12 @@ void BM_Time_FromCivilDay0_Libc(benchmark::State& state) {
BENCHMARK(BM_Time_FromCivilDay0_Libc);
const char* const kFormats[] = {
- RFC1123_full, // 0
- RFC1123_no_wday, // 1
- RFC3339_full, // 2
- RFC3339_sec, // 3
- "%Y-%m-%dT%H:%M:%S", // 4
- "%Y-%m-%d", // 5
+ RFC1123_full, // 0
+ RFC1123_no_wday, // 1
+ RFC3339_full, // 2
+ RFC3339_sec, // 3
+ "%Y-%m-%d%ET%H:%M:%S", // 4
+ "%Y-%m-%d", // 5
};
const int kNumFormats = sizeof(kFormats) / sizeof(kFormats[0]);
diff --git a/absl/time/internal/cctz/src/civil_time_test.cc b/absl/time/internal/cctz/src/civil_time_test.cc
index be894d70..a5a71230 100644
--- a/absl/time/internal/cctz/src/civil_time_test.cc
+++ b/absl/time/internal/cctz/src/civil_time_test.cc
@@ -235,6 +235,16 @@ TEST(CivilTime, Difference) {
}
// NOTE: Run this with --copt=-ftrapv to detect overflow problems.
+TEST(CivilTime, ConstructionWithHugeYear) {
+ constexpr civil_hour h(-9223372036854775807, 1, 1, -1);
+ static_assert(h.year() == -9223372036854775807 - 1,
+ "ConstructionWithHugeYear");
+ static_assert(h.month() == 12, "ConstructionWithHugeYear");
+ static_assert(h.day() == 31, "ConstructionWithHugeYear");
+ static_assert(h.hour() == 23, "ConstructionWithHugeYear");
+}
+
+// NOTE: Run this with --copt=-ftrapv to detect overflow problems.
TEST(CivilTime, DifferenceWithHugeYear) {
{
constexpr civil_day d1(9223372036854775807, 1, 1);
diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc
index 950b23a1..d8cb0474 100644
--- a/absl/time/internal/cctz/src/time_zone_format.cc
+++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -67,6 +67,48 @@ char* strptime(const char* s, const char* fmt, std::tm* tm) {
}
#endif
+// Convert a cctz::weekday to a tm_wday value (0-6, Sunday = 0).
+int ToTmWday(weekday wd) {
+ switch (wd) {
+ case weekday::sunday:
+ return 0;
+ case weekday::monday:
+ return 1;
+ case weekday::tuesday:
+ return 2;
+ case weekday::wednesday:
+ return 3;
+ case weekday::thursday:
+ return 4;
+ case weekday::friday:
+ return 5;
+ case weekday::saturday:
+ return 6;
+ }
+ return 0; /*NOTREACHED*/
+}
+
+// Convert a tm_wday value (0-6, Sunday = 0) to a cctz::weekday.
+weekday FromTmWday(int tm_wday) {
+ switch (tm_wday) {
+ case 0:
+ return weekday::sunday;
+ case 1:
+ return weekday::monday;
+ case 2:
+ return weekday::tuesday;
+ case 3:
+ return weekday::wednesday;
+ case 4:
+ return weekday::thursday;
+ case 5:
+ return weekday::friday;
+ case 6:
+ return weekday::saturday;
+ }
+ return weekday::sunday; /*NOTREACHED*/
+}
+
std::tm ToTM(const time_zone::absolute_lookup& al) {
std::tm tm{};
tm.tm_sec = al.cs.second();
@@ -84,34 +126,19 @@ std::tm ToTM(const time_zone::absolute_lookup& al) {
tm.tm_year = static_cast<int>(al.cs.year() - 1900);
}
- switch (get_weekday(al.cs)) {
- case weekday::sunday:
- tm.tm_wday = 0;
- break;
- case weekday::monday:
- tm.tm_wday = 1;
- break;
- case weekday::tuesday:
- tm.tm_wday = 2;
- break;
- case weekday::wednesday:
- tm.tm_wday = 3;
- break;
- case weekday::thursday:
- tm.tm_wday = 4;
- break;
- case weekday::friday:
- tm.tm_wday = 5;
- break;
- case weekday::saturday:
- tm.tm_wday = 6;
- break;
- }
+ tm.tm_wday = ToTmWday(get_weekday(al.cs));
tm.tm_yday = get_yearday(al.cs) - 1;
tm.tm_isdst = al.is_dst ? 1 : 0;
return tm;
}
+// Returns the week of the year [0:53] given a civil day and the day on
+// which weeks are defined to start.
+int ToWeek(const civil_day& cd, weekday week_start) {
+ const civil_day d(cd.year() % 400, cd.month(), cd.day());
+ return static_cast<int>((d - prev_weekday(civil_year(d), week_start)) / 7);
+}
+
const char kDigits[] = "0123456789";
// Formats a 64-bit integer in the given field width. Note that it is up
@@ -189,7 +216,7 @@ void FormatTM(std::string* out, const std::string& fmt, const std::tm& tm) {
// strftime(3) returns the number of characters placed in the output
// array (which may be 0 characters). It also returns 0 to indicate
// an error, like the array wasn't large enough. To accommodate this,
- // the following code grows the buffer size from 2x the format std::string
+ // the following code grows the buffer size from 2x the format string
// length up to 32x.
for (std::size_t i = 2; i != 32; i *= 2) {
std::size_t buf_size = fmt.size() * i;
@@ -290,6 +317,7 @@ const std::int_fast64_t kExp10[kDigits10_64 + 1] = {
// - %E#S - Seconds with # digits of fractional precision
// - %E*S - Seconds with full fractional precision (a literal '*')
// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
+// - %ET - The RFC3339 "date-time" separator "T"
//
// The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are
// handled internally for performance reasons. strftime(3) is slow due to
@@ -354,7 +382,7 @@ std::string format(const std::string& format, const time_point<seconds>& tp,
if (cur == end || (cur - percent) % 2 == 0) continue;
// Simple specifiers that we handle ourselves.
- if (strchr("YmdeHMSzZs%", *cur)) {
+ if (strchr("YmdeUuWwHMSzZs%", *cur)) {
if (cur - 1 != pending) {
FormatTM(&result, std::string(pending, cur - 1), tm);
}
@@ -375,6 +403,22 @@ std::string format(const std::string& format, const time_point<seconds>& tp,
if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows
result.append(bp, static_cast<std::size_t>(ep - bp));
break;
+ case 'U':
+ bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::sunday));
+ result.append(bp, static_cast<std::size_t>(ep - bp));
+ break;
+ case 'u':
+ bp = Format64(ep, 0, tm.tm_wday ? tm.tm_wday : 7);
+ result.append(bp, static_cast<std::size_t>(ep - bp));
+ break;
+ case 'W':
+ bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::monday));
+ result.append(bp, static_cast<std::size_t>(ep - bp));
+ break;
+ case 'w':
+ bp = Format64(ep, 0, tm.tm_wday);
+ result.append(bp, static_cast<std::size_t>(ep - bp));
+ break;
case 'H':
bp = Format02d(ep, al.cs.hour());
result.append(bp, static_cast<std::size_t>(ep - bp));
@@ -448,7 +492,14 @@ std::string format(const std::string& format, const time_point<seconds>& tp,
if (*cur != 'E' || ++cur == end) continue;
// Format our extensions.
- if (*cur == 'z') {
+ if (*cur == 'T') {
+ // Formats %ET.
+ if (cur - 2 != pending) {
+ FormatTM(&result, std::string(pending, cur - 2), tm);
+ }
+ result.append("T");
+ pending = ++cur;
+ } else if (*cur == 'z') {
// Formats %Ez.
if (cur - 2 != pending) {
FormatTM(&result, std::string(pending, cur - 2), tm);
@@ -551,7 +602,7 @@ const char* ParseOffset(const char* dp, const char* mode, int* offset) {
} else {
dp = nullptr;
}
- } else if (first == 'Z') { // Zulu
+ } else if (first == 'Z' || first == 'z') { // Zulu
*offset = 0;
} else {
dp = nullptr;
@@ -602,12 +653,32 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) {
return dp;
}
+// Sets year, tm_mon and tm_mday given the year, week_num, and tm_wday,
+// and the day on which weeks are defined to start. Returns false if year
+// would need to move outside its bounds.
+bool FromWeek(int week_num, weekday week_start, year_t* year, std::tm* tm) {
+ const civil_year y(*year % 400);
+ civil_day cd = prev_weekday(y, week_start); // week 0
+ cd = next_weekday(cd - 1, FromTmWday(tm->tm_wday)) + (week_num * 7);
+ if (const year_t shift = cd.year() - y.year()) {
+ if (shift > 0) {
+ if (*year > std::numeric_limits<year_t>::max() - shift) return false;
+ } else {
+ if (*year < std::numeric_limits<year_t>::min() - shift) return false;
+ }
+ *year += shift;
+ }
+ tm->tm_mon = cd.month() - 1;
+ tm->tm_mday = cd.day();
+ return true;
+}
+
} // namespace
// Uses strptime(3) to parse the given input. Supports the same extended
// format specifiers as format(), although %E#S and %E*S are treated
// identically (and similarly for %E#f and %E*f). %Ez and %E*z also accept
-// the same inputs.
+// the same inputs. %ET accepts either 'T' or 't'.
//
// The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are
// handled internally so that we can normally avoid strptime() altogether
@@ -651,6 +722,8 @@ bool parse(const std::string& format, const std::string& input,
const char* fmt = format.c_str(); // NUL terminated
bool twelve_hour = false;
bool afternoon = false;
+ int week_num = -1;
+ weekday week_start = weekday::sunday;
bool saw_percent_s = false;
std::int_fast64_t percent_s = 0;
@@ -689,10 +762,27 @@ bool parse(const std::string& format, const std::string& input,
case 'm':
data = ParseInt(data, 2, 1, 12, &tm.tm_mon);
if (data != nullptr) tm.tm_mon -= 1;
+ week_num = -1;
continue;
case 'd':
case 'e':
data = ParseInt(data, 2, 1, 31, &tm.tm_mday);
+ week_num = -1;
+ continue;
+ case 'U':
+ data = ParseInt(data, 0, 0, 53, &week_num);
+ week_start = weekday::sunday;
+ continue;
+ case 'W':
+ data = ParseInt(data, 0, 0, 53, &week_num);
+ week_start = weekday::monday;
+ continue;
+ case 'u':
+ data = ParseInt(data, 0, 1, 7, &tm.tm_wday);
+ if (data != nullptr) tm.tm_wday %= 7;
+ continue;
+ case 'w':
+ data = ParseInt(data, 0, 0, 6, &tm.tm_wday);
continue;
case 'H':
data = ParseInt(data, 2, 0, 23, &tm.tm_hour);
@@ -742,6 +832,15 @@ bool parse(const std::string& format, const std::string& input,
data = (*data == '%' ? data + 1 : nullptr);
continue;
case 'E':
+ if (fmt[0] == 'T') {
+ if (*data == 'T' || *data == 't') {
+ ++data;
+ ++fmt;
+ } else {
+ data = nullptr;
+ }
+ continue;
+ }
if (fmt[0] == 'z' || (fmt[0] == '*' && fmt[1] == 'z')) {
data = ParseOffset(data, ":", &offset);
if (data != nullptr) saw_offset = true;
@@ -839,7 +938,7 @@ bool parse(const std::string& format, const std::string& input,
// Skip any remaining whitespace.
while (std::isspace(*data)) ++data;
- // parse() must consume the entire input std::string.
+ // parse() must consume the entire input string.
if (*data != '\0') {
if (err != nullptr) *err = "Illegal trailing data in input string";
return false;
@@ -874,6 +973,14 @@ bool parse(const std::string& format, const std::string& input,
year += 1900;
}
+ // Compute year, tm.tm_mon and tm.tm_mday if we parsed a week number.
+ if (week_num != -1) {
+ if (!FromWeek(week_num, week_start, &year, &tm)) {
+ if (err != nullptr) *err = "Out-of-range field";
+ return false;
+ }
+ }
+
const int month = tm.tm_mon + 1;
civil_second cs(year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc
index caebcc4d..a11f93e2 100644
--- a/absl/time/internal/cctz/src/time_zone_format_test.cc
+++ b/absl/time/internal/cctz/src/time_zone_format_test.cc
@@ -48,8 +48,8 @@ namespace {
EXPECT_STREQ(zone, al.abbr); \
} while (0)
-const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez";
-const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez";
+const char RFC3339_full[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez";
+const char RFC3339_sec[] = "%Y-%m-%d%ET%H:%M:%S%Ez";
const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z";
const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z";
@@ -679,6 +679,34 @@ TEST(Format, RFC1123Format) { // locale specific
EXPECT_EQ("28 Jun 1977 09:08:07 -0700", format(RFC1123_no_wday, tp, tz));
}
+TEST(Format, Week) {
+ const time_zone utc = utc_time_zone();
+
+ auto tp = convert(civil_second(2017, 1, 1, 0, 0, 0), utc);
+ EXPECT_EQ("2017-01-7", format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2017-00-0", format("%Y-%W-%w", tp, utc));
+
+ tp = convert(civil_second(2017, 12, 31, 0, 0, 0), utc);
+ EXPECT_EQ("2017-53-7", format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2017-52-0", format("%Y-%W-%w", tp, utc));
+
+ tp = convert(civil_second(2018, 1, 1, 0, 0, 0), utc);
+ EXPECT_EQ("2018-00-1", format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2018-01-1", format("%Y-%W-%w", tp, utc));
+
+ tp = convert(civil_second(2018, 12, 31, 0, 0, 0), utc);
+ EXPECT_EQ("2018-52-1", format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2018-53-1", format("%Y-%W-%w", tp, utc));
+
+ tp = convert(civil_second(2019, 1, 1, 0, 0, 0), utc);
+ EXPECT_EQ("2019-00-2", format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2019-00-2", format("%Y-%W-%w", tp, utc));
+
+ tp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc);
+ EXPECT_EQ("2019-52-2", format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2019-52-2", format("%Y-%W-%w", tp, utc));
+}
+
//
// Testing parse()
//
@@ -767,7 +795,7 @@ TEST(Parse, WithTimeZone) {
EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz));
time_point<chrono::nanoseconds> tp;
- // We can parse a std::string without a UTC offset if we supply a timezone.
+ // We can parse a string without a UTC offset if we supply a timezone.
EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp));
ExpectTime(tp, tz, 2013, 6, 28, 19, 8, 9, -7 * 60 * 60, true, "PDT");
@@ -1379,10 +1407,85 @@ TEST(Parse, RFC3339Format) {
EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp));
ExpectTime(tp, tz, 2014, 2, 12, 20, 21, 0, 0, false, "UTC");
- // Check that %Ez also accepts "Z" as a synonym for "+00:00".
+ // Check that %ET also accepts "t".
time_point<chrono::nanoseconds> tp2;
- EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2));
+ EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12t20:21:00+00:00", tz, &tp2));
EXPECT_EQ(tp, tp2);
+
+ // Check that %Ez also accepts "Z" as a synonym for "+00:00".
+ time_point<chrono::nanoseconds> tp3;
+ EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp3));
+ EXPECT_EQ(tp, tp3);
+
+ // Check that %Ez also accepts "z" as a synonym for "+00:00".
+ time_point<chrono::nanoseconds> tp4;
+ EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00z", tz, &tp4));
+ EXPECT_EQ(tp, tp4);
+}
+
+TEST(Parse, Week) {
+ const time_zone utc = utc_time_zone();
+ time_point<absl::time_internal::cctz::seconds> tp;
+
+ auto exp = convert(civil_second(2017, 1, 1, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2017-01-7", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2017-00-0", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ exp = convert(civil_second(2017, 12, 31, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2017-53-7", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2017-52-0", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ exp = convert(civil_second(2018, 1, 1, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2018-00-1", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2018-01-1", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ exp = convert(civil_second(2018, 12, 31, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2018-52-1", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2018-53-1", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ exp = convert(civil_second(2019, 1, 1, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2019-00-2", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2019-00-2", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ exp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2019-52-2", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2019-52-2", utc, &tp));
+ EXPECT_EQ(exp, tp);
+}
+
+TEST(Parse, WeekYearShift) {
+ // %U/%W conversions with week values in {0, 52, 53} can slip
+ // into the previous/following calendar years.
+ const time_zone utc = utc_time_zone();
+ time_point<absl::time_internal::cctz::seconds> tp;
+
+ auto exp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2020-00-2", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2020-00-2", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ exp = convert(civil_second(2021, 1, 1, 0, 0, 0), utc);
+ EXPECT_TRUE(parse("%Y-%U-%u", "2020-52-5", utc, &tp));
+ EXPECT_EQ(exp, tp);
+ EXPECT_TRUE(parse("%Y-%W-%w", "2020-52-5", utc, &tp));
+ EXPECT_EQ(exp, tp);
+
+ // Slipping into the previous/following calendar years should fail when
+ // we're already at the extremes.
+ EXPECT_FALSE(parse("%Y-%U-%u", "-9223372036854775808-0-7", utc, &tp));
+ EXPECT_FALSE(parse("%Y-%U-%u", "9223372036854775807-53-7", utc, &tp));
}
TEST(Parse, MaxRange) {
diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc
index 030ae0e1..f34e3aec 100644
--- a/absl/time/internal/cctz/src/time_zone_impl.cc
+++ b/absl/time/internal/cctz/src/time_zone_impl.cc
@@ -15,6 +15,7 @@
#include "time_zone_impl.h"
#include <deque>
+#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
@@ -48,17 +49,16 @@ std::mutex& TimeZoneMutex() {
time_zone time_zone::Impl::UTC() { return time_zone(UTCImpl()); }
bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) {
- const time_zone::Impl* const utc_impl = UTCImpl();
+ const Impl* const utc_impl = UTCImpl();
- // First check for UTC (which is never a key in time_zone_map).
+ // Check for UTC (which is never a key in time_zone_map).
auto offset = seconds::zero();
if (FixedOffsetFromName(name, &offset) && offset == seconds::zero()) {
*tz = time_zone(utc_impl);
return true;
}
- // Then check, under a shared lock, whether the time zone has already
- // been loaded. This is the common path. TODO: Move to shared_mutex.
+ // Check whether the time zone has already been loaded.
{
std::lock_guard<std::mutex> lock(TimeZoneMutex());
if (time_zone_map != nullptr) {
@@ -70,20 +70,15 @@ bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) {
}
}
- // Now check again, under an exclusive lock.
+ // Load the new time zone (outside the lock).
+ std::unique_ptr<const Impl> new_impl(new Impl(name));
+
+ // Add the new time zone to the map.
std::lock_guard<std::mutex> lock(TimeZoneMutex());
if (time_zone_map == nullptr) time_zone_map = new TimeZoneImplByName;
const Impl*& impl = (*time_zone_map)[name];
- if (impl == nullptr) {
- // The first thread in loads the new time zone.
- Impl* new_impl = new Impl(name);
- new_impl->zone_ = TimeZoneIf::Load(new_impl->name_);
- if (new_impl->zone_ == nullptr) {
- delete new_impl; // free the nascent Impl
- impl = utc_impl; // and fallback to UTC
- } else {
- impl = new_impl; // install new time zone
- }
+ if (impl == nullptr) { // this thread won any load race
+ impl = new_impl->zone_ ? new_impl.release() : utc_impl;
}
*tz = time_zone(impl);
return impl != utc_impl;
@@ -104,14 +99,11 @@ void time_zone::Impl::ClearTimeZoneMapTestOnly() {
}
}
-time_zone::Impl::Impl(const std::string& name) : name_(name) {}
+time_zone::Impl::Impl(const std::string& name)
+ : name_(name), zone_(TimeZoneIf::Load(name_)) {}
const time_zone::Impl* time_zone::Impl::UTCImpl() {
- static Impl* utc_impl = [] {
- Impl* impl = new Impl("UTC");
- impl->zone_ = TimeZoneIf::Load(impl->name_); // never fails
- return impl;
- }();
+ static const Impl* utc_impl = new Impl("UTC"); // never fails
return utc_impl;
}
diff --git a/absl/time/internal/cctz/src/time_zone_impl.h b/absl/time/internal/cctz/src/time_zone_impl.h
index 69806c10..7d747ba9 100644
--- a/absl/time/internal/cctz/src/time_zone_impl.h
+++ b/absl/time/internal/cctz/src/time_zone_impl.h
@@ -71,7 +71,7 @@ class time_zone::Impl {
return zone_->PrevTransition(tp, trans);
}
- // Returns an implementation-defined version std::string for this time zone.
+ // Returns an implementation-defined version string for this time zone.
std::string Version() const { return zone_->Version(); }
// Returns an implementation-defined description of this time zone.
diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc
index f1697cdf..8039353e 100644
--- a/absl/time/internal/cctz/src/time_zone_info.cc
+++ b/absl/time/internal/cctz/src/time_zone_info.cc
@@ -40,7 +40,6 @@
#include <cstdlib>
#include <cstring>
#include <functional>
-#include <iostream>
#include <memory>
#include <sstream>
#include <string>
@@ -83,6 +82,27 @@ const std::int_least32_t kSecsPerYear[2] = {
366 * kSecsPerDay,
};
+// Convert a cctz::weekday to a POSIX TZ weekday number (0==Sun, ..., 6=Sat).
+inline int ToPosixWeekday(weekday wd) {
+ switch (wd) {
+ case weekday::sunday:
+ return 0;
+ case weekday::monday:
+ return 1;
+ case weekday::tuesday:
+ return 2;
+ case weekday::wednesday:
+ return 3;
+ case weekday::thursday:
+ return 4;
+ case weekday::friday:
+ return 5;
+ case weekday::saturday:
+ return 6;
+ }
+ return 0; /*NOTREACHED*/
+}
+
// Single-byte, unsigned numeric values are encoded directly.
inline std::uint_fast8_t Decode8(const char* cp) {
return static_cast<std::uint_fast8_t>(*cp) & 0xff;
@@ -188,15 +208,13 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
tt.is_dst = false;
tt.abbr_index = 0;
- // We temporarily add some redundant, contemporary (2013 through 2023)
+ // We temporarily add some redundant, contemporary (2015 through 2025)
// transitions for performance reasons. See TimeZoneInfo::LocalTime().
// TODO: Fix the performance issue and remove the extra transitions.
transitions_.clear();
transitions_.reserve(12);
for (const std::int_fast64_t unix_time : {
- -(1LL << 59), // BIG_BANG
- 1356998400LL, // 2013-01-01T00:00:00+00:00
- 1388534400LL, // 2014-01-01T00:00:00+00:00
+ -(1LL << 59), // a "first half" transition
1420070400LL, // 2015-01-01T00:00:00+00:00
1451606400LL, // 2016-01-01T00:00:00+00:00
1483228800LL, // 2017-01-01T00:00:00+00:00
@@ -206,7 +224,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
1609459200LL, // 2021-01-01T00:00:00+00:00
1640995200LL, // 2022-01-01T00:00:00+00:00
1672531200LL, // 2023-01-01T00:00:00+00:00
- 2147483647LL, // 2^31 - 1
+ 1704067200LL, // 2024-01-01T00:00:00+00:00
+ 1735689600LL, // 2025-01-01T00:00:00+00:00
}) {
Transition& tr(*transitions_.emplace(transitions_.end()));
tr.unix_time = unix_time;
@@ -217,8 +236,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
default_transition_type_ = 0;
abbreviations_ = FixedOffsetToAbbr(offset);
- abbreviations_.append(1, '\0'); // add NUL
- future_spec_.clear(); // never needed for a fixed-offset zone
+ abbreviations_.append(1, '\0');
+ future_spec_.clear(); // never needed for a fixed-offset zone
extended_ = false;
tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
@@ -259,21 +278,6 @@ std::size_t TimeZoneInfo::Header::DataLength(std::size_t time_len) const {
return len;
}
-// Check that the TransitionType has the expected offset/is_dst/abbreviation.
-void TimeZoneInfo::CheckTransition(const std::string& name,
- const TransitionType& tt,
- std::int_fast32_t offset, bool is_dst,
- const std::string& abbr) const {
- if (tt.utc_offset != offset || tt.is_dst != is_dst ||
- &abbreviations_[tt.abbr_index] != abbr) {
- std::clog << name << ": Transition"
- << " offset=" << tt.utc_offset << "/"
- << (tt.is_dst ? "DST" : "STD")
- << "/abbr=" << &abbreviations_[tt.abbr_index]
- << " does not match POSIX spec '" << future_spec_ << "'\n";
- }
-}
-
// zic(8) can generate no-op transitions when a zone changes rules at an
// instant when there is actually no discontinuity. So we check whether
// two transitions have equivalent types (same offset/is_dst/abbr).
@@ -282,117 +286,108 @@ bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index,
if (tt1_index == tt2_index) return true;
const TransitionType& tt1(transition_types_[tt1_index]);
const TransitionType& tt2(transition_types_[tt2_index]);
- if (tt1.is_dst != tt2.is_dst) return false;
if (tt1.utc_offset != tt2.utc_offset) return false;
+ if (tt1.is_dst != tt2.is_dst) return false;
if (tt1.abbr_index != tt2.abbr_index) return false;
return true;
}
+// Find/make a transition type with these attributes.
+bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
+ const std::string& abbr,
+ std::uint_least8_t* index) {
+ std::size_t type_index = 0;
+ std::size_t abbr_index = abbreviations_.size();
+ for (; type_index != transition_types_.size(); ++type_index) {
+ const TransitionType& tt(transition_types_[type_index]);
+ const char* tt_abbr = &abbreviations_[tt.abbr_index];
+ if (tt_abbr == abbr) abbr_index = tt.abbr_index;
+ if (tt.utc_offset == utc_offset && tt.is_dst == is_dst) {
+ if (abbr_index == tt.abbr_index) break; // reuse
+ }
+ }
+ if (type_index > 255 || abbr_index > 255) {
+ // No index space (8 bits) available for a new type or abbreviation.
+ return false;
+ }
+ if (type_index == transition_types_.size()) {
+ TransitionType& tt(*transition_types_.emplace(transition_types_.end()));
+ tt.utc_offset = static_cast<std::int_least32_t>(utc_offset);
+ tt.is_dst = is_dst;
+ if (abbr_index == abbreviations_.size()) {
+ abbreviations_.append(abbr);
+ abbreviations_.append(1, '\0');
+ }
+ tt.abbr_index = static_cast<std::uint_least8_t>(abbr_index);
+ }
+ *index = static_cast<std::uint_least8_t>(type_index);
+ return true;
+}
+
// Use the POSIX-TZ-environment-variable-style string to handle times
// in years after the last transition stored in the zoneinfo data.
-void TimeZoneInfo::ExtendTransitions(const std::string& name,
- const Header& hdr) {
+bool TimeZoneInfo::ExtendTransitions() {
extended_ = false;
- bool extending = !future_spec_.empty();
+ if (future_spec_.empty()) return true; // last transition prevails
PosixTimeZone posix;
- if (extending && !ParsePosixSpec(future_spec_, &posix)) {
- std::clog << name << ": Failed to parse '" << future_spec_ << "'\n";
- extending = false;
- }
-
- if (extending && posix.dst_abbr.empty()) { // std only
- // The future specification should match the last/default transition,
- // and that means that handling the future will fall out naturally.
- std::uint_fast8_t index = default_transition_type_;
- if (hdr.timecnt != 0) index = transitions_[hdr.timecnt - 1].type_index;
- const TransitionType& tt(transition_types_[index]);
- CheckTransition(name, tt, posix.std_offset, false, posix.std_abbr);
- extending = false;
- }
-
- if (extending && hdr.timecnt < 2) {
- std::clog << name << ": Too few transitions for POSIX spec\n";
- extending = false;
- }
-
- if (!extending) {
- // Ensure that there is always a transition in the second half of the
- // time line (the BIG_BANG transition is in the first half) so that the
- // signed difference between a civil_second and the civil_second of its
- // previous transition is always representable, without overflow.
- const Transition& last(transitions_.back());
- if (last.unix_time < 0) {
- const std::uint_fast8_t type_index = last.type_index;
- Transition& tr(*transitions_.emplace(transitions_.end()));
- tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00
- tr.type_index = type_index;
- }
- return; // last transition wins
+ if (!ParsePosixSpec(future_spec_, &posix)) return false;
+
+ // Find transition type for the future std specification.
+ std::uint_least8_t std_ti;
+ if (!GetTransitionType(posix.std_offset, false, posix.std_abbr, &std_ti))
+ return false;
+
+ if (posix.dst_abbr.empty()) { // std only
+ // The future specification should match the last transition, and
+ // that means that handling the future will fall out naturally.
+ return EquivTransitions(transitions_.back().type_index, std_ti);
}
+ // Find transition type for the future dst specification.
+ std::uint_least8_t dst_ti;
+ if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti))
+ return false;
+
// Extend the transitions for an additional 400 years using the
// future specification. Years beyond those can be handled by
// mapping back to a cycle-equivalent year within that range.
- // zic(8) should probably do this so that we don't have to.
- // TODO: Reduce the extension by the number of compatible
- // transitions already in place.
- transitions_.reserve(hdr.timecnt + 400 * 2 + 1);
- transitions_.resize(hdr.timecnt + 400 * 2);
+ // We may need two additional transitions for the current year.
+ transitions_.reserve(transitions_.size() + 400 * 2 + 2);
extended_ = true;
- // The future specification should match the last two transitions,
- // and those transitions should have different is_dst flags. Note
- // that nothing says the UTC offset used by the is_dst transition
- // must be greater than that used by the !is_dst transition. (See
- // Europe/Dublin, for example.)
- const Transition* tr0 = &transitions_[hdr.timecnt - 1];
- const Transition* tr1 = &transitions_[hdr.timecnt - 2];
- const TransitionType* tt0 = &transition_types_[tr0->type_index];
- const TransitionType* tt1 = &transition_types_[tr1->type_index];
- const TransitionType& dst(tt0->is_dst ? *tt0 : *tt1);
- const TransitionType& std(tt0->is_dst ? *tt1 : *tt0);
- CheckTransition(name, dst, posix.dst_offset, true, posix.dst_abbr);
- CheckTransition(name, std, posix.std_offset, false, posix.std_abbr);
-
- // Add the transitions to tr1 and back to tr0 for each extra year.
- last_year_ = LocalTime(tr0->unix_time, *tt0).cs.year();
+ const Transition& last(transitions_.back());
+ const std::int_fast64_t last_time = last.unix_time;
+ const TransitionType& last_tt(transition_types_[last.type_index]);
+ last_year_ = LocalTime(last_time, last_tt).cs.year();
bool leap_year = IsLeap(last_year_);
- const civil_day jan1(last_year_, 1, 1);
- std::int_fast64_t jan1_time = civil_second(jan1) - civil_second();
- int jan1_weekday = (static_cast<int>(get_weekday(jan1)) + 1) % 7;
- Transition* tr = &transitions_[hdr.timecnt]; // next trans to fill
- if (LocalTime(tr1->unix_time, *tt1).cs.year() != last_year_) {
- // Add a single extra transition to align to a calendar year.
- transitions_.resize(transitions_.size() + 1);
- assert(tr == &transitions_[hdr.timecnt]); // no reallocation
- const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start);
- std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1);
- tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset;
- tr++->type_index = tr1->type_index;
- tr0 = &transitions_[hdr.timecnt];
- tr1 = &transitions_[hdr.timecnt - 1];
- tt0 = &transition_types_[tr0->type_index];
- tt1 = &transition_types_[tr1->type_index];
- }
- const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start);
- const PosixTransition& pt0(tt0->is_dst ? posix.dst_start : posix.dst_end);
- for (const year_t limit = last_year_ + 400; last_year_ < limit;) {
- last_year_ += 1; // an additional year of generated transitions
+ const civil_second jan1(last_year_);
+ std::int_fast64_t jan1_time = jan1 - civil_second();
+ int jan1_weekday = ToPosixWeekday(get_weekday(jan1));
+
+ Transition dst = {0, dst_ti, civil_second(), civil_second()};
+ Transition std = {0, std_ti, civil_second(), civil_second()};
+ for (const year_t limit = last_year_ + 400;; ++last_year_) {
+ auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start);
+ auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end);
+ dst.unix_time = jan1_time + dst_trans_off - posix.std_offset;
+ std.unix_time = jan1_time + std_trans_off - posix.dst_offset;
+ const auto* ta = dst.unix_time < std.unix_time ? &dst : &std;
+ const auto* tb = dst.unix_time < std.unix_time ? &std : &dst;
+ if (last_time < tb->unix_time) {
+ if (last_time < ta->unix_time) transitions_.push_back(*ta);
+ transitions_.push_back(*tb);
+ }
+ if (last_year_ == limit) break;
jan1_time += kSecsPerYear[leap_year];
jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7;
- leap_year = !leap_year && IsLeap(last_year_);
- std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1);
- tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset;
- tr++->type_index = tr1->type_index;
- std::int_fast64_t tr0_offset = TransOffset(leap_year, jan1_weekday, pt0);
- tr->unix_time = jan1_time + tr0_offset - tt1->utc_offset;
- tr++->type_index = tr0->type_index;
- }
- assert(tr == &transitions_[0] + transitions_.size());
+ leap_year = !leap_year && IsLeap(last_year_ + 1);
+ }
+
+ return true;
}
-bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
+bool TimeZoneInfo::Load(ZoneInfoSource* zip) {
// Read and validate the header.
tzhead tzh;
if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false;
@@ -430,7 +425,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
const char* bp = tbuf.data();
// Decode and validate the transitions.
- transitions_.reserve(hdr.timecnt + 2); // We might add a couple.
+ transitions_.reserve(hdr.timecnt + 2);
transitions_.resize(hdr.timecnt);
for (std::size_t i = 0; i != hdr.timecnt; ++i) {
transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp);
@@ -449,6 +444,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
}
// Decode and validate the transition types.
+ transition_types_.reserve(hdr.typecnt + 2);
transition_types_.resize(hdr.typecnt);
for (std::size_t i = 0; i != hdr.typecnt; ++i) {
transition_types_[i].utc_offset =
@@ -475,6 +471,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
}
// Copy all the abbreviations.
+ abbreviations_.reserve(hdr.charcnt + 10);
abbreviations_.assign(bp, hdr.charcnt);
bp += hdr.charcnt;
@@ -506,7 +503,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
// If we did not find version information during the standard loading
// process (as of tzh_version '3' that is unsupported), then ask the
- // ZoneInfoSource for any out-of-bound version std::string it may be privy to.
+ // ZoneInfoSource for any out-of-bound version string it may be privy to.
if (version_.empty()) {
version_ = zip->Version();
}
@@ -525,19 +522,29 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
transitions_.resize(hdr.timecnt);
// Ensure that there is always a transition in the first half of the
- // time line (the second half is handled in ExtendTransitions()) so that
- // the signed difference between a civil_second and the civil_second of
- // its previous transition is always representable, without overflow.
- // A contemporary zic will usually have already done this for us.
+ // time line (the second half is handled below) so that the signed
+ // difference between a civil_second and the civil_second of its
+ // previous transition is always representable, without overflow.
if (transitions_.empty() || transitions_.front().unix_time >= 0) {
Transition& tr(*transitions_.emplace(transitions_.begin()));
- tr.unix_time = -(1LL << 59); // see tz/zic.c "BIG_BANG"
+ tr.unix_time = -(1LL << 59); // -18267312070-10-26T17:01:52+00:00
tr.type_index = default_transition_type_;
- hdr.timecnt += 1;
}
// Extend the transitions using the future specification.
- ExtendTransitions(name, hdr);
+ if (!ExtendTransitions()) return false;
+
+ // Ensure that there is always a transition in the second half of the
+ // time line (the first half is handled above) so that the signed
+ // difference between a civil_second and the civil_second of its
+ // previous transition is always representable, without overflow.
+ const Transition& last(transitions_.back());
+ if (last.unix_time < 0) {
+ const std::uint_fast8_t type_index = last.type_index;
+ Transition& tr(*transitions_.emplace(transitions_.end()));
+ tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00
+ tr.type_index = type_index;
+ }
// Compute the local civil time for each transition and the preceding
// second. These will be used for reverse conversions in MakeTime().
@@ -718,12 +725,12 @@ bool TimeZoneInfo::Load(const std::string& name) {
// Find and use a ZoneInfoSource to load the named zone.
auto zip = cctz_extension::zone_info_source_factory(
- name, [](const std::string& name) -> std::unique_ptr<ZoneInfoSource> {
- if (auto zip = FileZoneInfoSource::Open(name)) return zip;
- if (auto zip = AndroidZoneInfoSource::Open(name)) return zip;
+ name, [](const std::string& n) -> std::unique_ptr<ZoneInfoSource> {
+ if (auto z = FileZoneInfoSource::Open(n)) return z;
+ if (auto z = AndroidZoneInfoSource::Open(n)) return z;
return nullptr;
});
- return zip != nullptr && Load(name, zip.get());
+ return zip != nullptr && Load(zip.get());
}
// BreakTime() translation for a particular transition type.
@@ -897,8 +904,8 @@ bool TimeZoneInfo::NextTransition(const time_point<seconds>& tp,
const Transition* begin = &transitions_[0];
const Transition* end = begin + transitions_.size();
if (begin->unix_time <= -(1LL << 59)) {
- // Do not report the BIG_BANG found in recent zoneinfo data as it is
- // really a sentinel, not a transition. See tz/zic.c.
+ // Do not report the BIG_BANG found in some zoneinfo data as it is
+ // really a sentinel, not a transition. See pre-2018f tz/zic.c.
++begin;
}
std::int_fast64_t unix_time = ToUnixSeconds(tp);
@@ -923,8 +930,8 @@ bool TimeZoneInfo::PrevTransition(const time_point<seconds>& tp,
const Transition* begin = &transitions_[0];
const Transition* end = begin + transitions_.size();
if (begin->unix_time <= -(1LL << 59)) {
- // Do not report the BIG_BANG found in recent zoneinfo data as it is
- // really a sentinel, not a transition. See tz/zic.c.
+ // Do not report the BIG_BANG found in some zoneinfo data as it is
+ // really a sentinel, not a transition. See pre-2018f tz/zic.c.
++begin;
}
std::int_fast64_t unix_time = ToUnixSeconds(tp);
diff --git a/absl/time/internal/cctz/src/time_zone_info.h b/absl/time/internal/cctz/src/time_zone_info.h
index 2a10c06c..2467ff55 100644
--- a/absl/time/internal/cctz/src/time_zone_info.h
+++ b/absl/time/internal/cctz/src/time_zone_info.h
@@ -95,15 +95,14 @@ class TimeZoneInfo : public TimeZoneIf {
std::size_t DataLength(std::size_t time_len) const;
};
- void CheckTransition(const std::string& name, const TransitionType& tt,
- std::int_fast32_t offset, bool is_dst,
- const std::string& abbr) const;
+ bool GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
+ const std::string& abbr, std::uint_least8_t* index);
bool EquivTransitions(std::uint_fast8_t tt1_index,
std::uint_fast8_t tt2_index) const;
- void ExtendTransitions(const std::string& name, const Header& hdr);
+ bool ExtendTransitions();
bool ResetToBuiltinUTC(const seconds& offset);
- bool Load(const std::string& name, ZoneInfoSource* zip);
+ bool Load(ZoneInfoSource* zip);
// Helpers for BreakTime() and MakeTime().
time_zone::absolute_lookup LocalTime(std::int_fast64_t unix_time,
diff --git a/absl/time/internal/cctz/src/time_zone_libc.cc b/absl/time/internal/cctz/src/time_zone_libc.cc
index 47cf84c6..a14982a9 100644
--- a/absl/time/internal/cctz/src/time_zone_libc.cc
+++ b/absl/time/internal/cctz/src/time_zone_libc.cc
@@ -153,7 +153,8 @@ std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) {
std::tm tm;
while (lo + 1 != hi) {
const std::time_t mid = lo + (hi - lo) / 2;
- if (std::tm* tmp = local_time(&mid, &tm)) {
+ std::tm* tmp = local_time(&mid, &tm);
+ if (tmp != nullptr) {
if (tm_gmtoff(*tmp) == offset) {
hi = mid;
} else {
@@ -163,7 +164,8 @@ std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) {
// If std::tm cannot hold some result we resort to a linear search,
// ignoring all failed conversions. Slow, but never really happens.
while (++lo != hi) {
- if (std::tm* tmp = local_time(&lo, &tm)) {
+ tmp = local_time(&lo, &tm);
+ if (tmp != nullptr) {
if (tm_gmtoff(*tmp) == offset) break;
}
}
@@ -223,11 +225,10 @@ time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const {
civil_second() + ToUnixSeconds(time_point<seconds>::min());
static const civil_second max_tp_cs =
civil_second() + ToUnixSeconds(time_point<seconds>::max());
- const time_point<seconds> tp =
- (cs < min_tp_cs)
- ? time_point<seconds>::min()
- : (cs > max_tp_cs) ? time_point<seconds>::max()
- : FromUnixSeconds(cs - civil_second());
+ const time_point<seconds> tp = (cs < min_tp_cs) ? time_point<seconds>::min()
+ : (cs > max_tp_cs)
+ ? time_point<seconds>::max()
+ : FromUnixSeconds(cs - civil_second());
return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
}
diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
index 99137a08..9a1a8d6e 100644
--- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc
+++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -211,6 +211,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
+ "America/Nuuk",
"America/Ojinaga",
"America/Panama",
"America/Pangnirtung",
@@ -749,7 +750,7 @@ TEST(TimeZone, Failures) {
EXPECT_EQ(chrono::system_clock::from_time_t(0),
convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC
- // Loading an empty std::string timezone should fail.
+ // Loading an empty string timezone should fail.
tz = LoadZone("America/Los_Angeles");
EXPECT_FALSE(load_time_zone("", &tz));
EXPECT_EQ(chrono::system_clock::from_time_t(0),
@@ -932,7 +933,7 @@ TEST(MakeTime, Normalization) {
// NOTE: Run this with -ftrapv to detect overflow problems.
TEST(MakeTime, SysSecondsLimits) {
- const char RFC3339[] = "%Y-%m-%dT%H:%M:%S%Ez";
+ const char RFC3339[] = "%Y-%m-%d%ET%H:%M:%S%Ez";
const time_zone utc = utc_time_zone();
const time_zone east = fixed_time_zone(chrono::hours(14));
const time_zone west = fixed_time_zone(-chrono::hours(14));
@@ -1003,13 +1004,17 @@ TEST(MakeTime, SysSecondsLimits) {
#if defined(_WIN32) || defined(_WIN64)
// localtime_s() and gmtime_s() don't believe in years outside [1970:3000].
#else
- const time_zone utc = LoadZone("libc:UTC");
+ const time_zone cut = LoadZone("libc:UTC");
const year_t max_tm_year = year_t{std::numeric_limits<int>::max()} + 1900;
- tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), utc);
- EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, utc));
+ tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), cut);
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+ // The BSD gmtime_r() fails on extreme positive tm_year values.
+#else
+ EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, cut));
+#endif
const year_t min_tm_year = year_t{std::numeric_limits<int>::min()} + 1900;
- tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), utc);
- EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, utc));
+ tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), cut);
+ EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, cut));
#endif
}
}
diff --git a/absl/time/internal/cctz/src/tzfile.h b/absl/time/internal/cctz/src/tzfile.h
index 1ed55e0f..659f84cf 100644
--- a/absl/time/internal/cctz/src/tzfile.h
+++ b/absl/time/internal/cctz/src/tzfile.h
@@ -83,13 +83,13 @@ struct tzhead {
** If tzh_version is '2' or greater, the above is followed by a second instance
** of tzhead and a second instance of the data in which each coded transition
** time uses 8 rather than 4 chars,
-** then a POSIX-TZ-environment-variable-style std::string for use in handling
+** then a POSIX-TZ-environment-variable-style string for use in handling
** instants after the last transition time stored in the file
** (with nothing between the newlines if there is no POSIX representation for
** such instants).
**
** If tz_version is '3' or greater, the above is extended as follows.
-** First, the POSIX TZ std::string's hour offset may range from -167
+** First, the POSIX TZ string's hour offset may range from -167
** through 167 as compared to the POSIX-required 0 through 24.
** Second, its DST start time may be January 1 at 00:00 and its stop
** time December 31 at 24:00 plus the difference between DST and
@@ -108,15 +108,15 @@ struct tzhead {
#ifndef TZ_MAX_TYPES
/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */
#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
-#endif /* !defined TZ_MAX_TYPES */
+#endif /* !defined TZ_MAX_TYPES */
#ifndef TZ_MAX_CHARS
#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
- /* (limited by what unsigned chars can hold) */
-#endif /* !defined TZ_MAX_CHARS */
+/* (limited by what unsigned chars can hold) */
+#endif /* !defined TZ_MAX_CHARS */
#ifndef TZ_MAX_LEAPS
#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
-#endif /* !defined TZ_MAX_LEAPS */
+#endif /* !defined TZ_MAX_LEAPS */
#endif /* !defined TZFILE_H */
diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc
index 98ea1612..72095339 100644
--- a/absl/time/internal/cctz/src/zone_info_source.cc
+++ b/absl/time/internal/cctz/src/zone_info_source.cc
@@ -83,7 +83,8 @@ ZoneInfoSourceFactory default_factory = DefaultFactory;
"@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \
"@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \
"@@ZA")
-#elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM64)
+#elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM) || \
+ defined(_M_ARM64)
#pragma comment( \
linker, \
"/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \
diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version
index db18f831..7f680eec 100644
--- a/absl/time/internal/cctz/testdata/version
+++ b/absl/time/internal/cctz/testdata/version
@@ -1 +1 @@
-2019c
+2020a
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca
index 245f4ebe..d39016b8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun
index a91f65f2..066fbed0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Dawson b/absl/time/internal/cctz/testdata/zoneinfo/America/Dawson
index db9ceadd..2b6c3eea 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Dawson
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Dawson
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk
new file mode 100644
index 00000000..0160308b
--- /dev/null
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse
index fb3cd71a..062b58ce 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chongqing b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chongqing
index 3c0bef20..91f6f8bc 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chongqing
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chongqing
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chungking b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chungking
index 3c0bef20..91f6f8bc 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chungking
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Chungking
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Harbin b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Harbin
index 3c0bef20..91f6f8bc 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Harbin
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Harbin
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Shanghai b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Shanghai
index 3c0bef20..91f6f8bc 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Shanghai
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Shanghai
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon
index fb3cd71a..062b58ce 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/PRC b/absl/time/internal/cctz/testdata/zoneinfo/PRC
index 3c0bef20..91f6f8bc 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/PRC
+++ b/absl/time/internal/cctz/testdata/zoneinfo/PRC
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
index 822ffa1f..53ee77e8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
+++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
@@ -128,8 +128,8 @@ CA +4906-11631 America/Creston MST - BC (Creston)
CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John)
CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson)
CA +4916-12307 America/Vancouver Pacific - BC (most areas)
-CA +6043-13503 America/Whitehorse Pacific - Yukon (south)
-CA +6404-13925 America/Dawson Pacific - Yukon (north)
+CA +6043-13503 America/Whitehorse Pacific - Yukon (east)
+CA +6404-13925 America/Dawson Pacific - Yukon (west)
CC -1210+09655 Indian/Cocos
CH,DE,LI +4723+00832 Europe/Zurich Swiss time
CI,BF,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan
@@ -173,7 +173,7 @@ GE +4143+04449 Asia/Tbilisi
GF +0456-05220 America/Cayenne
GH +0533-00013 Africa/Accra
GI +3608-00521 Europe/Gibraltar
-GL +6411-05144 America/Godthab Greenland (most areas)
+GL +6411-05144 America/Nuuk Greenland (most areas)
GL +7646-01840 America/Danmarkshavn National Park (east coast)
GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit
GL +7634-06847 America/Thule Thule/Pituffik
@@ -290,7 +290,7 @@ RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade
RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad
RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area
# Mention RU and UA alphabetically. See "territorial claims" above.
-RU,UA +4457+03406 Europe/Simferopol MSK+00 - Crimea
+RU,UA +4457+03406 Europe/Simferopol Crimea
RU +5836+04939 Europe/Kirov MSK+00 - Kirov
RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan
RU +4844+04425 Europe/Volgograd MSK+01 - Volgograd
@@ -341,8 +341,8 @@ TT,AG,AI,BL,DM,GD,GP,KN,LC,MF,MS,VC,VG,VI +1039-06131 America/Port_of_Spain
TV -0831+17913 Pacific/Funafuti
TW +2503+12130 Asia/Taipei
UA +5026+03031 Europe/Kiev Ukraine (most areas)
-UA +4837+02218 Europe/Uzhgorod Ruthenia
-UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east)
+UA +4837+02218 Europe/Uzhgorod Transcarpathia
+UA +4750+03510 Europe/Zaporozhye Zaporozhye and east Lugansk
UM +1917+16637 Pacific/Wake Wake Island
US +404251-0740023 America/New_York Eastern (most areas)
US +421953-0830245 America/Detroit Eastern - MI (most areas)
diff --git a/absl/time/internal/test_util.cc b/absl/time/internal/test_util.cc
index 9bffe121..9a485a07 100644
--- a/absl/time/internal/test_util.cc
+++ b/absl/time/internal/test_util.cc
@@ -18,6 +18,7 @@
#include <cstddef>
#include <cstring>
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/time/internal/cctz/include/cctz/zone_info_source.h"