]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Added timezone to the CalendarSpec, parser/formatter and the timedatectl
authorIvan Kurnosov <zerkms@zerkms.com>
Wed, 6 Sep 2017 09:56:36 +0000 (21:56 +1200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sun, 17 Sep 2017 07:42:20 +0000 (09:42 +0200)
src/basic/calendarspec.c
src/basic/calendarspec.h
src/basic/time-util.c
src/test/test-calendarspec.c
src/test/test-date.c

index b0712a4bada7e44c2b3dc5c315169a351dcde9d5..0606f0923fea3e4783440dd1bf72ead62978363f 100644 (file)
@@ -25,6 +25,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <time.h>
 
 #include "alloc-util.h"
@@ -33,6 +34,7 @@
 #include "macro.h"
 #include "parse-util.h"
 #include "string-util.h"
+#include "time-util.h"
 
 #define BITS_WEEKDAYS 127
 #define MIN_YEAR 1970
@@ -59,6 +61,7 @@ void calendar_spec_free(CalendarSpec *c) {
         free_chain(c->hour);
         free_chain(c->minute);
         free_chain(c->microsecond);
+        free(c->timezone);
 
         free(c);
 }
@@ -351,7 +354,10 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
 
         if (c->utc)
                 fputs_unlocked(" UTC", f);
-        else if (IN_SET(c->dst, 0, 1)) {
+        else if (c->timezone != NULL) {
+                fputc_unlocked(' ', f);
+                fputs_unlocked(c->timezone, f);
+        } else if (IN_SET(c->dst, 0, 1)) {
 
                 /* If daylight saving is explicitly on or off, let's show the used timezone. */
 
@@ -888,6 +894,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
         if (!c)
                 return -ENOMEM;
         c->dst = -1;
+        c->timezone = NULL;
 
         utc = endswith_no_case(p, " UTC");
         if (utc) {
@@ -919,6 +926,22 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 if (IN_SET(j, 0, 1)) {
                         p = strndupa(p, e - p - 1);
                         c->dst = j;
+                } else {
+                        const char *last_space;
+                        last_space = strrchr(p, ' ');
+
+                        if (last_space != NULL) {
+                                const char *timezone = last_space + 1;
+
+                                if (timezone_is_valid(timezone)) {
+                                        c->timezone = strdup(timezone);
+                                        if (!c->timezone) {
+                                                r = -ENOMEM;
+                                                goto fail;
+                                        }
+                                        p = strndupa(p, last_space - p);
+                                }
+                        }
                 }
         }
 
@@ -1293,7 +1316,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
         }
 }
 
-int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) {
         struct tm tm;
         time_t t;
         int r;
@@ -1321,3 +1344,58 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
         *next = (usec_t) t * USEC_PER_SEC + tm_usec;
         return 0;
 }
