Merge "Expose tzalloc()/localtime_rz()/mktime_z()/tzfree()."
diff --git a/docs/status.md b/docs/status.md
index 411b140..308c354 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -57,6 +57,11 @@
 
 New libc functions in V (API level 35):
   * `timespec_getres` (C23 addition).
+  * `localtime_rz`, `mktime_z`, `tzalloc`, and `tzfree` (NetBSD
+    extensions implemented in tzcode, and the "least non-standard"
+    functions for avoiding $TZ if you need to use multiple time zones in
+    multi-threaded C).
+  * `mbsrtowcs_l` and `wcsrtombs_l` aliases for `mbsrtowcs` and `wcsrtombs`.
 
 New libc functions in U (API level 34):
   * `close_range` and `copy_file_range` (Linux-specific GNU extensions).
diff --git a/libc/Android.bp b/libc/Android.bp
index 1e2458a..ffa8222 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -252,10 +252,11 @@
     srcs: [
         "tzcode/**/*.c",
         "tzcode/bionic.cpp",
-        // tzcode doesn't include strptime or wcsftime, so we use the OpenBSD
-        // code (with some local changes in the strptime case).
+        // tzcode doesn't include strptime, so we use a fork of the
+        // OpenBSD code which needs this global data.
         "upstream-openbsd/lib/libc/locale/_def_time.c",
-        "upstream-openbsd/lib/libc/time/wcsftime.c",
+        // tzcode doesn't include wcsftime, so we use the FreeBSD code.
+        "upstream-freebsd/lib/libc/locale/wcsftime.c",
     ],
 
     cflags: [
@@ -284,7 +285,10 @@
         "-Dlint",
     ],
 
-    local_include_dirs: ["tzcode/"],
+    local_include_dirs: [
+        "tzcode/",
+        "upstream-freebsd/android/include",
+    ],
     name: "libc_tzcode",
 }
 
diff --git a/libc/NOTICE b/libc/NOTICE
index 441e79c..daea7bc 100644
--- a/libc/NOTICE
+++ b/libc/NOTICE
@@ -63,38 +63,6 @@
 
 -------------------------------------------------------------------
 
-Based on the UCB version with the ID appearing below.
-This is ANSIish only when "multibyte character == plain character".
-
-Copyright (c) 1989, 1993
-   The Regents of the University of California.  All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-1. Redistributions of source code must retain the above copyright
-   notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
-   notice, this list of conditions and the following disclaimer in the
-   documentation and/or other materials provided with the distribution.
-3. Neither the name of the University nor the names of its contributors
-   may be used to endorse or promote products derived from this software
-   without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-
--------------------------------------------------------------------
-
 Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
 All rights reserved.
 
@@ -3242,6 +3210,37 @@
 Copyright (c) 2002 Tim J. Robbins
 All rights reserved.
 
+Copyright (c) 2011 The FreeBSD Foundation
+
+Portions of this software were developed by David Chisnall
+under sponsorship from the FreeBSD Foundation.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
+Copyright (c) 2002 Tim J. Robbins
+All rights reserved.
+
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
diff --git a/libc/bionic/wchar.cpp b/libc/bionic/wchar.cpp
index bd9a45e..bb97b3e 100644
--- a/libc/bionic/wchar.cpp
+++ b/libc/bionic/wchar.cpp
@@ -135,6 +135,7 @@
 size_t mbsrtowcs(wchar_t* dst, const char** src, size_t len, mbstate_t* ps) {
   return mbsnrtowcs(dst, src, SIZE_MAX, len, ps);
 }
+__strong_alias(mbsrtowcs_l, mbsrtowcs);
 
 size_t wcrtomb(char* s, wchar_t wc, mbstate_t* ps) {
   static mbstate_t __private_state;
@@ -210,3 +211,4 @@
 size_t wcsrtombs(char* dst, const wchar_t** src, size_t len, mbstate_t* ps) {
   return wcsnrtombs(dst, src, SIZE_MAX, len, ps);
 }
+__strong_alias(wcsrtombs_l, wcsrtombs);
diff --git a/libc/bionic/wchar_l.cpp b/libc/bionic/wchar_l.cpp
index a86961f..1e7a231 100644
--- a/libc/bionic/wchar_l.cpp
+++ b/libc/bionic/wchar_l.cpp
@@ -41,14 +41,6 @@
   return wcscoll(ws1, ws2);
 }
 
-size_t wcsftime_l(wchar_t* buf, size_t n, const wchar_t* fmt, const struct tm* tm, locale_t) {
-  return wcsftime(buf, n, fmt, tm);
-}
-
-size_t wcsxfrm_l(wchar_t* dst, const wchar_t* src, size_t n, locale_t) {
-  return wcsxfrm(dst, src, n);
-}
-
 double wcstod_l(const wchar_t* s, wchar_t** end_ptr, locale_t) {
   return wcstod(s, end_ptr);
 }
