mirror of
https://github.com/systemd/systemd.git
synced 2025-03-19 22:50:17 +03:00
Merge 968b9ee66efc2f7a8a43a66c98e25a4f397a0ecb into fdab24bf6acc62d3011f9b5abdf834b4886642b2
This commit is contained in:
commit
87f74dcb5e
@ -255,6 +255,13 @@ tomorrow Pacific/Auckland → Thu 2012-11-23 19:00:00
|
||||
the local timezone, similar to the supported syntax of timestamps (see above), or the timezone
|
||||
in the IANA timezone database format (also see above).</para>
|
||||
|
||||
<para>There is a special format for periodic events in the form
|
||||
<literal>«timestamp» and every «timespan»</literal> to describe general
|
||||
periodic intervals. For example timespan <literal>2w</literal>, <literal>3w</literal>, <literal>2d</literal>…
|
||||
The reference point for such an interval must be a single point in time
|
||||
(timestamp). This syntax can't be combined with previously described wild-card
|
||||
formats. The minimum value for time period is <literal>60s</literal>.</para>
|
||||
|
||||
<para>The following special expressions may be used as shorthands for longer normalized forms:</para>
|
||||
|
||||
<programlisting> minutely → *-*-* *:*:00
|
||||
@ -302,7 +309,8 @@ Wed..Sat,Tue 12-10-15 1:2:3 → Tue..Sat 2012-10-15 01:02:03
|
||||
weekly Pacific/Auckland → Mon *-*-* 00:00:00 Pacific/Auckland
|
||||
yearly → *-01-01 00:00:00
|
||||
annually → *-01-01 00:00:00
|
||||
*:2/3 → *-*-* *:02/3:00</programlisting>
|
||||
*:2/3 → *-*-* *:02/3:00
|
||||
25-1-1 10 CET and every 2w → 2025-01-01 09:00:00 UTC and every 2w</programlisting>
|
||||
|
||||
<para>Calendar events are used by timer units, see
|
||||
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
|
@ -26,6 +26,7 @@
|
||||
#define BITS_WEEKDAYS 127
|
||||
#define MIN_YEAR 1970
|
||||
#define MAX_YEAR 2199
|
||||
#define MIN_PERIOD (60 * USEC_PER_SEC)
|
||||
|
||||
/* An arbitrary limit on the length of the chains of components. We don't want to
|
||||
* build a very long linked list, which would be slow to iterate over and might cause
|
||||
@ -382,6 +383,13 @@ int calendar_spec_to_string(const CalendarSpec *c, char **ret) {
|
||||
}
|
||||
}
|
||||
|
||||
if (c->period) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
fputs(" and every ", f);
|
||||
(void) format_timespan(buf, sizeof(buf), c->period, 0);
|
||||
fputs(buf, f);
|
||||
}
|
||||
|
||||
return memstream_finalize(&m, ret, NULL);
|
||||
}
|
||||
|
||||
@ -620,6 +628,39 @@ static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_single_value_component(CalendarComponent *cc) {
|
||||
assert(cc);
|
||||
return cc->stop == -1 && cc->repeat == 0 && cc->next == NULL;
|
||||
}
|
||||
|
||||
static int calendarspec_to_usec_t(const CalendarSpec *c, usec_t *t) {
|
||||
struct tm tm;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(is_single_value_component(c->year));
|
||||
assert(is_single_value_component(c->month));
|
||||
assert(is_single_value_component(c->day));
|
||||
assert(is_single_value_component(c->hour));
|
||||
assert(is_single_value_component(c->minute));
|
||||
assert(is_single_value_component(c->microsecond));
|
||||
|
||||
tm.tm_year = c->year->start - 1900;
|
||||
tm.tm_mon = c->month->start - 1;
|
||||
tm.tm_mday = c->day->start;
|
||||
tm.tm_hour = c->hour->start;
|
||||
tm.tm_min = c->minute->start;
|
||||
tm.tm_sec = c->microsecond->start / USEC_PER_SEC;
|
||||
tm.tm_isdst = -1;
|
||||
|
||||
r = mktime_or_timegm_usec(&tm, c->utc, t);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (t)
|
||||
*t += c->microsecond->start % USEC_PER_SEC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
|
||||
int r, start, stop = -1, repeat = 0;
|
||||
CalendarComponent *cc;
|
||||
@ -872,6 +913,38 @@ finish:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_and_every(const char *s, usec_t *ts, usec_t *rep) {
|
||||
_cleanup_free_ char *p_ts = NULL;
|
||||
int i, r;
|
||||
|
||||
char const *p = strcasestr(s, " and ");
|
||||
if ( !p )
|
||||
return -EINVAL;
|
||||
|
||||
p_ts = strndup(s, p - s);
|
||||
i = strlen(p_ts);
|
||||
while ( i > 0 && isspace(p_ts[--i]) )
|
||||
p_ts[i] = (char)0;
|
||||
|
||||
r = parse_timestamp(p_ts, ts);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p += strspn(p, " ");
|
||||
if ( !(p = startswith_no_case(p, "and ")) )
|
||||
return -EINVAL;
|
||||
p += strspn(p, " ");
|
||||
if ( !(p = startswith_no_case(p, "every ")) )
|
||||
return -EINVAL;
|
||||
p += strspn(p, " ");
|
||||
|
||||
r = parse_time(p, rep, USEC_PER_SEC);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int calendar_spec_from_string(const char *p, CalendarSpec **ret) {
|
||||
const char *utc;
|
||||
_cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
|
||||
@ -889,6 +962,21 @@ int calendar_spec_from_string(const char *p, CalendarSpec **ret) {
|
||||
.timezone = NULL,
|
||||
};
|
||||
|
||||
if (strcasestr(p, " and ")) {
|
||||
usec_t ts;
|
||||
r = parse_and_every(p, &ts, &(c->period));
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (c->period < MIN_PERIOD)
|
||||
return -ERANGE;
|
||||
r = calendarspec_from_time_t(c, (time_t)(ts / USEC_PER_SEC));
|
||||
if (r < 0)
|
||||
return r;
|
||||
c->microsecond->start += ts % USEC_PER_SEC;
|
||||
c->weekdays_bits = -1;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
utc = endswith_no_case(p, " UTC");
|
||||
if (utc) {
|
||||
c->utc = true;
|
||||
@ -1089,6 +1177,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **ret) {
|
||||
|
||||
calendar_spec_normalize(c);
|
||||
|
||||
finish:
|
||||
if (!calendar_spec_valid(c))
|
||||
return -EINVAL;
|
||||
|
||||
@ -1359,6 +1448,67 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
|
||||
"Infinite loop in calendar calculation: %s", strna(s));
|
||||
}
|
||||
|
||||
static int usec_dstoffset_sec(usec_t usec, time_t *ret_dstoffset) {
|
||||
struct tm tm;
|
||||
time_t t = usec / USEC_PER_SEC;
|
||||
if ( !localtime_r(&t, &tm) )
|
||||
return -EINVAL;
|
||||
if ( tm.tm_isdst) {
|
||||
tm.tm_isdst = 0;
|
||||
time_t tmp = mktime(&tm);
|
||||
if ( tmp == -1 )
|
||||
return -EINVAL;
|
||||
*ret_dstoffset = tmp - t;
|
||||
} else {
|
||||
*ret_dstoffset = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int calendar_spec_next_usec_period(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
|
||||
usec_t ts;
|
||||
int r = calendarspec_to_usec_t(spec, &ts);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (usec < ts) {
|
||||
/* The timestamp is in the future. The first event of the period. */
|
||||
*ret_next = ts;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( spec->period % (24 * 3600 * USEC_PER_SEC) ) {
|
||||
/* The period is not multiple of the whole day.
|
||||
* The next event is on the multiple of the period duration. */
|
||||
*ret_next = ts + ( (usec - ts) / spec->period +1 ) * spec->period;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The period is multiple of the whole day. We need to compensate daylight
|
||||
* saving. The repeating events should be at the same time of day according to
|
||||
* daylight saving. We need to test up to three points around and detect
|
||||
* localtime discontinuity on change of DST/Non DST localtime mode. */
|
||||
time_t dstoffset;
|
||||
if ( (r = usec_dstoffset_sec(ts, &dstoffset)) )
|
||||
return r;
|
||||
const size_t NRT = 3;
|
||||
size_t i;
|
||||
usec_t u = ts + ( (usec - ts) / spec->period ) * spec->period;
|
||||
for(i = 0; i < NRT; i++) {
|
||||
time_t dstoffset2;
|
||||
r = usec_dstoffset_sec(u, &dstoffset2);
|
||||
if (r < 0)
|
||||
return r;
|
||||
u += ( dstoffset - dstoffset2 ) * USEC_PER_SEC;
|
||||
if ( usec < u ) {
|
||||
*ret_next = u;
|
||||
return 0;
|
||||
}
|
||||
dstoffset = dstoffset2;
|
||||
u += spec->period;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
|
||||
usec_t tm_usec;
|
||||
struct tm tm;
|
||||
@ -1369,6 +1519,9 @@ static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, u
|
||||
if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if ( spec->period )
|
||||
return calendar_spec_next_usec_period(spec, usec, ret_next);
|
||||
|
||||
usec++;
|
||||
r = localtime_or_gmtime_usec(usec, spec->utc, &tm);
|
||||
if (r < 0)
|
||||
|
@ -22,6 +22,7 @@ typedef struct CalendarSpec {
|
||||
bool utc:1;
|
||||
signed int dst:2;
|
||||
char *timezone;
|
||||
usec_t period;
|
||||
|
||||
CalendarComponent *year;
|
||||
CalendarComponent *month;
|
||||
|
@ -181,6 +181,7 @@ TEST(calendar_spec_one) {
|
||||
test_one("@0 UTC", "1970-01-01 00:00:00 UTC");
|
||||
test_one("*:05..05", "*-*-* *:05:00");
|
||||
test_one("*:05..10/6", "*-*-* *:05:00");
|
||||
test_one("2025-3-1 10:0 UTC and every 14d", "2025-03-01 10:00:00 UTC and every 2w");
|
||||
}
|
||||
|
||||
TEST(calendar_spec_next) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user