+
+typedef struct SpecNextResult {
+        usec_t next;
+        int return_value;
+} SpecNextResult;
+
+int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
+        pid_t pid;
+        SpecNextResult *shared;
+        SpecNextResult tmp;
+        int r;
+
+        if (isempty(spec->timezone))
+                return calendar_spec_next_usec_impl(spec, usec, next);
+
+        shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+        if (shared == MAP_FAILED)
+                return negative_errno();
+
+        pid = fork();
+
+        if (pid == -1) {
+                int fork_errno = errno;
+                (void) munmap(shared, sizeof *shared);
+                return -fork_errno;
+        }
+
+        if (pid == 0) {
+                if (setenv("TZ", spec->timezone, 1) != 0) {
+                        shared->return_value = negative_errno();
+                        _exit(EXIT_FAILURE);
+                }
+
+                tzset();
+
+                shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        r = wait_for_terminate(pid, NULL);
+        if (r < 0) {
+                (void) munmap(shared, sizeof *shared);
+                return r;
+        }
+
+        tmp = *shared;
+        if (munmap(shared, sizeof *shared) != 0)
+                return negative_errno();
+
+        if (tmp.return_value == 0)
+                *next = tmp.next;
+
+        return tmp.return_value;
+}
index 3d8798de0b08e0834bdd3d6dd081da329430f4d1..8888251705f491710ff1e702eb1e0804c2843608 100644 (file)
@@ -40,6 +40,7 @@ typedef struct CalendarSpec {
         bool end_of_month;
         bool utc;
         int dst;
+        char *timezone;
 
         CalendarComponent *year;
         CalendarComponent *month;
index 68ba86f6a55902ced6484646a325c03fc54fc84d..d6acdcea2c170ab5be3189a3a7abb71aea9cfc85 100644 (file)
@@ -21,6 +21,7 @@
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/timerfd.h>
@@ -596,7 +597,7 @@ int timestamp_deserialize(const char *value, usec_t *timestamp) {
         return r;
 }
 
-int parse_timestamp(const char *t, usec_t *usec) {
+static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
         static const struct {
                 const char *name;
                 const int nr;
@@ -617,7 +618,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 { "Sat",       6 },
         };
 
-        const char *k, *utc, *tzn = NULL;
+        const char *k, *utc = NULL, *tzn = NULL;
         struct tm tm, copy;
         time_t x;
         usec_t x_usec, plus = 0, minus = 0, ret;
@@ -645,84 +646,86 @@ int parse_timestamp(const char *t, usec_t *usec) {
         assert(t);
         assert(usec);
 
-        if (t[0] == '@')
+        if (t[0] == '@' && !with_tz)
                 return parse_sec(t + 1, usec);
 
         ret = now(CLOCK_REALTIME);
 
-        if (streq(t, "now"))
-                goto finish;
+        if (!with_tz) {
+                if (streq(t, "now"))
+                        goto finish;
 
-        else if (t[0] == '+') {
-                r = parse_sec(t+1, &plus);
-                if (r < 0)
-                        return r;
+                else if (t[0] == '+') {
+                        r = parse_sec(t+1, &plus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
+                        goto finish;
 
-        } else if (t[0] == '-') {
-                r = parse_sec(t+1, &minus);
-                if (r < 0)
-                        return r;
+                } else if (t[0] == '-') {
+                        r = parse_sec(t+1, &minus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
+                        goto finish;
 
-        } else if ((k = endswith(t, " ago"))) {
-                t = strndupa(t, k - t);
+                } else if ((k = endswith(t, " ago"))) {
+                        t = strndupa(t, k - t);
 
-                r = parse_sec(t, &minus);
-                if (r < 0)
-                        return r;
+                        r = parse_sec(t, &minus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
+                        goto finish;
 
-        } else if ((k = endswith(t, " left"))) {
-                t = strndupa(t, k - t);
+                } else if ((k = endswith(t, " left"))) {
+                        t = strndupa(t, k - t);
 
-                r = parse_sec(t, &plus);
-                if (r < 0)
-                        return r;
+                        r = parse_sec(t, &plus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
-        }
+                        goto finish;
+                }
 
-        /* See if the timestamp is suffixed with UTC */
-        utc = endswith_no_case(t, " UTC");
-        if (utc)
-                t = strndupa(t, utc - t);
-        else {
-                const char *e = NULL;
-                int j;
+                /* See if the timestamp is suffixed with UTC */
+                utc = endswith_no_case(t, " UTC");
+                if (utc)
+                        t = strndupa(t, utc - t);
+                else {
+                        const char *e = NULL;
+                        int j;
 
-                tzset();
+                        tzset();
 
-                /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
-                 * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
-                 * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
-                 * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
-                 * support arbitrary timezone specifications.  */
+                        /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
+                        * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
+                        * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
+                        * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
+                        * support arbitrary timezone specifications.  */
 
-                for (j = 0; j <= 1; j++) {
+                        for (j = 0; j <= 1; j++) {
 
-                        if (isempty(tzname[j]))
-                                continue;
+                                if (isempty(tzname[j]))
+                                        continue;
 
-                        e = endswith_no_case(t, tzname[j]);
-                        if (!e)
-                                continue;
-                        if (e == t)
-                                continue;
-                        if (e[-1] != ' ')
-                                continue;
+                                e = endswith_no_case(t, tzname[j]);
+                                if (!e)
+                                        continue;
+                                if (e == t)
+                                        continue;
+                                if (e[-1] != ' ')
+                                        continue;
 
-                        break;
-                }
+                                break;
+                        }
 
-                if (IN_SET(j, 0, 1)) {
-                        /* Found one of the two timezones specified. */
-                        t = strndupa(t, e - t - 1);
-                        dst = j;
-                        tzn = tzname[j];
+                        if (IN_SET(j, 0, 1)) {
+                                /* Found one of the two timezones specified. */
+                                t = strndupa(t, e - t - 1);
+                                dst = j;
+                                tzn = tzname[j];
+                        }
                 }
         }
 
@@ -732,9 +735,11 @@ int parse_timestamp(const char *t, usec_t *usec) {
         if (!localtime_or_gmtime_r(&x, &tm, utc))
                 return -EINVAL;
 
-        tm.tm_isdst = dst;
-        if (tzn)
-                tm.tm_zone = tzn;
+        if (!with_tz) {
+                tm.tm_isdst = dst;
+                if (tzn)
+                        tm.tm_zone = tzn;
+        }
 
         if (streq(t, "today")) {
                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
@@ -874,6 +879,71 @@ finish:
         return 0;
 }
 
+typedef struct ParseTimestampResult {
+        usec_t usec;
+        int return_value;
+} ParseTimestampResult;
+
+int parse_timestamp(const char *t, usec_t *usec) {
+        char *last_space, *timezone = NULL;
+        ParseTimestampResult *shared, tmp;
+        int r;
+        pid_t pid;
+
+        last_space = strrchr(t, ' ');
+
+        if (last_space != NULL) {
+                if (timezone_is_valid(last_space + 1)) {
+                        timezone = last_space + 1;
+                }
+        }
+
+        if (timezone == NULL || endswith_no_case(t, " UTC"))
+                return parse_timestamp_impl(t, usec, false);
+
+        t = strndupa(t, last_space - t);
+
+        shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+        if (shared == MAP_FAILED)
+                return negative_errno();
+
+        pid = fork();
+
+        if (pid == -1) {
+                int fork_errno = errno;
+                (void) munmap(shared, sizeof *shared);
+                return -fork_errno;
+        }
+
+        if (pid == 0) {
+                if (setenv("TZ", timezone, 1) != 0) {
+                        shared->return_value = negative_errno();
+                        _exit(EXIT_FAILURE);
+                }
+
+                tzset();
+
+                shared->return_value = parse_timestamp_impl(t, &shared->usec, true);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        r = wait_for_terminate(pid, NULL);
+        if (r < 0) {
+                (void) munmap(shared, sizeof *shared);
+                return r;
+        }
+
+        tmp = *shared;
+        if (munmap(shared, sizeof *shared) != 0)
+                return negative_errno();
+
+        if (tmp.return_value == 0)
+                *usec = tmp.usec;
+
+        return tmp.return_value;
+}
+
 static char* extract_multiplier(char *p, usec_t *multiplier) {
         static const struct {
                 const char *suffix;
index a026ce4ef12d2370aee361d01d33391d7d0282f2..bd5bebe66d7f5d72f0be92001d021171edfdd24a 100644 (file)
@@ -170,6 +170,8 @@ int main(int argc, char* argv[]) {
         test_one("annually", "*-01-01 00:00:00");
         test_one("*:2/3", "*-*-* *:02/3:00");
         test_one("2015-10-25 01:00:00 uTc", "2015-10-25 01:00:00 UTC");
+        test_one("2015-10-25 01:00:00 Asia/Vladivostok", "2015-10-25 01:00:00 Asia/Vladivostok");
+        test_one("weekly Pacific/Auckland", "Mon *-*-* 00:00:00 Pacific/Auckland");
         test_one("2016-03-27 03:17:00.4200005", "2016-03-27 03:17:00.420001");
         test_one("2016-03-27 03:17:00/0.42", "2016-03-27 03:17:00/0.420000");
         test_one("9..11,13:00,30", "*-*-* 09..11,13:00,30:00");
@@ -219,6 +221,16 @@ int main(int argc, char* argv[]) {
         test_next("2017-08-06 9,11,13,15,17:00 UTC", "", 1502029800000000, 1502031600000000);
         test_next("2017-08-06 9..17/2:00 UTC", "", 1502029800000000, 1502031600000000);
         test_next("2016-12-* 3..21/6:00 UTC", "", 1482613200000001, 1482634800000000);
+        test_next("2017-09-24 03:30:00 Pacific/Auckland", "", 12345, 1506177000000000);
+        // Due to daylight saving time - 2017-09-24 02:30:00 does not exist
+        test_next("2017-09-24 02:30:00 Pacific/Auckland", "", 12345, -1);
+        test_next("2017-04-02 02:30:00 Pacific/Auckland", "", 12345, 1491053400000000);
+        // Confirm that even though it's a time change here (backward) 02:30 happens only once
+        test_next("2017-04-02 02:30:00 Pacific/Auckland", "", 1491053400000000, -1);
+        test_next("2017-04-02 03:30:00 Pacific/Auckland", "", 12345, 1491060600000000);
+        // Confirm that timezones in the Spec work regardless of current timezone
+        test_next("2017-09-09 20:42:00 Pacific/Auckland", "", 12345, 1504946520000000);
+        test_next("2017-09-09 20:42:00 Pacific/Auckland", "EET", 12345, 1504946520000000);
 
         assert_se(calendar_spec_from_string("test", &c) < 0);
         assert_se(calendar_spec_from_string(" utc", &c) < 0);
index 0e7d44fade9f5d1e03e3d82c7d5642b7e642027c..5cbb8bcc3ce0446ecdd35fe0550b8c08fbba3434 100644 (file)
@@ -90,6 +90,10 @@ int main(int argc, char *argv[]) {
         test_one("yesterday");
         test_one("today");
         test_one("tomorrow");
+        test_one_noutc("16:20 UTC");
+        test_one_noutc("16:20 Asia/Seoul");
+        test_one_noutc("tomorrow Asia/Seoul");
+        test_one_noutc("2012-12-30 18:42 Asia/Seoul");
         test_one_noutc("now");
         test_one_noutc("+2d");
         test_one_noutc("+2y 4d");
@@ -100,6 +104,9 @@ int main(int argc, char *argv[]) {
         test_should_fail("1969-12-31 UTC");
         test_should_fail("-100y");
         test_should_fail("today UTC UTC");
+        test_should_fail("now Asia/Seoul");
+        test_should_fail("+2d Asia/Seoul");
+        test_should_fail("@1395716396 Asia/Seoul");
 #if SIZEOF_TIME_T == 8
         test_should_pass("9999-12-30 23:59:59 UTC");
         test_should_fail("9999-12-31 00:00:00 UTC");