Incorporate upstream's strptime %Z/%z support.

I made toybox use strptime %Z recently (so that it can parse the default
POSIX date(1) output), forgetting that bionic's strptime(3) doesn't
support %Z. Neither does glibc, for that matter; the toybox change works
on glibc effectively by accident --- glibc just ignores the next word
when parsing %Z and assumes that the current time zone ($TZ) is
appropriate. Which it is for the only obvious use case of "round trip
date(1) output".

The related %z is potentially quite a bit more useful in general (parsing
any valid RFC822 time zone), though sadly not useful for the toybox case
that prompted.

Every time I touch this file I promise that I'll actually get us back in
sync with upstream, and every time I fail to get round to it. Maybe
2020 or 2021 will finally be the year...

Also add corresponding tests.

Bug: https://b/167455975
Test: treehugger
Change-Id: I13a7fb7e3ad01ae855750b9314d2eec661fe034f
diff --git a/tests/time_test.cpp b/tests/time_test.cpp
index 3d745ea..b1de0a4 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -363,6 +363,105 @@
   EXPECT_TRUE(memcmp(&tm, &zero, sizeof(tm)) == 0);
 }
 
+TEST(time, strptime_Z) {
+#if defined(__BIONIC__)
+  // glibc doesn't handle %Z at all.
+  // The BSDs only handle hard-coded "GMT" and "UTC", plus whatever two strings
+  // are in the global `tzname` (which correspond to the current $TZ).
+  struct tm tm;
+  setenv("TZ", "Europe/Berlin", 1);
+
+  // "GMT" always works.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("GMT", "%Z", &tm));
+  EXPECT_STREQ("GMT", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(0, tm.tm_gmtoff);
+
+  // As does "UTC".
+  tm = {};
+  ASSERT_EQ('\0', *strptime("UTC", "%Z", &tm));
+  EXPECT_STREQ("UTC", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(0, tm.tm_gmtoff);
+
+  // Europe/Berlin is known as "CET" when there's no DST.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("CET", "%Z", &tm));
+  EXPECT_STREQ("CET", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(3600, tm.tm_gmtoff);
+
+  // Europe/Berlin is known as "CEST" when there's no DST.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("CEST", "%Z", &tm));
+  EXPECT_STREQ("CEST", tm.tm_zone);
+  EXPECT_EQ(1, tm.tm_isdst);
+  EXPECT_EQ(3600, tm.tm_gmtoff);
+
+  // And as long as we're in Europe/Berlin, those are the only time zone
+  // abbreviations that are recognized.
+  tm = {};
+  ASSERT_TRUE(strptime("PDT", "%Z", &tm) == nullptr);
+#endif
+}
+
+TEST(time, strptime_z) {
+  struct tm tm;
+  setenv("TZ", "Europe/Berlin", 1);
+
+  // "UT" is what RFC822 called UTC.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("UT", "%z", &tm));
+  EXPECT_STREQ("UTC", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(0, tm.tm_gmtoff);
+  // "GMT" is RFC822's other name for UTC.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("GMT", "%z", &tm));
+  EXPECT_STREQ("UTC", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(0, tm.tm_gmtoff);
+
+  // "Z" ("Zulu") is a synonym for UTC.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("Z", "%z", &tm));
+  EXPECT_STREQ("UTC", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(0, tm.tm_gmtoff);
+
+  // "PST"/"PDT" and the other common US zone abbreviations are all supported.
+  tm = {};
+  ASSERT_EQ('\0', *strptime("PST", "%z", &tm));
+  EXPECT_STREQ("PST", tm.tm_zone);
+  EXPECT_EQ(0, tm.tm_isdst);
+  EXPECT_EQ(-28800, tm.tm_gmtoff);
+  tm = {};
+  ASSERT_EQ('\0', *strptime("PDT", "%z", &tm));
+  EXPECT_STREQ("PDT", tm.tm_zone);
+  EXPECT_EQ(1, tm.tm_isdst);
+  EXPECT_EQ(-25200, tm.tm_gmtoff);
+
+  // +-hh
+  tm = {};
+  ASSERT_EQ('\0', *strptime("+01", "%z", &tm));
+  EXPECT_EQ(3600, tm.tm_gmtoff);
+  EXPECT_TRUE(tm.tm_zone == nullptr);
+  EXPECT_EQ(0, tm.tm_isdst);
+  // +-hhmm
+  tm = {};
+  ASSERT_EQ('\0', *strptime("+0130", "%z", &tm));
+  EXPECT_EQ(5400, tm.tm_gmtoff);
+  EXPECT_TRUE(tm.tm_zone == nullptr);
+  EXPECT_EQ(0, tm.tm_isdst);
+  // +-hh:mm
+  tm = {};
+  ASSERT_EQ('\0', *strptime("+01:30", "%z", &tm));
+  EXPECT_EQ(5400, tm.tm_gmtoff);
+  EXPECT_TRUE(tm.tm_zone == nullptr);
+  EXPECT_EQ(0, tm.tm_isdst);
+}
+
 void SetTime(timer_t t, time_t value_s, time_t value_ns, time_t interval_s, time_t interval_ns) {
   itimerspec ts;
   ts.it_value.tv_sec = value_s;