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
+
+ systemd-analyze timestamp TIMESTAMP...
+
+ 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
+
+
+
+
systemd-analyze timespan EXPRESSION...
- 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;
}