# HG changeset patch # User Bram Moolenaar # Date 1560891607 -7200 # Node ID d03a52e02f1ae2c622c4ef761897d3e8705c2661 # Parent afef6986c785340c01d82df273c378be96e2598d patch 8.1.1567: localtime_r() does not respond to $TZ changes commit https://github.com/vim/vim/commit/db51730df1817fc4b6ecf5a065c69fac518ad821 Author: Bram Moolenaar Date: Tue Jun 18 22:53:24 2019 +0200 patch 8.1.1567: localtime_r() does not respond to $TZ changes Problem: Localtime_r() does not respond to $TZ changes. Solution: If $TZ changes then call tzset(). (Tom Ryder) diff --git a/src/auto/configure b/src/auto/configure --- a/src/auto/configure +++ b/src/auto/configure @@ -12569,7 +12569,7 @@ for ac_func in fchdir fchown fchmod fsyn memset mkdtemp nanosleep opendir putenv qsort readlink select setenv \ getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction \ sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp \ - strnicmp strpbrk strtol tgetent towlower towupper iswupper \ + strnicmp strpbrk strtol tgetent towlower towupper iswupper tzset \ usleep utime utimes mblen ftruncate unsetenv posix_openpt do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` diff --git a/src/config.h.in b/src/config.h.in --- a/src/config.h.in +++ b/src/config.h.in @@ -217,6 +217,7 @@ #undef HAVE_TOWLOWER #undef HAVE_TOWUPPER #undef HAVE_ISWUPPER +#undef HAVE_TZSET #undef HAVE_UNSETENV #undef HAVE_USLEEP #undef HAVE_UTIME diff --git a/src/configure.ac b/src/configure.ac --- a/src/configure.ac +++ b/src/configure.ac @@ -3742,7 +3742,7 @@ AC_CHECK_FUNCS(fchdir fchown fchmod fsyn memset mkdtemp nanosleep opendir putenv qsort readlink select setenv \ getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction \ sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp \ - strnicmp strpbrk strtol tgetent towlower towupper iswupper \ + strnicmp strpbrk strtol tgetent towlower towupper iswupper tzset \ usleep utime utimes mblen ftruncate unsetenv posix_openpt) AC_FUNC_SELECT_ARGTYPES AC_FUNC_FSEEKO diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -13188,9 +13188,7 @@ f_str2nr(typval_T *argvars, typval_T *re f_strftime(typval_T *argvars, typval_T *rettv) { char_u result_buf[256]; -# ifdef HAVE_LOCALTIME_R struct tm tmval; -# endif struct tm *curtime; time_t seconds; char_u *p; @@ -13202,11 +13200,7 @@ f_strftime(typval_T *argvars, typval_T * seconds = time(NULL); else seconds = (time_t)tv_get_number(&argvars[1]); -# ifdef HAVE_LOCALTIME_R - curtime = localtime_r(&seconds, &tmval); -# else - curtime = localtime(&seconds); -# endif + curtime = vim_localtime(&seconds, &tmval); /* MSVC returns NULL for an invalid value of seconds. */ if (curtime == NULL) rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); diff --git a/src/memline.c b/src/memline.c --- a/src/memline.c +++ b/src/memline.c @@ -2082,6 +2082,48 @@ get_b0_dict(char_u *fname, dict_T *d) #endif /* + * Cache of the current timezone name as retrieved from TZ, or an empty string + * where unset, up to 64 octets long including trailing null byte. + */ +#if defined(HAVE_LOCALTIME_R) && defined(HAVE_TZSET) +static char tz_cache[64]; +#endif + +/* + * Call either localtime(3) or localtime_r(3) from POSIX libc time.h, with the + * latter version preferred for reentrancy. + * + * If we use localtime_r(3) and we have tzset(3) available, check to see if the + * environment variable TZ has changed since the last run, and call tzset(3) to + * update the global timezone variables if it has. This is because the POSIX + * standard doesn't require localtime_r(3) implementations to do that as it + * does with localtime(3), and we don't want to call tzset(3) every time. + */ + struct tm * +vim_localtime( + const time_t *timep, // timestamp for local representation + struct tm *result) // pointer to caller return buffer +{ +#ifdef HAVE_LOCALTIME_R +# ifdef HAVE_TZSET + char *tz; // pointer for TZ environment var + + tz = (char *)mch_getenv((char_u *)"TZ"); + if (tz == NULL) + tz = ""; + if (STRNCMP(tz_cache, tz, sizeof(tz_cache) - 1) != 0) + { + tzset(); + vim_strncpy((char_u *)tz_cache, (char_u *)tz, sizeof(tz_cache) - 1); + } +# endif // HAVE_TZSET + return localtime_r(timep, result); +#else + return localtime(timep); +#endif // HAVE_LOCALTIME_R +} + +/* * Replacement for ctime(), which is not safe to use. * Requires strftime(), otherwise returns "(unknown)". * If "thetime" is invalid returns "(invalid)". Never returns NULL. @@ -2093,16 +2135,10 @@ get_ctime(time_t thetime, int add_newlin { static char buf[50]; #ifdef HAVE_STRFTIME -# ifdef HAVE_LOCALTIME_R struct tm tmval; -# endif struct tm *curtime; -# ifdef HAVE_LOCALTIME_R - curtime = localtime_r(&thetime, &tmval); -# else - curtime = localtime(&thetime); -# endif + curtime = vim_localtime(&thetime, &tmval); /* MSVC returns NULL for an invalid value of seconds. */ if (curtime == NULL) vim_strncpy((char_u *)buf, (char_u *)_("(Invalid)"), sizeof(buf) - 1); diff --git a/src/proto/memline.pro b/src/proto/memline.pro --- a/src/proto/memline.pro +++ b/src/proto/memline.pro @@ -13,6 +13,7 @@ void ml_recover(int checkext); int recover_names(char_u *fname, int list, int nr, char_u **fname_out); char_u *make_percent_swname(char_u *dir, char_u *name); void get_b0_dict(char_u *fname, dict_T *d); +struct tm *vim_localtime(const time_t *timep, struct tm *result); char *get_ctime(time_t thetime, int add_newline); void ml_sync_all(int check_file, int check_char); void ml_preserve(buf_T *buf, int message); diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -187,6 +187,30 @@ func Test_strftime() call assert_fails('call strftime([])', 'E730:') call assert_fails('call strftime("%Y", [])', 'E745:') + + " Check that the time changes after we change the timezone + " Save previous timezone value, if any + if exists('$TZ') + let tz = $TZ + endif + + " Force EST and then UTC, save the current hour (24-hour clock) for each + let $TZ = 'EST' | let est = strftime('%H') + let $TZ = 'UTC' | let utc = strftime('%H') + + " Those hours should be two bytes long, and should not be the same; if they + " are, a tzset(3) call may have failed somewhere + call assert_equal(strlen(est), 2) + call assert_equal(strlen(utc), 2) + call assert_notequal(est, utc) + + " If we cached a timezone value, put it back, otherwise clear it + if exists('tz') + let $TZ = tz + else + unlet $TZ + endif + endfunc func Test_resolve_unix() diff --git a/src/undo.c b/src/undo.c --- a/src/undo.c +++ b/src/undo.c @@ -3111,18 +3111,12 @@ ex_undolist(exarg_T *eap UNUSED) u_add_time(char_u *buf, size_t buflen, time_t tt) { #ifdef HAVE_STRFTIME -# ifdef HAVE_LOCALTIME_R struct tm tmval; -# endif struct tm *curtime; if (vim_time() - tt >= 100) { -# ifdef HAVE_LOCALTIME_R - curtime = localtime_r(&tt, &tmval); -# else - curtime = localtime(&tt); -# endif + curtime = vim_localtime(&tt, &tmval); if (vim_time() - tt < (60L * 60L * 12L)) /* within 12 hours */ (void)strftime((char *)buf, buflen, "%H:%M:%S", curtime); diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -778,6 +778,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1567, +/**/ 1566, /**/ 1565,