On Mon, Jun 17, 2019 at 09:47:35AM +1200, Tom Ryder wrote:
On Sun, Jun 16, 2019 at 03:32:30PM +0200, Bram Moolenaar wrote:
Just add it as a global function, without "static". Then update the
prototype in src/proto/evalfunc.pro. You can do it automatically if
you have cproto, but it's easy enough to do manually.
OK, I will do that.
Hi Bram; is the attached patch any closer? It builds with --features
set to "tiny" or "normal" to test the +eval conditionals, passes all
tests on both, and corrects the issue for Vim's strftime() on the
latter. I appreciate your patience on this.
--
Tom Ryder <https://sanctum.geek.nz/>
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/20190617094522.GD18019%40conan.
For more options, visit https://groups.google.com/d/optout.
>From d70902078b6cee57559f07c25d596e9f369feb57 Mon Sep 17 00:00:00 2001
From: Tom Ryder <[email protected]>
Date: Sun, 16 Jun 2019 00:11:56 +1200
Subject: [PATCH] Add localtime/tzset abstraction: vim_localtime()
Patch 8.1.1313 (tag v8.1.1313, commit 63d2555) replaces calls to
localtime(3) with the re-entrant localtime_r(3) if available. The
POSIX specification for localtime_r(3)'s behavior with respect to
timezones differs from that for localtime(3). To quote the
specification:
> The localtime() function ... acts as if it called tzset(3) and sets
> the external variables tzname with information about the current
> timezone, timezone with the difference between Coordinated Universal
> Time (UTC) and local standard time in seconds, and daylight to
> a nonzero value if daylight savings time rules apply during some part
> of the year. ... The localtime_r() function ... need not set tzname,
> timezone, and daylight.
The last sentence is most relevant here. It suggests that
implementations of localtime_r(3) need not update the timezone variables
with respect to the TZ environment variable. This would mean that
changes to that environment variable while the process is running may
not influence the time information returned by localtime_r(3) with
respect to the desired timezone.
Indeed, on Debian GNU/Linux with GNU libc 2.24-11-deb9u4, successive
calls to the Vim function strftime() with different values for $TZ as
applied with :let exhibit confusing behavior after v8.3.131:
:let $TZ = "UTC" | echo $TZ.' -> '.strftime("%c")
UTC -> Wed 12 Jun 2019 04:10:29 UTC
:let $TZ = "Pacific/Auckland" | echo $TZ.' -> '.strftime("%c")
Pacific/Auckland -> Wed 12 Jun 2019 04:10:32 UTC
Note that neither the hour nor the reported timezone changes in the
stftime() output between invocations, even though the value of the TZ
environment value itself does change.
This patch adds an abstraction vim_localtime() with the same type
signatures as localtime_r(3) to evalfunc.c, and applies it in place of
all existing uses where included by +eval. If the re-entrant
localtime_r(3) is available, a static cache of the value of the TZ
environment variable is applied to check whether $TZ differs between
successive calls to vim_localtime(), and to run tzset(3) accordingly if
available.
---
src/auto/configure | 2 +-
src/config.h.in | 1 +
src/configure.ac | 2 +-
src/evalfunc.c | 49 +++++++++++++++++++++++++++++-----
src/memline.c | 6 +++--
src/proto/evalfunc.pro | 1 +
src/testdir/test_functions.vim | 24 +++++++++++++++++
src/undo.c | 6 +++--
8 files changed, 78 insertions(+), 13 deletions(-)
diff --git a/src/auto/configure b/src/auto/configure
index c326abf16..8dcad0bb8 100755
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -12569,7 +12569,7 @@ for ac_func in fchdir fchown fchmod fsync getcwd
getpseudotty \
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
index c1ced6fe9..f9ae0bbf3 100644
--- 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
index dbf4f42b8..f7a71d079 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -3742,7 +3742,7 @@ AC_CHECK_FUNCS(fchdir fchown fchmod fsync getcwd
getpseudotty \
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
index 66612e8c8..7376f9f43 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -13181,6 +13181,47 @@ f_str2nr(typval_T *argvars, typval_T *rettv)
}
#ifdef HAVE_STRFTIME
+
+/*
+ * Cache of the current timezone name as retrieved from TZ, or an empty string
+ * where unset, up to TZ_CACHE_LEN-1 octets long.
+ */
+#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's buffer for return
+{
+#if defined(HAVE_LOCALTIME_R) && defined(HAVE_TZSET)
+ char *tz; // timezone retrieved from environment TZ
+
+ tz = mch_getenv((char_u *)"TZ");
+ if (tz == NULL)
+ tz = "";
+ if (strncmp(tz, tz_cache, sizeof(tz_cache) - 1) != 0)
+ {
+ tzset();
+ vim_strncpy(tz_cache, tz, sizeof(tz_cache) - 1);
+ }
+ return localtime_r(timep, result);
+#else
+ return localtime(timep);
+#endif // HAVE_LOCALTIME_R && HAVE_TZSET
+}
+
/*
* "strftime({format}[, {time}])" function
*/
@@ -13188,9 +13229,7 @@ f_str2nr(typval_T *argvars, typval_T *rettv)
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 +13241,7 @@ f_strftime(typval_T *argvars, typval_T *rettv)
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
index e7ae6dfb5..aded8cb1b 100644
--- a/src/memline.c
+++ b/src/memline.c
@@ -2093,12 +2093,14 @@ get_ctime(time_t thetime, int add_newline)
{
static char buf[50];
#ifdef HAVE_STRFTIME
-# ifdef HAVE_LOCALTIME_R
+# if defined(EVAL) || defined(HAVE_LOCALTIME_R)
struct tm tmval;
# endif
struct tm *curtime;
-# ifdef HAVE_LOCALTIME_R
+# if defined(EVAL)
+ curtime = vim_localtime(&thetime, &tmval)
+# elif defined(HAVE_LOCALTIME_R)
curtime = localtime_r(&thetime, &tmval);
# else
curtime = localtime(&thetime);
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index 533b9823e..113f67358 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -10,6 +10,7 @@ void execute_redir_str(char_u *value, int value_len);
void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
float_T vim_round(float_T f);
long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, typval_T
*skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit);
+struct tm *vim_localtime(const time_t *timep, struct tm *result);
void f_string(typval_T *argvars, typval_T *rettv);
callback_T get_callback(typval_T *arg);
void put_callback(callback_T *cb, typval_T *tv);
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index a2e4da6c7..f71717f73 100644
--- 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
index dd4667001..ecbbbc429 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -3111,14 +3111,16 @@ 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
+# if defined(EVAL) || defined(HAVE_LOCALTIME_R)
struct tm tmval;
# endif
struct tm *curtime;
if (vim_time() - tt >= 100)
{
-# ifdef HAVE_LOCALTIME_R
+# if defined(EVAL)
+ curtime = vim_localtime(&tt, &tmval);
+# elif defined(HAVE_LOCALTIME_R)
curtime = localtime_r(&tt, &tmval);
# else
curtime = localtime(&tt);
--
2.22.0