summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Larsson <lukas@erlang.org>2022-04-19 10:20:57 +0200
committerLukas Larsson <lukas@erlang.org>2022-04-19 10:20:57 +0200
commit5ee1d5909e9b0ce02dc5334f7f03f3ec3c7565f3 (patch)
tree71ed99c6c6e96e2a47eb6e10af0cc4edbec94555
parent6d5a5f31c36bbdaad21585d25974177bd1b75e66 (diff)
downloaderlang-5ee1d5909e9b0ce02dc5334f7f03f3ec3c7565f3.tar.gz
erts: Fix localtime_r summer/winter-time change bug
According to POSIX, localtime_r does not have to update the process internal tz data when called. So if time went from winter to summertime erts is running localtime_r may return an incorrect time. So we need to make sure to call tzset before each call to localtime_r. localtime is guaranteed to update the tz information, so no need to call it when using localtime.
-rw-r--r--erts/emulator/beam/erl_time_sup.c51
-rw-r--r--erts/emulator/sys/unix/erl_unix_sys.h21
-rw-r--r--erts/emulator/sys/unix/sys.c1
-rw-r--r--erts/emulator/sys/win32/erl_win_sys.h2
4 files changed, 33 insertions, 42 deletions
diff --git a/erts/emulator/beam/erl_time_sup.c b/erts/emulator/beam/erl_time_sup.c
index d26ea19494..f262f0e5be 100644
--- a/erts/emulator/beam/erl_time_sup.c
+++ b/erts/emulator/beam/erl_time_sup.c
@@ -1396,17 +1396,10 @@ void
get_time(int *hour, int *minute, int *second)
{
time_t the_clock;
- struct tm *tm;
-#ifdef HAVE_LOCALTIME_R
- struct tm tmbuf;
-#endif
-
+ struct tm *tm, tmbuf;
+
the_clock = time((time_t *)0);
-#ifdef HAVE_LOCALTIME_R
- tm = localtime_r(&the_clock, &tmbuf);
-#else
- tm = localtime(&the_clock);
-#endif
+ tm = sys_localtime_r(&the_clock, &tmbuf);
*hour = tm->tm_hour;
*minute = tm->tm_min;
*second = tm->tm_sec;
@@ -1417,18 +1410,11 @@ void
get_date(int *year, int *month, int *day)
{
time_t the_clock;
- struct tm *tm;
-#ifdef HAVE_LOCALTIME_R
- struct tm tmbuf;
-#endif
+ struct tm *tm, tmbuf;
the_clock = time((time_t *)0);
-#ifdef HAVE_LOCALTIME_R
- tm = localtime_r(&the_clock, &tmbuf);
-#else
- tm = localtime(&the_clock);
-#endif
+ tm = sys_localtime_r(&the_clock, &tmbuf);
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
@@ -1440,17 +1426,10 @@ get_localtime(int *year, int *month, int *day,
int *hour, int *minute, int *second)
{
time_t the_clock;
- struct tm *tm;
-#ifdef HAVE_LOCALTIME_R
- struct tm tmbuf;
-#endif
+ struct tm *tm, tmbuf;
the_clock = time((time_t *)0);
-#ifdef HAVE_LOCALTIME_R
- localtime_r(&the_clock, (tm = &tmbuf));
-#else
- tm = localtime(&the_clock);
-#endif
+ tm = sys_localtime_r(&the_clock, &tmbuf);
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
@@ -1711,11 +1690,8 @@ univ_to_local(Sint *year, Sint *month, Sint *day,
Sint *hour, Sint *minute, Sint *second)
{
time_t the_clock;
- struct tm *tm;
-#ifdef HAVE_LOCALTIME_R
- struct tm tmbuf;
-#endif
-
+ struct tm *tm, tmbuf;
+
if (!(IN_RANGE(BASEYEAR, *year, INT_MAX - 1) &&
IN_RANGE(1, *month, 12) &&
IN_RANGE(1, *day, (mdays[*month] +
@@ -1727,7 +1703,7 @@ univ_to_local(Sint *year, Sint *month, Sint *day,
IN_RANGE(0, *second, 59))) {
return 0;
}
-
+
the_clock = *second + 60 * (*minute + 60 * (*hour + 24 *
gregday(*year, *month, *day)));
#ifdef HAVE_POSIX2TIME
@@ -1745,11 +1721,8 @@ univ_to_local(Sint *year, Sint *month, Sint *day,
the_clock = posix2time(the_clock);
#endif
-#ifdef HAVE_LOCALTIME_R
- tm = localtime_r(&the_clock, &tmbuf);
-#else
- tm = localtime(&the_clock);
-#endif
+ tm = sys_localtime_r(&the_clock, &tmbuf);
+
if (tm) {
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
diff --git a/erts/emulator/sys/unix/erl_unix_sys.h b/erts/emulator/sys/unix/erl_unix_sys.h
index ae7a3ea23e..768f412c20 100644
--- a/erts/emulator/sys/unix/erl_unix_sys.h
+++ b/erts/emulator/sys/unix/erl_unix_sys.h
@@ -318,6 +318,27 @@ int sys_stop_hrvtime(void);
/* No use in having other resolutions than 1 Ms. */
#define SYS_CLOCK_RESOLUTION 1
+ERTS_GLB_INLINE struct tm *sys_localtime_r(time_t *the_clock, struct tm *buff);
+
+#if ERTS_GLB_INLINE_INCL_FUNC_DEF
+
+ERTS_GLB_INLINE struct tm *sys_localtime_r(time_t *the_clock, struct tm *buff) {
+#ifdef HAVE_LOCALTIME_R
+ tzset(); /* POSIX.1-2004 does not require tzset to be called within
+ localtime_r, so if summer/winter-time has passed localtime_r
+ will return the time when Erlang was started. So we need to
+ call tzset() before all localtime_r calls.
+
+ localtime already calls tzset for each call, so the performance
+ penalty should be acceptable.... */
+ return localtime_r(the_clock, buff);
+#else
+ return localtime(the_clock);
+#endif
+}
+
+#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */
+
/* These are defined in sys.c */
typedef void (*SIGFUNC)(int);
extern SIGFUNC sys_signal(int, SIGFUNC);
diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c
index 6935c0cca9..88e4cff800 100644
--- a/erts/emulator/sys/unix/sys.c
+++ b/erts/emulator/sys/unix/sys.c
@@ -335,7 +335,6 @@ erl_sys_init(void)
if (isatty(0)) {
tcgetattr(0,&initial_tty_mode);
}
- tzset(); /* Required at least for NetBSD with localtime_r() */
}
/* signal handling */
diff --git a/erts/emulator/sys/win32/erl_win_sys.h b/erts/emulator/sys/win32/erl_win_sys.h
index b00ba287e2..8b6947c4e0 100644
--- a/erts/emulator/sys/win32/erl_win_sys.h
+++ b/erts/emulator/sys/win32/erl_win_sys.h
@@ -140,8 +140,6 @@ struct tm *sys_localtime_r(time_t *epochs, struct tm *ptm);
struct tm *sys_gmtime_r(time_t *epochs, struct tm *ptm);
time_t sys_mktime( struct tm *ptm);
-#define localtime_r sys_localtime_r
-#define HAVE_LOCALTIME_R 1
#define gmtime_r sys_gmtime_r
#define HAVE_GMTIME_R
#define mktime sys_mktime