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
+}