From a9cac4c87a4ea3e6d1c0a1159909bf0e209a61a7 Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Thu, 12 Nov 2015 16:51:31 -0800 Subject: [PATCH] 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 --- libc/Android.mk | 3 +++ libc/tzcode/strftime.c | 18 +++++++++++++++++- tests/time_test.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/libc/Android.mk b/libc/Android.mk index 06dcc668b..a89fe230b 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -725,6 +725,7 @@ LOCAL_CFLAGS := $(libc_common_cflags) \ 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 += -DTM_GMTOFF=tm_gmtoff 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 10dfb4b6c..4349cf607 100644 --- a/libc/tzcode/strftime.c +++ b/libc/tzcode/strftime.c @@ -502,7 +502,23 @@ label: 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 c685e9478..a04c44976 100644 --- a/tests/time_test.cpp +++ b/tests/time_test.cpp @@ -143,6 +143,44 @@ TEST(time, strftime) { 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("", 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("", 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("", 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);