]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
musl: time-util: make parse_gmtoff() accept extended timezone offset format
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 8 Sep 2025 23:31:22 +0000 (08:31 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 12 Nov 2025 17:40:32 +0000 (02:40 +0900)
musl v1.2.5 does not support %z specifier in strptime(). Since
https://github.com/kraj/musl/commit/fced99e93daeefb0192fd16304f978d4401d1d77
%z is supported, but it only supports strict RFC-822/ISO 8601 format,
that is, 4 digits with sign (e.g. +0900 or -1400), but does not support
extended format: 2 digits or colon separated 4 digits (e.g. +09 or -14:00).
Let's add fallback logic to make it support the extended timezone spec.

src/basic/time-util.c

index e54c829623e26363083a691e7ae2514dc47d8620..c09c9fd6d0217bb9cd7cc6b555afe9a5d0d6950f 100644 (file)
@@ -13,6 +13,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "hexdecoct.h"
 #include "io-util.h"
 #include "log.h"
 #include "parse-util.h"
@@ -625,15 +626,85 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
 }
 
 int parse_gmtoff(const char *t, long *ret) {
+        int r;
+
         assert(t);
 
         struct tm tm;
         const char *k = strptime(t, "%z", &tm);
-        if (!k || *k != '\0')
+        if (k && *k == '\0') {
+                /* Success! */
+                if (ret)
+                        *ret = tm.tm_gmtoff;
+                return 0;
+        }
+
+        /* musl v1.2.5 does not support %z specifier in strptime(). Since
+         * https://github.com/kraj/musl/commit/fced99e93daeefb0192fd16304f978d4401d1d77
+         * %z is supported, but it only supports strict RFC-822/ISO 8601 format, that is, 4 digits with sign
+         * (e.g. +0900 or -1400), but does not support extended format: 2 digits or colon separated 4 digits
+         * (e.g. +09 or -14:00). Let's add fallback logic to make it support the extended timezone spec. */
+
+        bool positive;
+        switch (*t) {
+        case '+':
+                positive = true;
+                break;
+        case '-':
+                positive = false;
+                break;
+        default:
                 return -EINVAL;
+        }
+
+        t++;
+        r = undecchar(*t);
+        if (r < 0)
+                return r;
+
+        usec_t u = r * 10 * USEC_PER_HOUR;
+
+        t++;
+        r = undecchar(*t);
+        if (r < 0)
+                return r;
+        u += r * USEC_PER_HOUR;
+
+        t++;
+        if (*t == '\0') /* 2 digits case */
+                goto finalize;
+
+        if (*t == ':') /* skip colon */
+                t++;
+
+        r = undecchar(*t);
+        if (r < 0)
+                return r;
+        if (r >= 6) /* refuse minutes equal to or larger than 60 */
+                return -EINVAL;
+
+        u += r * 10 * USEC_PER_MINUTE;
+
+        t++;
+        r = undecchar(*t);
+        if (r < 0)
+                return r;
+
+        u += r * USEC_PER_MINUTE;
+
+        t++;
+        if (*t != '\0')
+                return -EINVAL;
+
+finalize:
+        if (u > USEC_PER_DAY) /* refuse larger than one day */
+                return -EINVAL;
+
+        if (ret) {
+                long gmtoff = u / USEC_PER_SEC;
+                *ret = positive ? gmtoff : -gmtoff;
+        }
 
-        if (ret)
-                *ret = tm.tm_gmtoff;
         return 0;
 }