While the three new unit tests succeed on mingw, they fail on MSVC. And that's not because of the SetEnvironmentVariable call at the beginning of the unit tests. Even after commenting these out, the tests fail when invoked like this: $ TZ=Europe/Berlin gltests/test-localtime-w32 $ TZ=Europe/Berlin gltests/test-localtime_r-w32 $ TZ=Europe/Berlin gltests/test-mktime-w32
These two patches provide a workaround. 2026-10-01 Bruno Haible <[email protected]> tzsanitize: Fix test failures on MSVC. * lib/tzsanitize.c: Include <windows.h>. (tzsanitize): Except on mingw, sanitize also the environment variable in the process environment block. 2026-10-01 Bruno Haible <[email protected]> tzsanitize: New module. * lib/tzsanitize.h: New file, extracted from lib/localtime.c. * lib/tzsanitize.c: New file, extracted from lib/localtime.c. * lib/ctime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, <wchar.h>. (rpl_ctime): Call tzsanitize. * lib/localtime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, <wchar.h>. (rpl_localtime): Call tzsanitize. * lib/mktime.c: Include tzsanitize.h, <stddef.h> instead of <stdlib.h>, <string.h>. (my_tzset): Call tzsanitize. * lib/strftime-fixes.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>. (rpl_strftime): Call tzsanitize. * lib/tzset.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, <wchar.h>. (rpl_tzset): Call tzsanitize. * lib/wcsftime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>. (rpl_wcsftime): Call tzsanitize. * modules/ctime (Depends-on): Add tzsanitize. * modules/localtime (Depends-on): Likewise. * modules/mktime (Depends-on): Likewise. * modules/strftime-fixes (Depends-on): Likewise. * modules/tzset (Depends-on): Likewise. * modules/wcsftime (Depends-on): Likewise.
>From f693cbe4b6f330951b907009b97c8601fbd0a720 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Thu, 1 Oct 2026 20:11:03 +0200 Subject: [PATCH 1/2] tzsanitize: New module. * lib/tzsanitize.h: New file, extracted from lib/localtime.c. * lib/tzsanitize.c: New file, extracted from lib/localtime.c. * lib/ctime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, <wchar.h>. (rpl_ctime): Call tzsanitize. * lib/localtime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, <wchar.h>. (rpl_localtime): Call tzsanitize. * lib/mktime.c: Include tzsanitize.h, <stddef.h> instead of <stdlib.h>, <string.h>. (my_tzset): Call tzsanitize. * lib/strftime-fixes.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>. (rpl_strftime): Call tzsanitize. * lib/tzset.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, <wchar.h>. (rpl_tzset): Call tzsanitize. * lib/wcsftime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>. (rpl_wcsftime): Call tzsanitize. * modules/ctime (Depends-on): Add tzsanitize. * modules/localtime (Depends-on): Likewise. * modules/mktime (Depends-on): Likewise. * modules/strftime-fixes (Depends-on): Likewise. * modules/tzset (Depends-on): Likewise. * modules/wcsftime (Depends-on): Likewise. --- ChangeLog | 30 ++++++++++++++++++++++ lib/ctime.c | 53 ++------------------------------------ lib/localtime.c | 53 ++------------------------------------ lib/mktime.c | 33 +++++------------------- lib/strftime-fixes.c | 29 +-------------------- lib/tzsanitize.c | 55 +++++++++++++++++++++++++++++++++++++++ lib/tzsanitize.h | 58 ++++++++++++++++++++++++++++++++++++++++++ lib/tzset.c | 53 ++------------------------------------ lib/wcsftime.c | 51 +++---------------------------------- modules/ctime | 1 + modules/localtime | 1 + modules/mktime | 1 + modules/strftime-fixes | 1 + modules/tzset | 1 + modules/wcsftime | 1 + 15 files changed, 165 insertions(+), 256 deletions(-) create mode 100644 lib/tzsanitize.c create mode 100644 lib/tzsanitize.h diff --git a/ChangeLog b/ChangeLog index a16f35bcc3..9343f91de3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,33 @@ +2026-10-01 Bruno Haible <[email protected]> + + tzsanitize: New module. + * lib/tzsanitize.h: New file, extracted from lib/localtime.c. + * lib/tzsanitize.c: New file, extracted from lib/localtime.c. + * lib/ctime.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, + <wchar.h>. + (rpl_ctime): Call tzsanitize. + * lib/localtime.c: Include tzsanitize.h instead of <stdlib.h>, + <string.h>, <wchar.h>. + (rpl_localtime): Call tzsanitize. + * lib/mktime.c: Include tzsanitize.h, <stddef.h> instead of <stdlib.h>, + <string.h>. + (my_tzset): Call tzsanitize. + * lib/strftime-fixes.c: Include tzsanitize.h instead of <stdlib.h>, + <string.h>. + (rpl_strftime): Call tzsanitize. + * lib/tzset.c: Include tzsanitize.h instead of <stdlib.h>, <string.h>, + <wchar.h>. + (rpl_tzset): Call tzsanitize. + * lib/wcsftime.c: Include tzsanitize.h instead of <stdlib.h>, + <string.h>. + (rpl_wcsftime): Call tzsanitize. + * modules/ctime (Depends-on): Add tzsanitize. + * modules/localtime (Depends-on): Likewise. + * modules/mktime (Depends-on): Likewise. + * modules/strftime-fixes (Depends-on): Likewise. + * modules/tzset (Depends-on): Likewise. + * modules/wcsftime (Depends-on): Likewise. + 2026-10-01 Bruno Haible <[email protected]> mktime: Add tests. diff --git a/lib/ctime.c b/lib/ctime.c index 5587e299ec..cbf32cc568 100644 --- a/lib/ctime.c +++ b/lib/ctime.c @@ -19,11 +19,7 @@ /* Specification. */ #include <time.h> -#include <stdlib.h> -#include <string.h> -#if defined _WIN32 && ! defined __CYGWIN__ -# include <wchar.h> -#endif +#include "tzsanitize.h" #undef ctime @@ -31,52 +27,7 @@ char * rpl_ctime (const time_t *tp) { #if defined _WIN32 && ! defined __CYGWIN__ - /* Rectify the value of the environment variable TZ. - There are four possible kinds of such values: - - Traditional US time zone names, e.g. "PST8PDT". Syntax: see - <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> - - Time zone names based on geography, that contain one or more - slashes, e.g. "Europe/Moscow". - - Time zone names based on geography, without slashes, e.g. - "Singapore". - - Time zone names that contain explicit DST rules. Syntax: see - <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> - The Microsoft CRT understands only the first kind. It produces incorrect - results if the value of TZ is of the other kinds. - But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value - of the second kind for most geographies, or of the first kind in a few - other geographies. If it is of the second kind, neutralize it. For the - Microsoft CRT, an absent or empty TZ means the time zone that the user - has set in the Windows Control Panel. - If the value of TZ is of the third or fourth kind -- Cygwin programs - understand these syntaxes as well --, it does not matter whether we - neutralize it or not, since these values occur only when a Cygwin user - has set TZ explicitly; this case is 1. rare and 2. under the user's - responsibility. */ - const char *tz = getenv ("TZ"); - if (tz != NULL && strchr (tz, '/') != NULL) - { - /* Neutralize it, in a way that is thread-safe. - (If we were to use _putenv ("TZ="), it would free the memory allocated - for the environment variable "TZ", and thus other threads that are - using the previously fetched value of getenv ("TZ") could crash.) */ - char **env = _environ; - wchar_t **wenv = _wenviron; - if (env != NULL) - for (char **ep = env; *ep != NULL; ep++) - { - char *s = *ep; - if (s[0] == 'T' && s[1] == 'Z' && s[2] == '=') - s[0] = '$'; - } - if (wenv != NULL) - for (wchar_t **wep = wenv; *wep != NULL; wep++) - { - wchar_t *ws = *wep; - if (ws[0] == L'T' && ws[1] == L'Z' && ws[2] == L'=') - ws[0] = L'$'; - } - } + tzsanitize (); #endif return ctime (tp); diff --git a/lib/localtime.c b/lib/localtime.c index 6c24742c9e..1af75963bd 100644 --- a/lib/localtime.c +++ b/lib/localtime.c @@ -19,11 +19,7 @@ /* Specification. */ #include <time.h> -#include <stdlib.h> -#include <string.h> -#if defined _WIN32 && ! defined __CYGWIN__ -# include <wchar.h> -#endif +#include "tzsanitize.h" #undef localtime @@ -31,52 +27,7 @@ struct tm * rpl_localtime (const time_t *tp) { #if defined _WIN32 && ! defined __CYGWIN__ - /* Rectify the value of the environment variable TZ. - There are four possible kinds of such values: - - Traditional US time zone names, e.g. "PST8PDT". Syntax: see - <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> - - Time zone names based on geography, that contain one or more - slashes, e.g. "Europe/Moscow". - - Time zone names based on geography, without slashes, e.g. - "Singapore". - - Time zone names that contain explicit DST rules. Syntax: see - <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> - The Microsoft CRT understands only the first kind. It produces incorrect - results if the value of TZ is of the other kinds. - But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value - of the second kind for most geographies, or of the first kind in a few - other geographies. If it is of the second kind, neutralize it. For the - Microsoft CRT, an absent or empty TZ means the time zone that the user - has set in the Windows Control Panel. - If the value of TZ is of the third or fourth kind -- Cygwin programs - understand these syntaxes as well --, it does not matter whether we - neutralize it or not, since these values occur only when a Cygwin user - has set TZ explicitly; this case is 1. rare and 2. under the user's - responsibility. */ - const char *tz = getenv ("TZ"); - if (tz != NULL && strchr (tz, '/') != NULL) - { - /* Neutralize it, in a way that is thread-safe. - (If we were to use _putenv ("TZ="), it would free the memory allocated - for the environment variable "TZ", and thus other threads that are - using the previously fetched value of getenv ("TZ") could crash.) */ - char **env = _environ; - wchar_t **wenv = _wenviron; - if (env != NULL) - for (char **ep = env; *ep != NULL; ep++) - { - char *s = *ep; - if (s[0] == 'T' && s[1] == 'Z' && s[2] == '=') - s[0] = '$'; - } - if (wenv != NULL) - for (wchar_t **wep = wenv; *wep != NULL; wep++) - { - wchar_t *ws = *wep; - if (ws[0] == L'T' && ws[1] == L'Z' && ws[2] == L'=') - ws[0] = L'$'; - } - } + tzsanitize (); #endif return localtime (tp); diff --git a/lib/mktime.c b/lib/mktime.c index c56c6f9a1d..32c8538f48 100644 --- a/lib/mktime.c +++ b/lib/mktime.c @@ -47,8 +47,7 @@ #include <limits.h> #include <stdbool.h> #include <stdckdint.h> -#include <stdlib.h> -#include <string.h> +#include <stddef.h> #include <intprops.h> @@ -67,36 +66,16 @@ #endif #include "mktime-internal.h" +#if NEED_MKTIME_WINDOWS +# include "tzsanitize.h" +#endif + #if !defined _LIBC && (NEED_MKTIME_WORKING || NEED_MKTIME_WINDOWS) static void my_tzset (void) { # if NEED_MKTIME_WINDOWS - /* Rectify the value of the environment variable TZ. - There are four possible kinds of such values: - - Traditional US time zone names, e.g. "PST8PDT". Syntax: see - <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> - - Time zone names based on geography, that contain one or more - slashes, e.g. "Europe/Moscow". - - Time zone names based on geography, without slashes, e.g. - "Singapore". - - Time zone names that contain explicit DST rules. Syntax: see - <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> - The Microsoft CRT understands only the first kind. It produces incorrect - results if the value of TZ is of the other kinds. - But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value - of the second kind for most geographies, or of the first kind in a few - other geographies. If it is of the second kind, neutralize it. For the - Microsoft CRT, an absent or empty TZ means the time zone that the user - has set in the Windows Control Panel. - If the value of TZ is of the third or fourth kind -- Cygwin programs - understand these syntaxes as well --, it does not matter whether we - neutralize it or not, since these values occur only when a Cygwin user - has set TZ explicitly; this case is 1. rare and 2. under the user's - responsibility. */ - const char *tz = getenv ("TZ"); - if (tz != NULL && strchr (tz, '/') != NULL) - _putenv ("TZ="); + tzsanitize (); # else tzset (); # endif diff --git a/lib/strftime-fixes.c b/lib/strftime-fixes.c index 11bb4da1b5..e2dfc19ea0 100644 --- a/lib/strftime-fixes.c +++ b/lib/strftime-fixes.c @@ -19,40 +19,13 @@ /* Specification. */ #include <time.h> -#include <stdlib.h> -#include <string.h> - #undef strftime size_t rpl_strftime (char *buf, size_t bufsize, const char *format, const struct tm *tp) { #if defined _WIN32 && ! defined __CYGWIN__ - /* Rectify the value of the environment variable TZ. - There are four possible kinds of such values: - - Traditional US time zone names, e.g. "PST8PDT". Syntax: see - <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> - - Time zone names based on geography, that contain one or more - slashes, e.g. "Europe/Moscow". - - Time zone names based on geography, without slashes, e.g. - "Singapore". - - Time zone names that contain explicit DST rules. Syntax: see - <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> - The Microsoft CRT understands only the first kind. It produces incorrect - results if the value of TZ is of the other kinds. - But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value - of the second kind for most geographies, or of the first kind in a few - other geographies. If it is of the second kind, neutralize it. For the - Microsoft CRT, an absent or empty TZ means the time zone that the user - has set in the Windows Control Panel. - If the value of TZ is of the third or fourth kind -- Cygwin programs - understand these syntaxes as well --, it does not matter whether we - neutralize it or not, since these values occur only when a Cygwin user - has set TZ explicitly; this case is 1. rare and 2. under the user's - responsibility. */ - const char *tz = getenv ("TZ"); - if (tz != NULL && strchr (tz, '/') != NULL) - _putenv ("TZ="); + tzsanitize (); #endif return strftime (buf, bufsize, format, tp); diff --git a/lib/tzsanitize.c b/lib/tzsanitize.c new file mode 100644 index 0000000000..291bcd6170 --- /dev/null +++ b/lib/tzsanitize.c @@ -0,0 +1,55 @@ +/* Sanitizing the TZ environment variable on native Windows. + Copyright (C) 2017-2026 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible. */ + +#include <config.h> + +/* Specification. */ +#include "tzsanitize.h" + +#include <stdlib.h> +#include <string.h> +#include <wchar.h> + +void +tzsanitize (void) +{ + const char *tz = getenv ("TZ"); + if (tz != NULL && strchr (tz, '/') != NULL) + { + /* Neutralize it, in a way that is thread-safe. + (If we were to use _putenv ("TZ="), it would free the memory allocated + for the environment variable "TZ", and thus other threads that are + using the previously fetched value of getenv ("TZ") could crash.) */ + char **env = _environ; + wchar_t **wenv = _wenviron; + if (env != NULL) + for (char **ep = env; *ep != NULL; ep++) + { + char *s = *ep; + if (s[0] == 'T' && s[1] == 'Z' && s[2] == '=') + s[0] = '$'; + } + if (wenv != NULL) + for (wchar_t **wep = wenv; *wep != NULL; wep++) + { + wchar_t *ws = *wep; + if (ws[0] == L'T' && ws[1] == L'Z' && ws[2] == L'=') + ws[0] = L'$'; + } + } +} diff --git a/lib/tzsanitize.h b/lib/tzsanitize.h new file mode 100644 index 0000000000..c7c50d1a55 --- /dev/null +++ b/lib/tzsanitize.h @@ -0,0 +1,58 @@ +/* Sanitizing the TZ environment variable on native Windows. + Copyright (C) 2017-2026 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible. */ + +#ifndef _TZSANITIZE_H +#define _TZSANITIZE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined _WIN32 && !defined __CYGWIN__ + +/* Rectify the value of the environment variable TZ. + There are four possible kinds of such values: + - Traditional US time zone names, e.g. "PST8PDT". Syntax: see + <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> + - Time zone names based on geography, that contain one or more + slashes, e.g. "Europe/Moscow". + - Time zone names based on geography, without slashes, e.g. + "Singapore". + - Time zone names that contain explicit DST rules. Syntax: see + <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> + The Microsoft CRT understands only the first kind. It produces incorrect + results if the value of TZ is of the other kinds. + But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value + of the second kind for most geographies, or of the first kind in a few + other geographies. If it is of the second kind, neutralize it. For the + Microsoft CRT, an absent or empty TZ means the time zone that the user + has set in the Windows Control Panel. + If the value of TZ is of the third or fourth kind -- Cygwin programs + understand these syntaxes as well --, it does not matter whether we + neutralize it or not, since these values occur only when a Cygwin user + has set TZ explicitly; this case is 1. rare and 2. under the user's + responsibility. */ +extern void tzsanitize (void); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _TZSANITIZE_H */ diff --git a/lib/tzset.c b/lib/tzset.c index 5644656878..1f6368aabb 100644 --- a/lib/tzset.c +++ b/lib/tzset.c @@ -22,63 +22,14 @@ /* Specification. */ #include <time.h> -#include <stdlib.h> -#include <string.h> -#if defined _WIN32 && ! defined __CYGWIN__ -# include <wchar.h> -#endif +#include "tzsanitize.h" void rpl_tzset (void) #undef tzset { #if defined _WIN32 && ! defined __CYGWIN__ - /* Rectify the value of the environment variable TZ. - There are four possible kinds of such values: - - Traditional US time zone names, e.g. "PST8PDT". Syntax: see - <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> - - Time zone names based on geography, that contain one or more - slashes, e.g. "Europe/Moscow". - - Time zone names based on geography, without slashes, e.g. - "Singapore". - - Time zone names that contain explicit DST rules. Syntax: see - <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> - The Microsoft CRT understands only the first kind. It produces incorrect - results if the value of TZ is of the other kinds. - But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value - of the second kind for most geographies, or of the first kind in a few - other geographies. If it is of the second kind, neutralize it. For the - Microsoft CRT, an absent or empty TZ means the time zone that the user - has set in the Windows Control Panel. - If the value of TZ is of the third or fourth kind -- Cygwin programs - understand these syntaxes as well --, it does not matter whether we - neutralize it or not, since these values occur only when a Cygwin user - has set TZ explicitly; this case is 1. rare and 2. under the user's - responsibility. */ - const char *tz = getenv ("TZ"); - if (tz != NULL && strchr (tz, '/') != NULL) - { - /* Neutralize it, in a way that is thread-safe. - (If we were to use _putenv ("TZ="), it would free the memory allocated - for the environment variable "TZ", and thus other threads that are - using the previously fetched value of getenv ("TZ") could crash.) */ - char **env = _environ; - wchar_t **wenv = _wenviron; - if (env != NULL) - for (char **ep = env; *ep != NULL; ep++) - { - char *s = *ep; - if (s[0] == 'T' && s[1] == 'Z' && s[2] == '=') - s[0] = '$'; - } - if (wenv != NULL) - for (wchar_t **wep = wenv; *wep != NULL; wep++) - { - wchar_t *ws = *wep; - if (ws[0] == L'T' && ws[1] == L'Z' && ws[2] == L'=') - ws[0] = L'$'; - } - } + tzsanitize (); /* On native Windows, tzset() is deprecated. Use _tzset() instead. See <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/posix-tzset> diff --git a/lib/wcsftime.c b/lib/wcsftime.c index 843e648177..f8560debbe 100644 --- a/lib/wcsftime.c +++ b/lib/wcsftime.c @@ -19,62 +19,17 @@ /* Specification. */ #include <wchar.h> -#include <stdlib.h> -#include <string.h> #include <time.h> +#include "tzsanitize.h" + #undef wcsftime size_t rpl_wcsftime (wchar_t *buf, size_t bufsize, const wchar_t *format, const struct tm *tp) { #if defined _WIN32 && ! defined __CYGWIN__ - /* Rectify the value of the environment variable TZ. - There are four possible kinds of such values: - - Traditional US time zone names, e.g. "PST8PDT". Syntax: see - <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset> - - Time zone names based on geography, that contain one or more - slashes, e.g. "Europe/Moscow". - - Time zone names based on geography, without slashes, e.g. - "Singapore". - - Time zone names that contain explicit DST rules. Syntax: see - <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03> - The Microsoft CRT understands only the first kind. It produces incorrect - results if the value of TZ is of the other kinds. - But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value - of the second kind for most geographies, or of the first kind in a few - other geographies. If it is of the second kind, neutralize it. For the - Microsoft CRT, an absent or empty TZ means the time zone that the user - has set in the Windows Control Panel. - If the value of TZ is of the third or fourth kind -- Cygwin programs - understand these syntaxes as well --, it does not matter whether we - neutralize it or not, since these values occur only when a Cygwin user - has set TZ explicitly; this case is 1. rare and 2. under the user's - responsibility. */ - const char *tz = getenv ("TZ"); - if (tz != NULL && strchr (tz, '/') != NULL) - { - /* Neutralize it, in a way that is thread-safe. - (If we were to use _putenv ("TZ="), it would free the memory allocated - for the environment variable "TZ", and thus other threads that are - using the previously fetched value of getenv ("TZ") could crash.) */ - char **env = _environ; - wchar_t **wenv = _wenviron; - if (env != NULL) - for (char **ep = env; *ep != NULL; ep++) - { - char *s = *ep; - if (s[0] == 'T' && s[1] == 'Z' && s[2] == '=') - s[0] = '$'; - } - if (wenv != NULL) - for (wchar_t **wep = wenv; *wep != NULL; wep++) - { - wchar_t *ws = *wep; - if (ws[0] == L'T' && ws[1] == L'Z' && ws[2] == L'=') - ws[0] = L'$'; - } - } + tzsanitize (); #endif return wcsftime (buf, bufsize, format, tp); diff --git a/modules/ctime b/modules/ctime index 11de22f2d9..2018c58f18 100644 --- a/modules/ctime +++ b/modules/ctime @@ -11,6 +11,7 @@ m4/ctime.m4 Depends-on: time-h +tzsanitize [test $REPLACE_CTIME = 1] configure.ac: gl_FUNC_CTIME diff --git a/modules/localtime b/modules/localtime index 7880775328..1d07fb95b5 100644 --- a/modules/localtime +++ b/modules/localtime @@ -7,6 +7,7 @@ m4/localtime.m4 Depends-on: time-h +tzsanitize [test $REPLACE_LOCALTIME = 1] configure.ac: gl_FUNC_LOCALTIME diff --git a/modules/mktime b/modules/mktime index 0653a5d30e..536b11f43f 100644 --- a/modules/mktime +++ b/modules/mktime @@ -16,6 +16,7 @@ libc-config [test $REPLACE_MKTIME = 1] bool [test $REPLACE_MKTIME = 1] stdckdint-h [test $REPLACE_MKTIME = 1] time_r [test $REPLACE_MKTIME = 1] +tzsanitize [test $REPLACE_MKTIME = 1] configure.ac: gl_FUNC_MKTIME diff --git a/modules/strftime-fixes b/modules/strftime-fixes index 626ab04268..007f38aa26 100644 --- a/modules/strftime-fixes +++ b/modules/strftime-fixes @@ -7,6 +7,7 @@ m4/strftime-fixes.m4 Depends-on: time-h +tzsanitize [test $REPLACE_STRFTIME = 1] configure.ac: gl_FUNC_STRFTIME diff --git a/modules/tzset b/modules/tzset index ade021195c..49924086fa 100644 --- a/modules/tzset +++ b/modules/tzset @@ -7,6 +7,7 @@ m4/tzset.m4 Depends-on: time-h +tzsanitize [test $REPLACE_TZSET = 1] configure.ac: gl_FUNC_TZSET diff --git a/modules/wcsftime b/modules/wcsftime index 8dcc068fb3..221602b754 100644 --- a/modules/wcsftime +++ b/modules/wcsftime @@ -7,6 +7,7 @@ m4/wcsftime.m4 Depends-on: wchar-h +tzsanitize [test $REPLACE_WCSFTIME = 1] configure.ac: gl_FUNC_WCSFTIME -- 2.53.0
>From 8c3bdfd243f29dd376c8a110c19e9e9525d1f64e Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Thu, 1 Oct 2026 20:37:56 +0200 Subject: [PATCH 2/2] tzsanitize: Fix test failures on MSVC. * lib/tzsanitize.c: Include <windows.h>. (tzsanitize): Except on mingw, sanitize also the environment variable in the process environment block. --- ChangeLog | 7 +++++++ lib/tzsanitize.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/ChangeLog b/ChangeLog index 9343f91de3..a32ce789d6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2026-10-01 Bruno Haible <[email protected]> + + tzsanitize: Fix test failures on MSVC. + * lib/tzsanitize.c: Include <windows.h>. + (tzsanitize): Except on mingw, sanitize also the environment variable + in the process environment block. + 2026-10-01 Bruno Haible <[email protected]> tzsanitize: New module. diff --git a/lib/tzsanitize.c b/lib/tzsanitize.c index 291bcd6170..dd4f31173f 100644 --- a/lib/tzsanitize.c +++ b/lib/tzsanitize.c @@ -25,6 +25,11 @@ #include <string.h> #include <wchar.h> +#if !defined __MINGW32__ +# define WIN32_LEAN_AND_MEAN /* avoid including junk */ +# include <windows.h> +#endif + void tzsanitize (void) { @@ -51,5 +56,28 @@ tzsanitize (void) if (ws[0] == L'T' && ws[1] == L'Z' && ws[2] == L'=') ws[0] = L'$'; } +#if !defined __MINGW32__ + SetEnvironmentVariable ("TZ", NULL); +#endif + } +#if !defined __MINGW32__ + /* On MSVC, we also need to sanitize the environment variables stored in the + process's environment block. + We don't lose much by assuming that the environment variable's value + is not too large. */ + else + { + /* Documentation: + <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getenvironmentvariable> */ + char tz_buf[256]; + DWORD tz_len = GetEnvironmentVariable ("TZ", tz_buf, sizeof (tz_buf)); + if (tz_len > 0 && tz_len < sizeof (tz_buf) + && strchr (tz_buf, '/') != NULL) + { + /* Documentation: + <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setenvironmentvariable> */ + SetEnvironmentVariable ("TZ", NULL); + } } +#endif } -- 2.53.0
