calendarspec: warn on weekday/date conflict in systemd-analyze and systemd-run

When a fixed date (e.g. 2027-01-01) is paired with a weekday constraint
(e.g. Thu) that does not match, the timer silently never elapses.

Add calendar_spec_from_string_full(..., warn_on_weekday_mismatch) so
user-facing tools can opt in to a log_warning() at parse time:
- systemd-analyze calendar: uses _full(true)
- systemd-run --on-calendar: uses _full(true)
- .timer OnCalendar=: uses log_syntax() with file/line context

Add test_calendar_spec_weekday_conflict(): forks a child with stderr
captured in a memfd via pidref_safe_fork_full(), verifies the warning
is emitted for conflicting specs and suppressed for valid ones.

Fixes: #40350
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
This commit is contained in:
dongshengyuan
2026-07-03 07:39:49 +02:00
committed by Lennart Poettering
parent 9d37c618ee
commit 21de611f69
8 changed files with 205 additions and 42 deletions
+18 -15
View File
@@ -321,24 +321,26 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) {
return tv;
}
/* Returns the abbreviated English weekday name for wd in Mon=0 … Sun=6 order.
* We use non-localized (English) form so that timestamps can be parsed with
* parse_timestamp() and always read the same regardless of locale. */
static const char *const weekday_table[_WEEKDAY_MAX] = {
[WEEKDAY_MON] = "Mon",
[WEEKDAY_TUE] = "Tue",
[WEEKDAY_WED] = "Wed",
[WEEKDAY_THU] = "Thu",
[WEEKDAY_FRI] = "Fri",
[WEEKDAY_SAT] = "Sat",
[WEEKDAY_SUN] = "Sun",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(weekday, int);
char* format_timestamp_style(
char *buf,
size_t l,
usec_t t,
TimestampStyle style) {
/* The weekdays in non-localized (English) form. We use this instead of the localized form, so that
* our generated timestamps may be parsed with parse_timestamp(), and always read the same. */
static const char * const weekdays[] = {
[0] = "Sun",
[1] = "Mon",
[2] = "Tue",
[3] = "Wed",
[4] = "Thu",
[5] = "Fri",
[6] = "Sat",
};
struct tm tm;
bool utc, us;
size_t n;
@@ -387,8 +389,9 @@ char* format_timestamp_style(
return NULL;
/* Start with the week day */
assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
memcpy(buf, weekdays[tm.tm_wday], 4);
const char *weekday = weekday_to_string(tm.tm_wday == 0 ? 6 : tm.tm_wday - 1);
assert(weekday);
memcpy(buf, weekday, 4);
if (style == TIMESTAMP_DATE) {
/* Special format string if only date should be shown. */
+16
View File
@@ -56,6 +56,18 @@ typedef enum TimestampStyle {
#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC))
#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC))
enum {
WEEKDAY_MON,
WEEKDAY_TUE,
WEEKDAY_WED,
WEEKDAY_THU,
WEEKDAY_FRI,
WEEKDAY_SAT,
WEEKDAY_SUN,
_WEEKDAY_MAX,
_WEEKDAY_INVALID = -EINVAL,
};
/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
* to 6. Let's rely on that. */
#define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U)
@@ -125,6 +137,10 @@ char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style
char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) _warn_unused_result_;
char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_;
/* Returns the abbreviated English weekday name for wd in Mon=0 … Sun=6 order
* (matching systemd's weekdays_bits layout). */
const char* weekday_to_string(int i);
_warn_unused_result_
static inline char* format_timestamp_relative(char *buf, size_t l, usec_t t) {
return format_timestamp_relative_full(buf, l, t, CLOCK_REALTIME, /* implicit_left= */ false);