Merge "strptime: support everything that strftime supports."
am: ff923681be

Change-Id: I39dd975a674c115e2fc9212c1bd967a6aefd6e2b
diff --git a/docs/status.md b/docs/status.md
index 0cbcb47..d6a2f4c 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -45,14 +45,17 @@
   * `getloadavg` (BSD/GNU extension in <stdlib.h>)
 
 New libc behavior in Q (API level 29):
-  * Whole printf family now supports the GNU `%m` extension, rather than a special-case hack in `syslog`
-  * `popen` now always uses `O_CLOEXEC`, not just with the `e` extension
-  * Bug fixes to handling of UTF-8 U+fffe/U+ffff and code points above U+10ffff
-  * `aligned_alloc` correctly verifies that `size` is a multiple of `alignment`
+  * Whole printf family now supports the GNU `%m` extension, rather than a
+    special-case hack in `syslog`.
+  * `popen` now always uses `O_CLOEXEC`, not just with the `e` extension.
+  * Bug fixes to handling of UTF-8 U+fffe/U+ffff and code points above U+10ffff.
+  * `aligned_alloc` correctly verifies that `size` is a multiple of `alignment`.
   * Using `%n` with the printf family is now reported as a FORTIFY failure.
     Previous versions of Android would ignore the `%n` but not consume the
     corresponding pointer argument, leading to obscure errors. The scanf family
     is unchanged.
+  * Support in strptime for `%F`, `%G`, `%g`, `%P`, `%u`, `%V`, and `%v`.
+    (strftime already supported them all.)
   * [fdsan](fdsan.md) detects common file descriptor errors at runtime.
 
 New libc functions in P (API level 28):
@@ -74,9 +77,9 @@
   * `syncfs`
 
 New libc behavior in P (API level 28):
-  * `%C` and `%S` support in the printf family (previously only the wprintf family supported these)
-  * `%mc`/`%ms`/`%m[` support in the scanf family
-  * `%s` support in strptime (strftime already supported it)
+  * `%C` and `%S` support in the printf family (previously only the wprintf family supported these).
+  * `%mc`/`%ms`/`%m[` support in the scanf family.
+  * `%s` support in strptime (strftime already supported it).
   * Using a `pthread_mutex_t` after it's been destroyed will be detected at
     runtime and reported as a FORTIFY failure.
   * Passing a null `FILE*` or `DIR*` to libc is now detected at runtime and
diff --git a/libc/tzcode/strptime.c b/libc/tzcode/strptime.c
index 0e9a7d3..41eaa9b 100644
--- a/libc/tzcode/strptime.c
+++ b/libc/tzcode/strptime.c
@@ -172,6 +172,12 @@
                 return (NULL);
             break;
 
+        case 'F':  /* The date as "%Y-%m-%d". */
+            _LEGAL_ALT(0);
+            if (!(bp = _strptime(bp, "%Y-%m-%d", tm, cr)))
+                return (NULL);
+            continue;
+
         case 'R':   /* The time as "%H:%M". */
             _LEGAL_ALT(0);
             if (!(bp = _strptime(bp, "%H:%M", tm, cr)))
@@ -190,6 +196,12 @@
                 return (NULL);
             break;
 
+        case 'v':  /* The date as "%e-%b-%Y". */
+            _LEGAL_ALT(0);
+            if (!(bp = _strptime(bp, "%e-%b-%Y", tm, cr)))
+                return (NULL);
+            break;
+
         case 'X':   /* The time, using the locale's format. */
             _LEGAL_ALT(_ALT_E);
             if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, cr)))
@@ -305,6 +317,7 @@
             tm->tm_mon--;
             break;
 
+        case 'P':
         case 'p':   /* The locale's equivalent of AM/PM. */
             _LEGAL_ALT(0);
             /* AM? */
@@ -377,6 +390,33 @@
                 return (NULL);
             break;
 
+        case 'u':  /* The day of week, monday = 1. */
+            _LEGAL_ALT(_ALT_O);
+            if (!(_conv_num(&bp, &i, 1, 7)))
+                return (NULL);
+            tm->tm_wday = i % 7;
+            continue;
+
+        case 'g':  /* The year corresponding to the ISO week
+                    * number but without the century.
+                    */
+            if (!(_conv_num(&bp, &i, 0, 99)))
+                return (NULL);
+            continue;
+
+        case 'G':  /* The year corresponding to the ISO week
+                    * number with century.
+                    */
+            do
+                bp++;
+            while (isdigit(*bp));
+            continue;
+
+        case 'V':  /* The ISO 8601:1988 week number as decimal */
+            if (!(_conv_num(&bp, &i, 0, 53)))
+                return (NULL);
+            continue;
+
         case 'Y':   /* The year. */
             _LEGAL_ALT(_ALT_E);
             if (!(_conv_num(&bp, &i, 0, 9999)))
diff --git a/tests/time_test.cpp b/tests/time_test.cpp
index 637fa85..8653d91 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -296,6 +296,59 @@
   EXPECT_STREQ("09:41:53", buf);
 }
 
+TEST(time, strptime_F) {
+  setenv("TZ", "UTC", 1);
+
+  struct tm tm = {};
+  ASSERT_EQ('\0', *strptime("2019-03-26", "%F", &tm));
+  EXPECT_EQ(119, tm.tm_year);
+  EXPECT_EQ(2, tm.tm_mon);
+  EXPECT_EQ(26, tm.tm_mday);
+}
+
+TEST(time, strptime_P_p) {
+  setenv("TZ", "UTC", 1);
+
+  struct tm tm = {.tm_hour = 12};
+  ASSERT_EQ('\0', *strptime("AM", "%p", &tm));
+  EXPECT_EQ(0, tm.tm_hour);
+
+  tm = {.tm_hour = 12};
+  ASSERT_EQ('\0', *strptime("AM", "%P", &tm));
+  EXPECT_EQ(0, tm.tm_hour);
+}
+
+TEST(time, strptime_u) {
+  setenv("TZ", "UTC", 1);
+
+  struct tm tm = {};
+  ASSERT_EQ('\0', *strptime("2", "%u", &tm));
+  EXPECT_EQ(2, tm.tm_wday);
+}
+
+TEST(time, strptime_v) {
+  setenv("TZ", "UTC", 1);
+
+  struct tm tm = {};
+  ASSERT_EQ('\0', *strptime("26-Mar-1980", "%v", &tm));
+  EXPECT_EQ(80, tm.tm_year);
+  EXPECT_EQ(2, tm.tm_mon);
+  EXPECT_EQ(26, tm.tm_mday);
+}
+
+TEST(time, strptime_V_G_g) {
+  setenv("TZ", "UTC", 1);
+
+  // %V (ISO-8601 week number), %G (year of week number, without century), and
+  // %g (year of week number) have no effect when parsed, and are supported
+  // solely so that it's possible for strptime(3) to parse everything that
+  // strftime(3) can output.
+  struct tm tm = {};
+  ASSERT_EQ('\0', *strptime("1 2 3", "%V %G %g", &tm));
+  struct tm zero = {};
+  EXPECT_TRUE(memcmp(&tm, &zero, sizeof(tm)) == 0);
+}
+
 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;