diff --git a/docs/status.md b/docs/status.md index 411b140a45..308c35434a 100644 --- a/docs/status.md +++ b/docs/status.md @@ -57,6 +57,11 @@ Current libc symbols: https://android.googlesource.com/platform/bionic/+/master/ 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 1e2458a29d..ffa82226a8 100644 --- a/libc/Android.bp +++ b/libc/Android.bp @@ -252,10 +252,11 @@ cc_library_static { 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 @@ cc_library_static { "-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 441e79c039..daea7bc218 100644 --- a/libc/NOTICE +++ b/libc/NOTICE @@ -63,38 +63,6 @@ is preserved. ------------------------------------------------------------------- -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 @@ POSSIBILITY OF SUCH DAMAGE. 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 bd9a45ee5e..bb97b3e00d 100644 --- a/libc/bionic/wchar.cpp +++ b/libc/bionic/wchar.cpp @@ -135,6 +135,7 @@ size_t mbsnrtowcs(wchar_t* dst, const char** src, size_t nmc, size_t len, mbstat 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 wcsnrtombs(char* dst, const wchar_t** src, size_t nwc, size_t len, mbstat 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 a86961f206..1e7a2319b1 100644 --- a/libc/bionic/wchar_l.cpp +++ b/libc/bionic/wchar_l.cpp @@ -41,14 +41,6 @@ int wcscoll_l(const wchar_t* ws1, const wchar_t* ws2, locale_t) { 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 @@ unsigned long long wcstoull_l(const wchar_t* s, wchar_t** end_ptr, int base, loc 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 6bf31bc351..bd3fac129e 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,10 +145,23 @@ double difftime(time_t __lhs, time_t __rhs); * [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 @@ -159,10 +178,24 @@ struct tm* _Nullable localtime(const time_t* _Nonnull __t); * 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(). */ @@ -246,9 +279,33 @@ char* _Nullable ctime_r(const time_t* _Nonnull __t, char* _Nonnull __buf); /** * [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 diff --git a/libc/include/wchar.h b/libc/include/wchar.h index cd09a19308..1060d97bdf 100644 --- a/libc/include/wchar.h +++ b/libc/include/wchar.h @@ -57,6 +57,7 @@ int mbsinit(const mbstate_t* _Nullable __ps); 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 @@ size_t wcsnrtombs(char* _Nullable __dst, const wchar_t* __BIONIC_COMPLICATED_NUL 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 0102b30db9..17141dc6d5 100644 --- a/libc/libc.map.txt +++ b/libc/libc.map.txt @@ -1586,7 +1586,13 @@ LIBC_U { # introduced=UpsideDownCake 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 d31a501567..ae7881e27c 100644 --- a/libc/tzcode/strptime.c +++ b/libc/tzcode/strptime.c @@ -28,6 +28,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "private.h" + #include #include #include @@ -37,7 +39,6 @@ #include #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 0000000000..010d70c06b --- /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 + +#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 0000000000..aabb632a74 --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#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 6870871bc7..0000000000 --- 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 -#include -#include - -#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 f89fa9a882..bef51a94e7 100644 --- a/tests/time_test.cpp +++ b/tests/time_test.cpp @@ -28,6 +28,7 @@ #include #include +#include #include "SignalUtils.h" #include "utils.h" @@ -1313,3 +1314,105 @@ TEST(time, difftime) { 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 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 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 +}