@@ -76,3 +68,7 @@
 long double wcstold_l(const wchar_t* s, wchar_t** end_ptr, locale_t) {
   return wcstold(s, end_ptr);
 }
+
+size_t wcsxfrm_l(wchar_t* dst, const wchar_t* src, size_t n, locale_t) {
+  return wcsxfrm(dst, src, n);
+}
diff --git a/libc/include/time.h b/libc/include/time.h
index 6bf31bc..bd3fac1 100644
--- a/libc/include/time.h
+++ b/libc/include/time.h
@@ -39,6 +39,12 @@
 
 __BEGIN_DECLS
 
+/* If we just use void* in the typedef, the compiler exposes that in error messages. */
+struct __timezone_t;
+
+/** The `timezone_t` type that represents a time zone. */
+typedef struct __timezone_t* timezone_t;
+
 /** Divisor to compute seconds from the result of a call to clock(). */
 #define CLOCKS_PER_SEC 1000000
 
@@ -139,11 +145,24 @@
  * [mktime(3)](http://man7.org/linux/man-pages/man3/mktime.3p.html) converts
  * broken-down time `tm` into the number of seconds since the Unix epoch.
  *
+ * See tzset() for details of how the time zone is set, and mktime_rz()
+ * for an alternative.
+ *
  * Returns the time in seconds on success, and returns -1 and sets `errno` on failure.
  */
 time_t mktime(struct tm* _Nonnull __tm);
 
 /**
+ * mktime_z(3) converts broken-down time `tm` into the number of seconds
+ * since the Unix epoch, assuming the given time zone.
+ *
+ * Returns the time in seconds on success, and returns -1 and sets `errno` on failure.
+ *
+ * Available since API level 35.
+ */
+time_t mktime_z(timezone_t _Nonnull __tz, struct tm* _Nonnull __tm) __INTRODUCED_IN(35);
+
+/**
  * [localtime(3)](http://man7.org/linux/man-pages/man3/localtime.3p.html) converts
  * the number of seconds since the Unix epoch in `t` to a broken-down time, taking
  * the device's timezone into account.
@@ -159,11 +178,25 @@
  * the number of seconds since the Unix epoch in `t` to a broken-down time.
  * That broken-down time will be written to the given struct `tm`.
  *
+ * See tzset() for details of how the time zone is set, and localtime_rz()
+ * for an alternative.
+ *
  * Returns a pointer to a broken-down time on success, and returns null and sets `errno` on failure.
  */
 struct tm* _Nullable localtime_r(const time_t* _Nonnull __t, struct tm* _Nonnull __tm);
 
 /**
+ * localtime_rz(3) converts the number of seconds since the Unix epoch in
+ * `t` to a broken-down time, assuming the given time zone. That broken-down
+ * time will be written to the given struct `tm`.
+ *
+ * Returns a pointer to a broken-down time on success, and returns null and sets `errno` on failure.
+ *
+ * Available since API level 35.
+ */
+struct tm* _Nullable localtime_rz(timezone_t _Nonnull __tz, const time_t* _Nonnull __t, struct tm* _Nonnull __tm) __INTRODUCED_IN(35);
+
+/**
  * Inverse of localtime().
  */
 time_t timelocal(struct tm* _Nonnull __tm);
@@ -246,10 +279,34 @@
 /**
  * [tzset(3)](http://man7.org/linux/man-pages/man3/tzset.3.html) tells
  * libc that the time zone has changed.
+ *
+ * Android looks at both the system property `persist.sys.timezone` and the
+ * environment variable `TZ`. The former is the device's current time zone
+ * as shown in Settings, while the latter is usually unset but can be used
+ * to override the global setting. This is a bad idea outside of unit tests
+ * or single-threaded programs because it's inherently thread-unsafe.
+ * See tzalloc(), localtime_rz(), mktime_z(), and tzfree() for an
+ * alternative.
  */
 void tzset(void);
 
 /**
+ * tzalloc(3) allocates a time zone corresponding to the given Olson id.
+ *
+ * Returns a time zone object on success, and returns NULL and sets `errno` on failure.
+ *
+ * Available since API level 35.
+ */
+timezone_t _Nullable tzalloc(const char* _Nullable __id) __INTRODUCED_IN(35);
+
+/**
+ * tzfree(3) frees a time zone object returned by tzalloc().
+ *
+ * Available since API level 35.
+ */
+void tzfree(timezone_t _Nullable __tz) __INTRODUCED_IN(35);
+
+/**
  * [clock(3)](http://man7.org/linux/man-pages/man3/clock.3.html)
  * returns an approximation of CPU time used, equivalent to
  * `clock_gettime(CLOCK_PROCESS_CPUTIME_ID)` but with more confusing
diff --git a/libc/include/wchar.h b/libc/include/wchar.h
index cd09a19..1060d97 100644
--- a/libc/include/wchar.h
+++ b/libc/include/wchar.h
@@ -57,6 +57,7 @@
 size_t mbrlen(const char* _Nullable __s, size_t __n, mbstate_t* _Nullable __ps);
 size_t mbrtowc(wchar_t* _Nullable __buf, const char* _Nullable __s, size_t __n, mbstate_t* _Nullable __ps);
 size_t mbsrtowcs(wchar_t* _Nullable __dst, const char* _Nullable * _Nonnull __src, size_t __dst_n, mbstate_t* _Nullable __ps);
+size_t mbsrtowcs_l(wchar_t* _Nullable __dst, const char* _Nullable * _Nonnull __src, size_t __dst_n, mbstate_t* _Nullable __ps, locale_t _Nonnull __l) __INTRODUCED_IN(35);
 size_t mbsnrtowcs(wchar_t* _Nullable __dst, const char* _Nullable * _Nullable  __src, size_t __src_n, size_t __dst_n, mbstate_t* _Nullable __ps) __INTRODUCED_IN(21);
 wint_t putwc(wchar_t __wc, FILE* _Nonnull __fp);
 wint_t putwchar(wchar_t __wc);
@@ -92,6 +93,7 @@
 wchar_t* _Nullable wcspbrk(const wchar_t* _Nonnull __s, const wchar_t* _Nonnull __accept);
 wchar_t* _Nullable wcsrchr(const wchar_t* _Nonnull __s, wchar_t __wc);
 size_t wcsrtombs(char* _Nullable __dst, const wchar_t* __BIONIC_COMPLICATED_NULLNESS * _Nullable __src, size_t __dst_n, mbstate_t* _Nullable __ps);
+size_t wcsrtombs_l(char* _Nullable __dst, const wchar_t* __BIONIC_COMPLICATED_NULLNESS * _Nullable __src, size_t __dst_n, mbstate_t* _Nullable __ps, locale_t _Nonnull __l) __INTRODUCED_IN(35);
 size_t wcsspn(const wchar_t* _Nonnull __s, const wchar_t* _Nonnull __accept);
 wchar_t* _Nullable wcsstr(const wchar_t* _Nonnull __haystack, const wchar_t* _Nonnull __needle);
 double wcstod(const wchar_t* _Nonnull __s, wchar_t* __BIONIC_COMPLICATED_NULLNESS * _Nullable __end_ptr);
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 0102b30..17141dc 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1586,7 +1586,13 @@
 
 LIBC_V { # introduced=VanillaIceCream
   global:
+    localtime_rz;
+    mbsrtowcs_l;
+    mktime_z;
     timespec_getres;
+    tzalloc;
+    tzfree;
+    wcsrtombs_l;
 } LIBC_U;
 
 LIBC_PRIVATE {
diff --git a/libc/tzcode/strptime.c b/libc/tzcode/strptime.c
index d31a501..ae7881e 100644
--- a/libc/tzcode/strptime.c
+++ b/libc/tzcode/strptime.c
@@ -28,6 +28,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "private.h"
+
 #include <ctype.h>
 #include <errno.h>
 #include <limits.h>
@@ -37,7 +39,6 @@
 #include <time.h>
 
 #include "localedef.h"
-#include "private.h"
 #include "tzfile.h"
 
 // Android: ignore OpenBSD's DEF_WEAK() stuff.
diff --git a/libc/upstream-freebsd/android/include/xlocale_private.h b/libc/upstream-freebsd/android/include/xlocale_private.h
new file mode 100644
index 0000000..010d70c
--- /dev/null
+++ b/libc/upstream-freebsd/android/include/xlocale_private.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <locale.h>
+
+#define __get_locale() LC_GLOBAL_LOCALE
+
+#define FIX_LOCALE(__l) /* Nothing. */
diff --git a/libc/upstream-freebsd/lib/libc/locale/wcsftime.c b/libc/upstream-freebsd/lib/libc/locale/wcsftime.c
new file mode 100644
index 0000000..aabb632
--- /dev/null
+++ b/libc/upstream-freebsd/lib/libc/locale/wcsftime.c
@@ -0,0 +1,124 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2002 Tim J. Robbins
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wchar.h>
+#include "xlocale_private.h"
+
+/*
+ * Convert date and time to a wide-character string.
+ *
+ * This is the wide-character counterpart of strftime(). So that we do not
+ * have to duplicate the code of strftime(), we convert the format string to
+ * multibyte, call strftime(), then convert the result back into wide
+ * characters.
+ *
+ * This technique loses in the presence of stateful multibyte encoding if any
+ * of the conversions in the format string change conversion state. When
+ * stateful encoding is implemented, we will need to reset the state between
+ * format specifications in the format string.
+ */
+size_t
+wcsftime_l(wchar_t * __restrict wcs, size_t maxsize,
+	const wchar_t * __restrict format, const struct tm * __restrict timeptr,
+	locale_t locale)
+{
+	static const mbstate_t initial;
+	mbstate_t mbs;
+	char *dst, *sformat;
+	const char *dstp;
+	const wchar_t *formatp;
+	size_t n, sflen;
+	int sverrno;
+	FIX_LOCALE(locale);
+
+	sformat = dst = NULL;
+
+	/*
+	 * Convert the supplied format string to a multibyte representation
+	 * for strftime(), which only handles single-byte characters.
+	 */
+	mbs = initial;
+	formatp = format;
+	sflen = wcsrtombs_l(NULL, &formatp, 0, &mbs, locale);
+	if (sflen == (size_t)-1)
+		goto error;
+	if ((sformat = malloc(sflen + 1)) == NULL)
+		goto error;
+	mbs = initial;
+	wcsrtombs_l(sformat, &formatp, sflen + 1, &mbs, locale);
+
+	/*
+	 * Allocate memory for longest multibyte sequence that will fit
+	 * into the caller's buffer and call strftime() to fill it.
+	 * Then, copy and convert the result back into wide characters in
+	 * the caller's buffer.
+	 */
+	if (SIZE_T_MAX / MB_CUR_MAX <= maxsize) {
+		/* maxsize is prepostorously large - avoid int. overflow. */
+		errno = EINVAL;
+		goto error;
+	}
+	if ((dst = malloc(maxsize * MB_CUR_MAX)) == NULL)
+		goto error;
+	if (strftime_l(dst, maxsize, sformat, timeptr, locale) == 0)
+		goto error;
+	dstp = dst;
+	mbs = initial;
+	n = mbsrtowcs_l(wcs, &dstp, maxsize, &mbs, locale);
+	if (n == (size_t)-2 || n == (size_t)-1 || dstp != NULL)
+		goto error;
+
+	free(sformat);
+	free(dst);
+	return (n);
+
+error:
+	sverrno = errno;
+	free(sformat);
+	free(dst);
+	errno = sverrno;
+	return (0);
+}
+size_t
+wcsftime(wchar_t * __restrict wcs, size_t maxsize,
+	const wchar_t * __restrict format, const struct tm * __restrict timeptr)
+{
+	return wcsftime_l(wcs, maxsize, format, timeptr, __get_locale());
+}
diff --git a/libc/upstream-openbsd/lib/libc/time/wcsftime.c b/libc/upstream-openbsd/lib/libc/time/wcsftime.c
deleted file mode 100644
index 6870871..0000000
--- a/libc/upstream-openbsd/lib/libc/time/wcsftime.c
+++ /dev/null
@@ -1,550 +0,0 @@
-/*	$OpenBSD: wcsftime.c,v 1.7 2019/05/12 12:49:52 schwarze Exp $ */
-/*
-** Based on the UCB version with the ID appearing below.
-** This is ANSIish only when "multibyte character == plain character".
-**
-** Copyright (c) 1989, 1993
-**	The Regents of the University of California.  All rights reserved.
-**
-** Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions
-** are met:
-** 1. Redistributions of source code must retain the above copyright
-**    notice, this list of conditions and the following disclaimer.
-** 2. Redistributions in binary form must reproduce the above copyright
-**    notice, this list of conditions and the following disclaimer in the
-**    documentation and/or other materials provided with the distribution.
-** 3. Neither the name of the University nor the names of its contributors
-**    may be used to endorse or promote products derived from this software
-**    without specific prior written permission.
-**
-** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-** ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-** SUCH DAMAGE.
-*/
-
-#include <fcntl.h>
-#include <locale.h>
-#include <wchar.h>
-
-#include "private.h"
-#include "tzfile.h"
-
-struct lc_time_T {
-	const wchar_t *	mon[MONSPERYEAR];
-	const wchar_t *	month[MONSPERYEAR];
-	const wchar_t *	wday[DAYSPERWEEK];
-	const wchar_t *	weekday[DAYSPERWEEK];
-	const wchar_t *	X_fmt;
-	const wchar_t *	x_fmt;
-	const wchar_t *	c_fmt;
-	const wchar_t *	am;
-	const wchar_t *	pm;
-	const wchar_t *	date_fmt;
-};
-
-#define Locale	(&C_time_locale)
-
-static const struct lc_time_T	C_time_locale = {
-	{
-		L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun",
-		L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec"
-	}, {
-		L"January", L"February", L"March", L"April", L"May", L"June",
-		L"July", L"August", L"September", L"October", L"November", 
-		L"December"
-	}, {
-		L"Sun", L"Mon", L"Tue", L"Wed",
-		L"Thu", L"Fri", L"Sat"
-	}, {
-		L"Sunday", L"Monday", L"Tuesday", L"Wednesday",
-		L"Thursday", L"Friday", L"Saturday"
-	},
-
-	/* X_fmt */
-	L"%H:%M:%S",
-
-	/*
-	** x_fmt
-	** C99 requires this format.
-	** Using just numbers (as here) makes Quakers happier;
-	** it's also compatible with SVR4.
-	*/
-	L"%m/%d/%y",
-
-	/*
-	** c_fmt
-	** C99 requires this format.
-	** Previously this code used "%D %X", but we now conform to C99.
-	** Note that
-	**	"%a %b %d %H:%M:%S %Y"
-	** is used by Solaris 2.3.
-	*/
-	L"%a %b %e %T %Y",
-
-	/* am */
-	L"AM",
-
-	/* pm */
-	L"PM",
-
-	/* date_fmt */
-	L"%a %b %e %H:%M:%S %Z %Y"
-};
-
-#define UNKNOWN L"?"
-static wchar_t *	_add(const wchar_t *, wchar_t *, const wchar_t *);
-static wchar_t *	_sadd(const char *, wchar_t *, const wchar_t *);
-static wchar_t *	_conv(int, const wchar_t *, wchar_t *, const wchar_t *);
-static wchar_t *	_fmt(const wchar_t *, const struct tm *, wchar_t *, const wchar_t *,
-			int *);
-static wchar_t *	_yconv(int, int, int, int, wchar_t *, const wchar_t *);
-
-extern char *	tzname[];
-
-#define IN_NONE	0
-#define IN_SOME	1
-#define IN_THIS	2
-#define IN_ALL	3
-
-size_t
-wcsftime(wchar_t *__restrict s, size_t maxsize, 
-    const wchar_t *__restrict format, const struct tm *__restrict t)
-{
-	wchar_t *p;
-	int	warn;
-
-	tzset();
-	warn = IN_NONE;
-	p = _fmt(((format == NULL) ? L"%c" : format), t, s, s + maxsize, &warn);
-	if (p == s + maxsize) {
-		if (maxsize > 0)
-			s[maxsize - 1] = '\0';
-		return 0;
-	}
-	*p = L'\0';
-	return p - s;
-}
-
-static wchar_t *
-_fmt(const wchar_t *format, const struct tm *t, wchar_t *pt, 
-    const wchar_t *ptlim, int *warnp)
-{
-	for ( ; *format; ++format) {
-		if (*format != L'%') {
-			if (pt == ptlim)
-				break;
-			*pt++ = *format;
-			continue;
-		}
-label:
-		switch (*++format) {
-		case '\0':
-			--format;
-			break;
-		case 'A':
-			pt = _add((t->tm_wday < 0 ||
-				t->tm_wday >= DAYSPERWEEK) ?
-				UNKNOWN : Locale->weekday[t->tm_wday],
-				pt, ptlim);
-			continue;
-		case 'a':
-			pt = _add((t->tm_wday < 0 ||
-				t->tm_wday >= DAYSPERWEEK) ?
-				UNKNOWN : Locale->wday[t->tm_wday],
-				pt, ptlim);
-			continue;
-		case 'B':
-			pt = _add((t->tm_mon < 0 ||
-				t->tm_mon >= MONSPERYEAR) ?
-				UNKNOWN : Locale->month[t->tm_mon],
-				pt, ptlim);
-			continue;
-		case 'b':
-		case 'h':
-			pt = _add((t->tm_mon < 0 ||
-				t->tm_mon >= MONSPERYEAR) ?
-				UNKNOWN : Locale->mon[t->tm_mon],
-				pt, ptlim);
-			continue;
-		case 'C':
-			/*
-			** %C used to do a...
-			**	_fmt("%a %b %e %X %Y", t);
-			** ...whereas now POSIX 1003.2 calls for
-			** something completely different.
-			** (ado, 1993-05-24)
-			*/
-			pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
-				pt, ptlim);
-			continue;
-		case 'c':
-			{
-			int warn2 = IN_SOME;
-
-			pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
-			if (warn2 == IN_ALL)
-				warn2 = IN_THIS;
-			if (warn2 > *warnp)
-				*warnp = warn2;
-			}
-			continue;
-		case 'D':
-			pt = _fmt(L"%m/%d/%y", t, pt, ptlim, warnp);
-			continue;
-		case 'd':
-			pt = _conv(t->tm_mday, L"%02d", pt, ptlim);
-			continue;
-		case 'E':
-		case 'O':
-			/*
-			** C99 locale modifiers.
-			** The sequences
-			**	%Ec %EC %Ex %EX %Ey %EY
-			**	%Od %oe %OH %OI %Om %OM
-			**	%OS %Ou %OU %OV %Ow %OW %Oy
-			** are supposed to provide alternate
-			** representations.
-			*/
-			goto label;
-		case 'e':
-			pt = _conv(t->tm_mday, L"%2d", pt, ptlim);
-			continue;
-		case 'F':
-			pt = _fmt(L"%Y-%m-%d", t, pt, ptlim, warnp);
-			continue;
-		case 'H':
-			pt = _conv(t->tm_hour, L"%02d", pt, ptlim);
-			continue;
-		case 'I':
-			pt = _conv((t->tm_hour % 12) ?
-				(t->tm_hour % 12) : 12,
-				L"%02d", pt, ptlim);
-			continue;
-		case 'j':
-			pt = _conv(t->tm_yday + 1, L"%03d", pt, ptlim);
-			continue;
-		case 'k':
-			/*
-			** This used to be...
-			**	_conv(t->tm_hour % 12 ?
-			**		t->tm_hour % 12 : 12, 2, ' ');
-			** ...and has been changed to the below to
-			** match SunOS 4.1.1 and Arnold Robbins'
-			** strftime version 3.0. That is, "%k" and
-			** "%l" have been swapped.
-			** (ado, 1993-05-24)
-			*/
-			pt = _conv(t->tm_hour, L"%2d", pt, ptlim);
-			continue;
-		case 'l':
-			/*
-			** This used to be...
-			**	_conv(t->tm_hour, 2, ' ');
-			** ...and has been changed to the below to
-			** match SunOS 4.1.1 and Arnold Robbin's
-			** strftime version 3.0. That is, "%k" and
-			** "%l" have been swapped.
-			** (ado, 1993-05-24)
-			*/
-			pt = _conv((t->tm_hour % 12) ?
-				(t->tm_hour % 12) : 12,
-				L"%2d", pt, ptlim);
-			continue;
-		case 'M':
-			pt = _conv(t->tm_min, L"%02d", pt, ptlim);
-			continue;
-		case 'm':
-			pt = _conv(t->tm_mon + 1, L"%02d", pt, ptlim);
-			continue;
-		case 'n':
-			pt = _add(L"\n", pt, ptlim);
-			continue;
-		case 'p':
-			pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
-				Locale->pm :
-				Locale->am,
-				pt, ptlim);
-			continue;
-		case 'R':
-			pt = _fmt(L"%H:%M", t, pt, ptlim, warnp);
-			continue;
-		case 'r':
-			pt = _fmt(L"%I:%M:%S %p", t, pt, ptlim, warnp);
-			continue;
-		case 'S':
-			pt = _conv(t->tm_sec, L"%02d", pt, ptlim);
-			continue;
-		case 's':
-			{
-				struct tm	tm;
-				wchar_t		buf[INT_STRLEN_MAXIMUM(
-							time_t) + 1];
-				time_t		mkt;
-
-				tm = *t;
-				mkt = mktime(&tm);
-				(void) swprintf(buf, 
-				    sizeof buf/sizeof buf[0],
-				    L"%ld", (long) mkt);
-				pt = _add(buf, pt, ptlim);
-			}
-			continue;
-		case 'T':
-			pt = _fmt(L"%H:%M:%S", t, pt, ptlim, warnp);
-			continue;
-		case 't':
-			pt = _add(L"\t", pt, ptlim);
-			continue;
-		case 'U':
-			pt = _conv((t->tm_yday + DAYSPERWEEK -
-				t->tm_wday) / DAYSPERWEEK,
-				L"%02d", pt, ptlim);
-			continue;
-		case 'u':
-			/*
-			** From Arnold Robbins' strftime version 3.0:
-			** "ISO 8601: Weekday as a decimal number
-			** [1 (Monday) - 7]"
-			** (ado, 1993-05-24)
-			*/
-			pt = _conv((t->tm_wday == 0) ?
-				DAYSPERWEEK : t->tm_wday,
-				L"%d", pt, ptlim);
-			continue;
-		case 'V':	/* ISO 8601 week number */
-		case 'G':	/* ISO 8601 year (four digits) */
-		case 'g':	/* ISO 8601 year (two digits) */
-/*
-** From Arnold Robbins' strftime version 3.0: "the week number of the
-** year (the first Monday as the first day of week 1) as a decimal number
-** (01-53)."
-** (ado, 1993-05-24)
-**
-** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
-** "Week 01 of a year is per definition the first week which has the
-** Thursday in this year, which is equivalent to the week which contains
-** the fourth day of January. In other words, the first week of a new year
-** is the week which has the majority of its days in the new year. Week 01
-** might also contain days from the previous year and the week before week
-** 01 of a year is the last week (52 or 53) of the previous year even if
-** it contains days from the new year. A week starts with Monday (day 1)
-** and ends with Sunday (day 7). For example, the first week of the year
-** 1997 lasts from 1996-12-30 to 1997-01-05..."
-** (ado, 1996-01-02)
-*/
-			{
-			int	year;
-			int	base;
-			int	yday;
-			int	wday;
-			int	w;
-
-			year = t->tm_year;
-			base = TM_YEAR_BASE;
-			yday = t->tm_yday;
-			wday = t->tm_wday;
-			for ( ; ; ) {
-				int	len;
-				int	bot;
-				int	top;
-
-				len = isleap_sum(year, base) ?
-					DAYSPERLYEAR :
-					DAYSPERNYEAR;
-				/*
-				** What yday (-3 ... 3) does the ISO year 
-				** begin on?
-				*/
-				bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3;
-				/*
-				** What yday does the NEXT ISO year begin on?
-				*/
-				top = bot - (len % DAYSPERWEEK);
-				if (top < -3)
-					top += DAYSPERWEEK;
-				top += len;
-				if (yday >= top) {
-					++base;
-					w = 1;
-					break;
-				}
-				if (yday >= bot) {
-					w = 1 + ((yday - bot) / DAYSPERWEEK);
-					break;
-				}
-				--base;
-				yday += isleap_sum(year, base) ?
-					DAYSPERLYEAR :
-					DAYSPERNYEAR;
-			}
-			if ((w == 52 && t->tm_mon == TM_JANUARY) ||
-				(w == 1 && t->tm_mon == TM_DECEMBER))
-					w = 53;
-			if (*format == 'V')
-				pt = _conv(w, L"%02d", pt, ptlim);
-			else if (*format == 'g') {
-				*warnp = IN_ALL;
-				pt = _yconv(year, base, 0, 1, pt, ptlim);
-			} else	
-				pt = _yconv(year, base, 1, 1, pt, ptlim);
-			}
-			continue;
-		case 'v':
-			/*
-			** From Arnold Robbins' strftime version 3.0:
-			** "date as dd-bbb-YYYY"
-			** (ado, 1993-05-24)
-			*/
-			pt = _fmt(L"%e-%b-%Y", t, pt, ptlim, warnp);
-			continue;
-		case 'W':
-			pt = _conv((t->tm_yday + DAYSPERWEEK -
-				(t->tm_wday ?
-				(t->tm_wday - 1) :
-				(DAYSPERWEEK - 1))) / DAYSPERWEEK,
-				L"%02d", pt, ptlim);
-			continue;
-		case 'w':
-			pt = _conv(t->tm_wday, L"%d", pt, ptlim);
-			continue;
-		case 'X':
-			pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
-			continue;
-		case 'x':
-			{
-			int	warn2 = IN_SOME;
-
-			pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
-			if (warn2 == IN_ALL)
-				warn2 = IN_THIS;
-			if (warn2 > *warnp)
-				*warnp = warn2;
-			}
-			continue;
-		case 'y':
-			*warnp = IN_ALL;
-			pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, pt, ptlim);
-			continue;
-		case 'Y':
-			pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, pt, ptlim);
-			continue;
-		case 'Z':
-			if (t->tm_zone != NULL)
-				pt = _sadd(t->tm_zone, pt, ptlim);
-			else
-				if (t->tm_isdst >= 0)
-					pt = _sadd(tzname[t->tm_isdst != 0], 
-					    pt, ptlim);
-			/*
-			** C99 says that %Z must be replaced by the
-			** empty string if the time zone is not
-			** determinable.
-			*/
-			continue;
-		case 'z':
-			{
-			int		diff;
-			wchar_t const *	sign;
-
-			if (t->tm_isdst < 0)
-				continue;
-			diff = t->tm_gmtoff;
-			if (diff < 0) {
-				sign = L"-";
-				diff = -diff;
-			} else	
-				sign = L"+";
-			pt = _add(sign, pt, ptlim);
-			diff /= SECSPERMIN;
-			diff = (diff / MINSPERHOUR) * 100 +
-				(diff % MINSPERHOUR);
-			pt = _conv(diff, L"%04d", pt, ptlim);
-			}
-			continue;
-		case '+':
-			pt = _fmt(Locale->date_fmt, t, pt, ptlim, warnp);
-			continue;
-		case '%':
-		/*
-		** X311J/88-090 (4.12.3.5): if conversion wchar_t is
-		** undefined, behavior is undefined. Print out the
-		** character itself as printf(3) also does.
-		*/
-		default:
-			if (pt != ptlim)
-				*pt++ = *format;
-			break;
-		}
-	}
-	return pt;
-}
-
-static wchar_t *
-_conv(int n, const wchar_t *format, wchar_t *pt, const wchar_t *ptlim)
-{
-	wchar_t	buf[INT_STRLEN_MAXIMUM(int) + 1];
-
-	(void) swprintf(buf, sizeof buf/sizeof buf[0], format, n);
-	return _add(buf, pt, ptlim);
-}
-
-static wchar_t *
-_add(const wchar_t *str, wchar_t *pt, const wchar_t *ptlim)
-{
-	while (pt < ptlim && (*pt = *str++) != L'\0')
-		++pt;
-	return pt;
-}
-
-static wchar_t *
-_sadd(const char *str, wchar_t *pt, const wchar_t *ptlim)
-{
-	while (pt < ptlim && (*pt = btowc(*str++)) != L'\0')
-		++pt;
-	return pt;
-}
-/*
-** POSIX and the C Standard are unclear or inconsistent about
-** what %C and %y do if the year is negative or exceeds 9999.
-** Use the convention that %C concatenated with %y yields the
-** same output as %Y, and that %Y contains at least 4 bytes,
-** with more only if necessary.
-*/
-
-static wchar_t *
-_yconv(int a, int b, int convert_top, int convert_yy, wchar_t *pt, 
-    const wchar_t *ptlim)
-{
-	int	lead;
-	int	trail;
-
-#define DIVISOR	100
-	trail = a % DIVISOR + b % DIVISOR;
-	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
-	trail %= DIVISOR;
-	if (trail < 0 && lead > 0) {
-		trail += DIVISOR;
-		--lead;
-	} else if (lead < 0 && trail > 0) {
-		trail -= DIVISOR;
-		++lead;
-	}
-	if (convert_top) {
-		if (lead == 0 && trail < 0)
-			pt = _add(L"-0", pt, ptlim);
-		else	pt = _conv(lead, L"%02d", pt, ptlim);
-	}
-	if (convert_yy)
-		pt = _conv(((trail < 0) ? -trail : trail), L"%02d", pt, ptlim);
-	return pt;
-}
-
diff --git a/tests/time_test.cpp b/tests/time_test.cpp
index f89fa9a..bef51a9 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -28,6 +28,7 @@
 
 #include <atomic>
 #include <chrono>
