1
0
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:
Václav Ovsík 2025-03-13 05:48:27 +01:00 committed by GitHub
commit 87f74dcb5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 164 additions and 1 deletions

View File

@ -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>

View File

@ -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)

View File

@ -22,6 +22,7 @@ typedef struct CalendarSpec {
bool utc:1;
signed int dst:2;
char *timezone;
usec_t period;
CalendarComponent *year;
CalendarComponent *month;

View File

@ -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) {