]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/timeutils: add get_gmtoff()
authorJ William Piggott <elseifthen@gmx.com>
Sun, 8 Oct 2017 20:06:24 +0000 (16:06 -0400)
committerJ William Piggott <elseifthen@gmx.com>
Fri, 10 Nov 2017 21:14:04 +0000 (16:14 -0500)
This new function returns the GMT offset relative to its
argument. It is used in this patch to fix two bugs:

1) On platforms that the tm struct excludes tm_gmtoff,
   hwclock assumes a one hour DST offset. This can cause
   an incorrect kernel timezone setting. For example:

 Master branch tested with tm_gmtoff illustrates the correct offset:
$ TZ="Australia/Lord_Howe" hwclock --hctosys --test | grep settimeofday
Calling settimeofday(1507494204.192398, -660)

 Master branch tested without tm_gmtoff has an incorrect offset:
$ TZ="Australia/Lord_Howe" hwclock --hctosys --test | grep settimeofday
Calling settimeofday(1507494249.193852, -690)

 Patched tested without tm_gmtoff has the correct offset:
$ TZ="Australia/Lord_Howe" hwclock --hctosys --test | grep settimeofday
Calling settimeofday(1507494260.194208, -660)

2) ISO 8601 'extended' format requires all time elements
   to use a colon (:).

Current invalid ISO 8601:
$ hwclock
2017-10-08 16:25:17.895462-0400

Patched:
$ hwclock
2017-10-08 16:25:34.141895-04:00

Also required by this change:
login-utils/last.c: increase ISO out_len and in_len by one to
                    accommodate the addition of the timezone colon.

Signed-off-by: J William Piggott <elseifthen@gmx.com>
include/timeutils.h
lib/timeutils.c
login-utils/last.c
sys-utils/hwclock.c

index edd42f7fe8098eb3584997612c8adc2106c862c1..e8a26146242c9292bec10f9d23191fb6a7c459ca 100644 (file)
@@ -53,6 +53,7 @@ typedef uint64_t nsec_t;
 #define FORMAT_TIMESPAN_MAX 64
 
 int parse_timestamp(const char *t, usec_t *usec);
+int get_gmtoff(const struct tm *tp);
 
 /* flags for strxxx_iso() functions */
 enum {
index d38970c108cc1c3c39959f8c6fd02dbefaf1be2f..adc255c33c71a952feac4efa0b6789101ad51cf1 100644 (file)
@@ -341,6 +341,65 @@ int parse_timestamp(const char *t, usec_t *usec)
        return 0;
 }
 
+/* Returns the difference in seconds between its argument and GMT. If if TP is
+ * invalid or no DST information is available default to UTC, that is, zero.
+ * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
+ * Derived from glibc/time/strftime_l.c
+ */
+int get_gmtoff(const struct tm *tp)
+{
+       if (tp->tm_isdst < 0)
+       return 0;
+
+#if HAVE_TM_GMTOFF
+       return tp->tm_gmtoff;
+#else
+       struct tm tm;
+       struct tm gtm;
+       struct tm ltm = *tp;
+       time_t lt;
+
+       tzset();
+       lt = mktime(&ltm);
+       /* Check if mktime returning -1 is an error or a valid time_t */
+       if (lt == (time_t) -1) {
+               if (! localtime_r(&lt, &tm)
+                       || ((ltm.tm_sec ^ tm.tm_sec)
+                           | (ltm.tm_min ^ tm.tm_min)
+                           | (ltm.tm_hour ^ tm.tm_hour)
+                           | (ltm.tm_mday ^ tm.tm_mday)
+                           | (ltm.tm_mon ^ tm.tm_mon)
+                           | (ltm.tm_year ^ tm.tm_year)))
+                       return 0;
+       }
+
+       if (! gmtime_r(&lt, &gtm))
+               return 0;
+
+       /* Calculate the GMT offset, that is, the difference between the
+        * TP argument (ltm) and GMT (gtm).
+        *
+        * Compute intervening leap days correctly even if year is negative.
+        * Take care to avoid int overflow in leap day calculations, but it's OK
+        * to assume that A and B are close to each other.
+        */
+       int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
+       int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
+       int a100 = a4 / 25 - (a4 % 25 < 0);
+       int b100 = b4 / 25 - (b4 % 25 < 0);
+       int a400 = a100 >> 2;
+       int b400 = b100 >> 2;
+       int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+
+       int years = ltm.tm_year - gtm.tm_year;
+       int days = (365 * years + intervening_leap_days
+                   + (ltm.tm_yday - gtm.tm_yday));
+
+       return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
+               + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
+#endif
+}
+
 static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz)
 {
        char *p = buf;
@@ -386,9 +445,14 @@ static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf
                p += len;
        }
 
-       if (flags & ISO_8601_TIMEZONE && strftime(p, bufsz, "%z", tm) <= 0)
+       if (flags & ISO_8601_TIMEZONE) {
+               int tmin  = get_gmtoff(tm) / 60;
+               int zhour = tmin / 60;
+               int zmin  = abs(tmin % 60);
+               len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
+               if (len < 0 || (size_t) len > bufsz)
                return -1;
-
+       }
        return 0;
 }
 
index f989836ba3b187c0c2d56202f0261a0ba88e70eb..f2e8f834e84e9a8678f877d81b71ef11a82d9d90 100644 (file)
@@ -145,8 +145,8 @@ static struct last_timefmt timefmts[] = {
        },
        [LAST_TIMEFTM_ISO8601] = {
                .name    = "iso",
-               .in_len  = 24,
-               .out_len = 26,
+               .in_len  = 25,
+               .out_len = 27,
                .in_fmt  = LAST_TIMEFTM_ISO8601,
                .out_fmt = LAST_TIMEFTM_ISO8601
        }
index c2c20812c888944eac9f312ff01ffd69a0e73afe..3ac43efeed2ac2fd5fb5b1564b3bac04c7af7a60 100644 (file)
@@ -608,13 +608,7 @@ set_system_clock(const struct hwclock_control *ctl,
        const struct timezone tz_utc = { 0 };
 
        broken = localtime(&newtime.tv_sec);
-#ifdef HAVE_TM_GMTOFF
-       minuteswest = -broken->tm_gmtoff / 60;  /* GNU extension */
-#else
-       minuteswest = timezone / 60;
-       if (broken->tm_isdst)
-               minuteswest -= 60;
-#endif
+       minuteswest = -get_gmtoff(broken) / 60;
 
        if (ctl->debug) {
                if (ctl->hctosys && !ctl->universal)