+#include <thread>
 
 #include "SignalUtils.h"
 #include "utils.h"
@@ -1313,3 +1314,105 @@
   ASSERT_EQ(1.0, difftime(1, 0));
   ASSERT_EQ(-1.0, difftime(0, 1));
 }
+
+TEST(time, tzfree_null) {
+#if __BIONIC__
+  tzfree(nullptr);
+#else
+  GTEST_SKIP() << "glibc doesn't have timezone_t";
+#endif
+}
+
+TEST(time, localtime_rz) {
+#if __BIONIC__
+  setenv("TZ", "America/Los_Angeles", 1);
+  tzset();
+
+  auto AssertTmEq = [](const struct tm& rhs, int hour) {
+    ASSERT_EQ(93, rhs.tm_year);
+    ASSERT_EQ(0, rhs.tm_mon);
+    ASSERT_EQ(1, rhs.tm_mday);
+    ASSERT_EQ(hour, rhs.tm_hour);
+    ASSERT_EQ(0, rhs.tm_min);
+    ASSERT_EQ(0, rhs.tm_sec);
+  };
+
+  const time_t t = 725875200;
+
+  // Spam localtime_r() while we use localtime_rz().
+  std::atomic<bool> done = false;
+  std::thread thread{[&] {
+    while (!done) {
+      struct tm tm {};
+      ASSERT_EQ(&tm, localtime_r(&t, &tm));
+      AssertTmEq(tm, 0);
+    }
+  }};
+
+  struct tm tm;
+
+  timezone_t london{tzalloc("Europe/London")};
+  tm = {};
+  ASSERT_EQ(&tm, localtime_rz(london, &t, &tm));
+  AssertTmEq(tm, 8);
+
+  timezone_t seoul{tzalloc("Asia/Seoul")};
+  tm = {};
+  ASSERT_EQ(&tm, localtime_rz(seoul, &t, &tm));
+  AssertTmEq(tm, 17);
+
+  // Just check that mktime()'s time zone didn't change.
+  tm = {};
+  ASSERT_EQ(&tm, localtime_r(&t, &tm));
+  ASSERT_EQ(0, tm.tm_hour);
+  AssertTmEq(tm, 0);
+
+  done = true;
+  thread.join();
+
+  tzfree(london);
+  tzfree(seoul);
+#else
+  GTEST_SKIP() << "glibc doesn't have timezone_t";
+#endif
+}
+
+TEST(time, mktime_z) {
+#if __BIONIC__
+  setenv("TZ", "America/Los_Angeles", 1);
+  tzset();
+
+  // Spam mktime() while we use mktime_z().
+  std::atomic<bool> done = false;
+  std::thread thread{[&done] {
+    while (!done) {
+      struct tm tm {
+        .tm_year = 93, .tm_mday = 1
+      };
+      ASSERT_EQ(725875200, mktime(&tm));
+    }
+  }};
+
+  struct tm tm;
+
+  timezone_t london{tzalloc("Europe/London")};
+  tm = {.tm_year = 93, .tm_mday = 1};
+  ASSERT_EQ(725846400, mktime_z(london, &tm));
+
+  timezone_t seoul{tzalloc("Asia/Seoul")};
+  tm = {.tm_year = 93, .tm_mday = 1};
+  ASSERT_EQ(725814000, mktime_z(seoul, &tm));
+
+  // Just check that mktime()'s time zone didn't change.
+  tm = {.tm_year = 93, .tm_mday = 1};
+  ASSERT_EQ(725875200, mktime(&tm));
+
+  done = true;
+  thread.join();
+
+  tzfree(london);
+  tzfree(seoul);
+#else
+  GTEST_SKIP() << "glibc doesn't have timezone_t";
+#endif
+}