Fix strftime if tm_zone is null.
Upstream tzcode said "On platforms with tm_zone, strftime.c now assumes it
is not NULL". Which is fine for any struct tm generated by tzcode, but not
necessarily true of a struct tm constructed by arbitrary code. In particular,
Netflix on Nexus Player was failing to start because they format "%Z" with
a struct tm whose tm_zone is null (the other fields are valid, but, yeah,
that's probably not intentional).
glibc takes a null tm_zone to mean "the current time zone", so let's do that
too. (Historically Android would use the empty string, and POSIX doesn't
clarify which of this is the appropriate behavior when tm_zone is null.)
Bug: http://b/25170306
Change-Id: Idbf68bfe90d143aca7dada8607742905188b1d33
diff --git a/libc/Android.mk b/libc/Android.mk
index 06dcc66..a89fe23 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -725,6 +725,7 @@
LOCAL_CFLAGS += -DALL_STATE
# Include tzsetwall, timelocal, timegm, time2posix, and posix2time.
LOCAL_CFLAGS += -DSTD_INSPIRED
+# Obviously, we want to be thread-safe.
LOCAL_CFLAGS += -DTHREAD_SAFE
# The name of the tm_gmtoff field in our struct tm.
LOCAL_CFLAGS += -DTM_GMTOFF=tm_gmtoff
@@ -732,6 +733,8 @@
LOCAL_CFLAGS += -DTZDIR=\"/system/usr/share/zoneinfo\"
# Include timezone and daylight globals.
LOCAL_CFLAGS += -DUSG_COMPAT=1
+# Use the empty string (instead of " ") as the timezone abbreviation fallback.
+LOCAL_CFLAGS += -DWILDABBR=\"\"
LOCAL_CFLAGS += -DNO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
LOCAL_CFLAGS += -Dlint
diff --git a/libc/tzcode/strftime.c b/libc/tzcode/strftime.c
index 10dfb4b..4349cf6 100644
--- a/libc/tzcode/strftime.c
+++ b/libc/tzcode/strftime.c
@@ -502,7 +502,23 @@
continue;
case 'Z':
#ifdef TM_ZONE
- pt = _add(t->TM_ZONE, pt, ptlim, modifier);
+ // BEGIN: Android-changed.
+ {
+ const char* zone = t->TM_ZONE;
+ if (!zone || !*zone) {
+ // "The value of tm_isdst shall be positive if Daylight Savings Time is
+ // in effect, 0 if Daylight Savings Time is not in effect, and negative
+ // if the information is not available."
+ if (t->tm_isdst == 0) zone = tzname[0];
+ else if (t->tm_isdst > 0) zone = tzname[1];
+
+ // "Replaced by the timezone name or abbreviation, or by no bytes if no
+ // timezone information exists."
+ if (!zone || !*zone) zone = "";
+ }
+ pt = _add(zone, pt, ptlim, modifier);
+ }
+ // END: Android-changed.
#else
if (t->tm_isdst >= 0)
pt = _add(tzname[t->tm_isdst != 0],
diff --git a/tests/time_test.cpp b/tests/time_test.cpp
index c685e94..a04c449 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -143,6 +143,44 @@
EXPECT_STREQ("Sun Mar 10 00:00:00 2100", buf);
}
+TEST(time, strftime_null_tm_zone) {
+ // Netflix on Nexus Player wouldn't start (http://b/25170306).
+ struct tm t;
+ memset(&t, 0, sizeof(tm));
+
+ char buf[64];
+
+ setenv("TZ", "America/Los_Angeles", 1);
+ tzset();
+
+ t.tm_isdst = 0; // "0 if Daylight Savings Time is not in effect".
+ EXPECT_EQ(5U, strftime(buf, sizeof(buf), "<%Z>", &t));
+ EXPECT_STREQ("<PST>", buf);
+
+#if defined(__BIONIC__) // glibc 2.19 only copes with tm_isdst being 0 and 1.
+ t.tm_isdst = 2; // "positive if Daylight Savings Time is in effect"
+ EXPECT_EQ(5U, strftime(buf, sizeof(buf), "<%Z>", &t));
+ EXPECT_STREQ("<PDT>", buf);
+
+ t.tm_isdst = -123; // "and negative if the information is not available".
+ EXPECT_EQ(2U, strftime(buf, sizeof(buf), "<%Z>", &t));
+ EXPECT_STREQ("<>", buf);
+#endif
+
+ setenv("TZ", "UTC", 1);
+ tzset();
+
+ t.tm_isdst = 0;
+ EXPECT_EQ(5U, strftime(buf, sizeof(buf), "<%Z>", &t));
+ EXPECT_STREQ("<UTC>", buf);
+
+#if defined(__BIONIC__) // glibc 2.19 thinks UTC DST is "UTC".
+ t.tm_isdst = 1; // UTC has no DST.
+ EXPECT_EQ(2U, strftime(buf, sizeof(buf), "<%Z>", &t));
+ EXPECT_STREQ("<>", buf);
+#endif
+}
+
TEST(time, strptime) {
setenv("TZ", "UTC", 1);