Merge pull request #6788 from zerkms/TIMER_TIMEZONE
Timezone support for timers
This commit is contained in:
commit
7db3d9fe9b
|
@ -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,19 @@ 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 && timezone_is_valid(last_space + 1)) {
|
||||
c->timezone = strdup(last_space + 1);
|
||||
if (!c->timezone) {
|
||||
r = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
p = strndupa(p, last_space - p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1293,7 +1313,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 +1341,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;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ typedef struct CalendarSpec {
|
|||
bool end_of_month;
|
||||
bool utc;
|
||||
int dst;
|
||||
char *timezone;
|
||||
|
||||
CalendarComponent *year;
|
||||
CalendarComponent *month;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* 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();
|
||||
|
||||
/* 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++) {
|
||||
|
||||
if (isempty(tzname[j]))
|
||||
continue;
|
||||
|
||||
e = endswith_no_case(t, tzname[j]);
|
||||
if (!e)
|
||||
continue;
|
||||
if (e == t)
|
||||
continue;
|
||||
if (e[-1] != ' ')
|
||||
continue;
|
||||
|
||||
break;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (IN_SET(j, 0, 1)) {
|
||||
/* Found one of the two timezones specified. */
|
||||
t = strndupa(t, e - t - 1);
|
||||
dst = j;
|
||||
tzn = tzname[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();
|
||||
|
||||
/* 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++) {
|
||||
|
||||
if (isempty(tzname[j]))
|
||||
continue;
|
||||
|
||||
e = endswith_no_case(t, tzname[j]);
|
||||
if (!e)
|
||||
continue;
|
||||
if (e == t)
|
||||
continue;
|
||||
if (e[-1] != ' ')
|
||||
continue;
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -733,7 +736,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
|
|||
return -EINVAL;
|
||||
|
||||
tm.tm_isdst = dst;
|
||||
if (tzn)
|
||||
if (!with_tz && tzn)
|
||||
tm.tm_zone = tzn;
|
||||
|
||||
if (streq(t, "today")) {
|
||||
|
@ -874,6 +877,67 @@ 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 && 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -33,6 +33,12 @@ static void test_should_pass(const char *p) {
|
|||
log_info("\"%s\" → \"%s\"", p, buf);
|
||||
|
||||
assert_se(parse_timestamp(buf, &q) >= 0);
|
||||
if (q != t) {
|
||||
char tmp[FORMAT_TIMESTAMP_MAX];
|
||||
|
||||
log_error("round-trip failed: \"%s\" → \"%s\"",
|
||||
buf, format_timestamp_us(tmp, sizeof(tmp), q));
|
||||
}
|
||||
assert_se(q == t);
|
||||
|
||||
assert_se(format_timestamp_relative(buf_relative, sizeof(buf_relative), t));
|
||||
|
@ -77,6 +83,10 @@ static void test_one_noutc(const char *p) {
|
|||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
log_set_max_level(LOG_DEBUG);
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
test_one("17:41");
|
||||
test_one("18:42:44");
|
||||
test_one("18:42:44.0");
|
||||
|
@ -90,6 +100,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 +114,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");
|
||||
|
|
Loading…
Reference in a new issue