Add strptime %s.

Bug: https://issuetracker.google.com/37128336
Test: ran tests
Change-Id: I1a660dbdb5f008e42226f26489f0f01b6db18025
diff --git a/libc/tzcode/strptime.c b/libc/tzcode/strptime.c
index 2100630..0e9a7d3 100644
--- a/libc/tzcode/strptime.c
+++ b/libc/tzcode/strptime.c
@@ -38,7 +38,9 @@
 
 //#include <sys/localedef.h>
 #include <ctype.h>
+#include <errno.h>
 #include <locale.h>
+#include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include "tzfile.h"
@@ -128,7 +130,7 @@
             fmt++;
             continue;
         }
-                
+
         if ((c = *fmt++) != '%')
             goto literal;
 
@@ -154,7 +156,7 @@
             _LEGAL_ALT(0);
             alt_format |= _ALT_O;
             goto again;
-            
+
         /*
          * "Complex" conversion rules, implemented through recursion.
          */
@@ -169,7 +171,7 @@
             if (!(bp = _strptime(bp, "%m/%d/%y", tm, cr)))
                 return (NULL);
             break;
-    
+
         case 'R':   /* The time as "%H:%M". */
             _LEGAL_ALT(0);
             if (!(bp = _strptime(bp, "%H:%M", tm, cr)))
@@ -337,6 +339,25 @@
                 return (NULL);
             break;
 
+        case 's':
+            {
+                // Android addition, based on FreeBSD's implementation.
+                int saved_errno = errno;
+                errno = 0;
+                const unsigned char* old_bp = bp;
+                long n = strtol((const char*) bp, (char**) &bp, 10);
+                time_t t = n;
+                if (bp == old_bp || errno == ERANGE || ((long) t) != n) {
+                    errno = saved_errno;
+                    return NULL;
+                }
+                errno = saved_errno;
+
+                if (localtime_r(&t, tm) == NULL) return NULL;
+            }
+            break;
+
+
         case 'U':   /* The week of year, beginning on sunday. */
         case 'W':   /* The week of year, beginning on monday. */
             _LEGAL_ALT(_ALT_O);
diff --git a/tests/time_test.cpp b/tests/time_test.cpp
index 2b9935a..ff427a6 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -871,3 +871,54 @@
   ASSERT_EQ(buf, ctime_r(&t, buf));
   ASSERT_STREQ("Thu Jan  1 00:00:00 1970\n", buf);
 }
+
+// https://issuetracker.google.com/37128336
+TEST(time, strftime_strptime_s) {
+  char buf[32];
+  const struct tm tm0 = { .tm_year = 1982-1900, .tm_mon = 0, .tm_mday = 1 };
+
+  setenv("TZ", "America/Los_Angeles", 1);
+  strftime(buf, sizeof(buf), "<%s>", &tm0);
+  EXPECT_STREQ("<378720000>", buf);
+
+  setenv("TZ", "UTC", 1);
+  strftime(buf, sizeof(buf), "<%s>", &tm0);
+  EXPECT_STREQ("<378691200>", buf);
+
+  struct tm tm;
+
+  setenv("TZ", "America/Los_Angeles", 1);
+  tzset();
+  memset(&tm, 0xff, sizeof(tm));
+  char* p = strptime("378720000x", "%s", &tm);
+  ASSERT_EQ('x', *p);
+  EXPECT_EQ(0, tm.tm_sec);
+  EXPECT_EQ(0, tm.tm_min);
+  EXPECT_EQ(0, tm.tm_hour);
+  EXPECT_EQ(1, tm.tm_mday);
+  EXPECT_EQ(0, tm.tm_mon);
+  EXPECT_EQ(82, tm.tm_year);
+  EXPECT_EQ(5, tm.tm_wday);
+  EXPECT_EQ(0, tm.tm_yday);
+  EXPECT_EQ(0, tm.tm_isdst);
+
+  setenv("TZ", "UTC", 1);
+  tzset();
+  memset(&tm, 0xff, sizeof(tm));
+  p = strptime("378691200x", "%s", &tm);
+  ASSERT_EQ('x', *p);
+  EXPECT_EQ(0, tm.tm_sec);
+  EXPECT_EQ(0, tm.tm_min);
+  EXPECT_EQ(0, tm.tm_hour);
+  EXPECT_EQ(1, tm.tm_mday);
+  EXPECT_EQ(0, tm.tm_mon);
+  EXPECT_EQ(82, tm.tm_year);
+  EXPECT_EQ(5, tm.tm_wday);
+  EXPECT_EQ(0, tm.tm_yday);
+  EXPECT_EQ(0, tm.tm_isdst);
+}
+
+TEST(time, strptime_s_nothing) {
+  struct tm tm;
+  ASSERT_EQ(nullptr, strptime("x", "%s", &tm));
+}