diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index abc05e9303..651a73848e 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -93,7 +93,13 @@ systemd-analyze OPTIONS calendar - SPECS + SPEC + + + systemd-analyze + OPTIONS + timestamp + TIMESTAMP systemd-analyze @@ -360,7 +366,8 @@ $ eog targets.svg systemd.time7. By default, only the next time the calendar expression will elapse is shown; use to show the specified number of next times the expression - elapses. + elapses. Each time the expression elapses forms a timestamp, see the timestamp + verb below. Show leap days in the near future @@ -382,13 +389,47 @@ Normalized form: *-02-29 00:00:00 + + <command>systemd-analyze timestamp <replaceable>TIMESTAMP</replaceable>...</command> + + This command parses a timestamp (i.e. a single point in time) and outputs the normalized form and + the difference between this timestamp and now. The timestamp should adhere to the syntax documented in + systemd.time7, + section "PARSING TIMESTAMPS". + + + Show parsing of timestamps + + $ systemd-analyze timestamp yesterday now tomorrow + Original form: yesterday +Normalized form: Mon 2019-05-20 00:00:00 CEST + (in UTC): Sun 2019-05-19 22:00:00 UTC + UNIX seconds: @15583032000 + From now: 1 day 9h ago + + Original form: now +Normalized form: Tue 2019-05-21 09:48:39 CEST + (in UTC): Tue 2019-05-21 07:48:39 UTC + UNIX seconds: @1558424919.659757 + From now: 43us ago + + Original form: tomorrow +Normalized form: Wed 2019-05-22 00:00:00 CEST + (in UTC): Tue 2019-05-21 22:00:00 UTC + UNIX seconds: @15584760000 + From now: 14h left + + + + <command>systemd-analyze timespan <replaceable>EXPRESSION</replaceable>...</command> - This command parses a time span and outputs the normalized form and the equivalent value in - microseconds. The time span should adhere to the same syntax documented in - systemd.time7. - Values without associated magnitudes are parsed as seconds. + This command parses a time span (i.e. a difference between two timestamps) and outputs the + normalized form and the equivalent value in microseconds. The time span should adhere to the syntax + documented in + systemd.time7, + section "PARSING TIME SPANS". Values without units are parsed as seconds. Show parsing of timespans diff --git a/man/systemd.time.xml b/man/systemd.time.xml index 4a6b808c02..c7d5f24b3c 100644 --- a/man/systemd.time.xml +++ b/man/systemd.time.xml @@ -173,6 +173,10 @@ tomorrow Pacific/Auckland → Thu 2012-11-23 19:00:00 2 months 5 days ago Note that a relative timestamp is also accepted where a timestamp is expected (see above). + + Use the timestamp command of + systemd-analyze1 to + validate and normalize timestamps for testing purposes. diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index e6bbb179de..5217a92b43 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -1673,6 +1673,18 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) { } #endif +static void parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) { + if (calendar && calendar_spec_from_string(p, NULL) >= 0) + log_notice("Hint: this expression is a valid calendar specification. " + "Use 'systemd-analyze calendar \"%s\"' instead?", p); + if (timestamp && parse_timestamp(p, NULL) >= 0) + log_notice("Hint: this expression is a valid timestamp. " + "Use 'systemd-analyze timestamp \"%s\"' instead?", p); + if (timespan && parse_time(p, NULL, USEC_PER_SEC) >= 0) + log_notice("Hint: this expression is a valid timespan. " + "Use 'systemd-analyze timespan \"%s\"' instead?", p); +} + static int dump_timespan(int argc, char *argv[], void *userdata) { char **input_timespan; @@ -1682,12 +1694,18 @@ static int dump_timespan(int argc, char *argv[], void *userdata) { char ft_buf[FORMAT_TIMESPAN_MAX]; r = parse_time(*input_timespan, &output_usecs, USEC_PER_SEC); - if (r < 0) - return log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan); + if (r < 0) { + log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan); + parsing_hint(*input_timespan, true, true, false); + return r; + } printf("Original: %s\n", *input_timespan); printf(" %ss: %" PRIu64 "\n", special_glyph(SPECIAL_GLYPH_MU), output_usecs); - printf(" Human: %s\n", format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude)); + printf(" Human: %s%s%s\n", + ansi_highlight(), + format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude), + ansi_normal()); if (input_timespan[1]) putchar('\n'); @@ -1696,18 +1714,65 @@ static int dump_timespan(int argc, char *argv[], void *userdata) { return EXIT_SUCCESS; } +static int test_timestamp_one(const char *p) { + usec_t usec; + char buf[FORMAT_TIMESTAMP_MAX]; + int r; + + r = parse_timestamp(p, &usec); + if (r < 0) { + log_error_errno(r, "Failed to parse \"%s\": %m", p); + parsing_hint(p, true, false, true); + return r; + } + + printf(" Original form: %s\n", p); + printf("Normalized form: %s%s%s\n", + ansi_highlight_blue(), + format_timestamp(buf, sizeof buf, usec), + ansi_normal()); + + if (!in_utc_timezone()) + printf(" (in UTC): %s\n", format_timestamp_utc(buf, sizeof buf, usec)); + + printf(" UNIX seconds: @%"PRI_USEC"%s%0*"PRI_USEC"\n", + usec / USEC_PER_SEC, + usec % USEC_PER_SEC ? "." : "", + usec % USEC_PER_SEC ? 6 : 0, + usec % USEC_PER_SEC); + + printf(" From now: %s\n", format_timestamp_relative(buf, sizeof buf, usec)); + + return 0; +} + +static int test_timestamp(int argc, char *argv[], void *userdata) { + int ret = 0, r; + char **p; + + STRV_FOREACH(p, strv_skip(argv, 1)) { + r = test_timestamp_one(*p); + if (ret == 0 && r < 0) + ret = r; + + if (*(p + 1)) + putchar('\n'); + } + + return ret; +} + static int test_calendar_one(usec_t n, const char *p) { _cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL; _cleanup_free_ char *t = NULL; int r; r = calendar_spec_from_string(p, &spec); - if (r < 0) - return log_error_errno(r, "Failed to parse calendar specification '%s': %m", p); - - r = calendar_spec_normalize(spec); - if (r < 0) - return log_error_errno(r, "Failed to normalize calendar specification '%s': %m", p); + if (r < 0) { + log_error_errno(r, "Failed to parse calendar specification '%s': %m", p); + parsing_hint(p, false, true, true); + return r; + } r = calendar_spec_to_string(spec, &t); if (r < 0) @@ -1891,8 +1956,9 @@ static int help(int argc, char *argv[], void *userdata) { " unit-paths List load directories for units\n" " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n" " verify FILE... Check unit files for correctness\n" - " calendar SPEC... Validate repetitive calendar time events\n" " service-watchdogs [BOOL] Get/set service watchdog state\n" + " calendar SPEC... Validate repetitive calendar time events\n" + " timestamp TIMESTAMP... Validate a timestamp\n" " timespan SPAN... Validate a time span\n" " security [UNIT...] Analyze security of unit\n" "\nSee the %s for details.\n" @@ -2093,8 +2159,9 @@ static int run(int argc, char *argv[]) { { "syscall-filter", VERB_ANY, VERB_ANY, 0, dump_syscall_filters }, { "verify", 2, VERB_ANY, 0, do_verify }, { "calendar", 2, VERB_ANY, 0, test_calendar }, - { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs }, + { "timestamp", 2, VERB_ANY, 0, test_timestamp }, { "timespan", 2, VERB_ANY, 0, dump_timespan }, + { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs }, { "security", VERB_ANY, VERB_ANY, 0, do_security }, {} }; diff --git a/src/basic/time-util.c b/src/basic/time-util.c index daf952baa1..8ccb4a941a 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -575,7 +575,6 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { */ assert(t); - assert(usec); if (t[0] == '@' && !with_tz) return parse_sec(t + 1, usec); @@ -803,8 +802,8 @@ finish: else return -EINVAL; - *usec = ret; - + if (usec) + *usec = ret; return 0; } @@ -861,7 +860,7 @@ int parse_timestamp(const char *t, usec_t *usec) { if (munmap(shared, sizeof *shared) != 0) return negative_errno(); - if (tmp.return_value == 0) + if (tmp.return_value == 0 && usec) *usec = tmp.usec; return tmp.return_value; @@ -923,7 +922,6 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { bool something = false; assert(t); - assert(usec); assert(default_unit > 0); p = t; @@ -935,7 +933,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { if (*s != 0) return -EINVAL; - *usec = USEC_INFINITY; + if (usec) + *usec = USEC_INFINITY; return 0; } @@ -1007,8 +1006,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { } } - *usec = r; - + if (usec) + *usec = r; return 0; } diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 4365dbaca8..1045b00929 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -862,7 +862,6 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { int r; assert(p); - assert(spec); c = new(CalendarSpec, 1); if (!c) @@ -1076,7 +1075,8 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { if (!calendar_spec_valid(c)) return -EINVAL; - *spec = TAKE_PTR(c); + if (spec) + *spec = TAKE_PTR(c); return 0; }