static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) { struct tm tm; time_t t; int r; usec_t tm_usec; assert(spec); assert(next); if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX) return -EINVAL; usec++; t = (time_t) (usec / USEC_PER_SEC); assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); tm_usec = usec % USEC_PER_SEC; r = find_next(spec, &tm, &tm_usec); if (r < 0) return r; t = mktime_or_timegm(&tm, spec->utc); if (t < 0) return -EINVAL; *next = (usec_t) t * USEC_PER_SEC + tm_usec; return 0; }
static bool tm_out_of_bounds(const struct tm *tm, bool utc) { struct tm t; assert(tm); t = *tm; if (mktime_or_timegm(&t, utc) < 0) return true; /* * Set an upper bound on the year so impossible dates like "*-02-31" * don't cause find_next() to loop forever. tm_year contains years * since 1900, so adjust it accordingly. */ if (tm->tm_year + 1900 > MAX_YEAR) return true; /* Did any normalization take place? If so, it was out of bounds before */ return t.tm_year != tm->tm_year || t.tm_mon != tm->tm_mon || t.tm_mday != tm->tm_mday || t.tm_hour != tm->tm_hour || t.tm_min != tm->tm_min || t.tm_sec != tm->tm_sec; }
static int find_end_of_month(struct tm *tm, bool utc, int day) { struct tm t = *tm; t.tm_mon++; t.tm_mday = 1 - day; if (mktime_or_timegm(&t, utc) < 0 || t.tm_mon != tm->tm_mon) return -1; return t.tm_mday; }
static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { struct tm t; int k; if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS) return true; t = *tm; if (mktime_or_timegm(&t, utc) < 0) return false; k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; return (weekdays_bits & (1 << k)); }
static bool tm_out_of_bounds(const struct tm *tm, bool utc) { struct tm t; assert(tm); t = *tm; if (mktime_or_timegm(&t, utc) == (time_t) -1) return true; /* Did any normalization take place? If so, it was out of bounds before */ return t.tm_year != tm->tm_year || t.tm_mon != tm->tm_mon || t.tm_mday != tm->tm_mday || t.tm_hour != tm->tm_hour || t.tm_min != tm->tm_min || t.tm_sec != tm->tm_sec; }
int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { struct tm tm; time_t t; int r; assert(spec); assert(next); t = (time_t) (usec / USEC_PER_SEC) + 1; assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); r = find_next(spec, &tm); if (r < 0) return r; t = mktime_or_timegm(&tm, spec->utc); if (t == (time_t) -1) return -EINVAL; *next = (usec_t) t * USEC_PER_SEC; return 0; }
static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { struct tm c; int tm_usec; int r; assert(spec); assert(tm); c = *tm; tm_usec = *usec; for (;;) { /* Normalize the current date */ (void) mktime_or_timegm(&c, spec->utc); c.tm_isdst = spec->dst; c.tm_year += 1900; r = find_matching_component(spec, spec->year, &c, &c.tm_year); c.tm_year -= 1900; if (r > 0) { c.tm_mon = 0; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; } if (r < 0) return r; if (tm_out_of_bounds(&c, spec->utc)) return -ENOENT; c.tm_mon += 1; r = find_matching_component(spec, spec->month, &c, &c.tm_mon); c.tm_mon -= 1; if (r > 0) { c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; } if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_year++; c.tm_mon = 0; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } r = find_matching_component(spec, spec->day, &c, &c.tm_mday); if (r > 0) c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_mon++; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { c.tm_mday++; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } r = find_matching_component(spec, spec->hour, &c, &c.tm_hour); if (r > 0) c.tm_min = c.tm_sec = tm_usec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_mday++; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } r = find_matching_component(spec, spec->minute, &c, &c.tm_min); if (r > 0) c.tm_sec = tm_usec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_hour++; c.tm_min = c.tm_sec = tm_usec = 0; continue; } c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec); tm_usec = c.tm_sec % USEC_PER_SEC; c.tm_sec /= USEC_PER_SEC; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_min++; c.tm_sec = tm_usec = 0; continue; } *tm = c; *usec = tm_usec; return 0; } }
int parse_timestamp(const char *t, usec_t *usec) { static const struct { const char *name; const int nr; } day_nr[] = { { "Sunday", 0 }, { "Sun", 0 }, { "Monday", 1 }, { "Mon", 1 }, { "Tuesday", 2 }, { "Tue", 2 }, { "Wednesday", 3 }, { "Wed", 3 }, { "Thursday", 4 }, { "Thu", 4 }, { "Friday", 5 }, { "Fri", 5 }, { "Saturday", 6 }, { "Sat", 6 }, }; const char *k; const char *utc; struct tm tm, copy; time_t x; usec_t x_usec, plus = 0, minus = 0, ret; int r, weekday = -1; unsigned i; /* * Allowed syntaxes: * * 2012-09-22 16:34:22 * 2012-09-22 16:34 (seconds will be set to 0) * 2012-09-22 (time will be set to 00:00:00) * 16:34:22 (date will be set to today) * 16:34 (date will be set to today, seconds to 0) * now * yesterday (time is set to 00:00:00) * today (time is set to 00:00:00) * tomorrow (time is set to 00:00:00) * +5min * -5days * @2147483647 (seconds since epoch) * */ assert(t); assert(usec); if (t[0] == '@') return parse_sec(t + 1, usec); ret = now(CLOCK_REALTIME); if (streq(t, "now")) goto finish; else if (t[0] == '+') { r = parse_sec(t+1, &plus); if (r < 0) return r; goto finish; } else if (t[0] == '-') { r = parse_sec(t+1, &minus); if (r < 0) return r; goto finish; } else if ((k = endswith(t, " ago"))) { t = strndupa(t, k - t); r = parse_sec(t, &minus); if (r < 0) return r; goto finish; } else if ((k = endswith(t, " left"))) { t = strndupa(t, k - t); r = parse_sec(t, &plus); if (r < 0) return r; goto finish; } utc = endswith_no_case(t, " UTC"); if (utc) t = strndupa(t, utc - t); x = ret / USEC_PER_SEC; x_usec = 0; assert_se(localtime_or_gmtime_r(&x, &tm, utc)); tm.tm_isdst = -1; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } else if (streq(t, "yesterday")) { tm.tm_mday --; tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } else if (streq(t, "tomorrow")) { tm.tm_mday ++; tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; if (!startswith_no_case(t, day_nr[i].name)) continue; skip = strlen(day_nr[i].name); if (t[skip] != ' ') continue; weekday = day_nr[i].nr; t += skip + 1; break; } copy = tm; k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); if (k) { if (*k == '.') goto parse_usec; else if (*k == 0) goto from_tm; } tm = copy; k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); if (k) { if (*k == '.') goto parse_usec; else if (*k == 0) goto from_tm; } tm = copy; k = strptime(t, "%y-%m-%d %H:%M", &tm); if (k && *k == 0) { tm.tm_sec = 0; goto from_tm; } tm = copy; k = strptime(t, "%Y-%m-%d %H:%M", &tm); if (k && *k == 0) { tm.tm_sec = 0; goto from_tm; } tm = copy; k = strptime(t, "%y-%m-%d", &tm); if (k && *k == 0) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } tm = copy; k = strptime(t, "%Y-%m-%d", &tm); if (k && *k == 0) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } tm = copy; k = strptime(t, "%H:%M:%S", &tm); if (k) { if (*k == '.') goto parse_usec; else if (*k == 0) goto from_tm; } tm = copy; k = strptime(t, "%H:%M", &tm); if (k && *k == 0) { tm.tm_sec = 0; goto from_tm; } return -EINVAL; parse_usec: { unsigned add; k++; r = parse_fractional_part_u(&k, 6, &add); if (r < 0) return -EINVAL; if (*k) return -EINVAL; x_usec = add; } from_tm: x = mktime_or_timegm(&tm, utc); if (x == (time_t) -1) return -EINVAL; if (weekday >= 0 && tm.tm_wday != weekday) return -EINVAL; ret = (usec_t) x * USEC_PER_SEC + x_usec; finish: ret += plus; if (ret > minus) ret -= minus; else ret = 0; *usec = ret; return 0; }
static int find_next(const CalendarSpec *spec, struct tm *tm) { struct tm c; int r; assert(spec); assert(tm); c = *tm; for (;;) { /* Normalize the current date */ mktime_or_timegm(&c, spec->utc); c.tm_isdst = -1; c.tm_year += 1900; r = find_matching_component(spec->year, &c.tm_year); c.tm_year -= 1900; if (r > 0) { c.tm_mon = 0; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = 0; } if (r < 0 || tm_out_of_bounds(&c, spec->utc)) return r; c.tm_mon += 1; r = find_matching_component(spec->month, &c.tm_mon); c.tm_mon -= 1; if (r > 0) { c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = 0; } if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_year ++; c.tm_mon = 0; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = 0; continue; } r = find_matching_component(spec->day, &c.tm_mday); if (r > 0) c.tm_hour = c.tm_min = c.tm_sec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_mon ++; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = 0; continue; } if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { c.tm_mday++; c.tm_hour = c.tm_min = c.tm_sec = 0; continue; } r = find_matching_component(spec->hour, &c.tm_hour); if (r > 0) c.tm_min = c.tm_sec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_mday ++; c.tm_hour = c.tm_min = c.tm_sec = 0; continue; } r = find_matching_component(spec->minute, &c.tm_min); if (r > 0) c.tm_sec = 0; if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_hour ++; c.tm_min = c.tm_sec = 0; continue; } r = find_matching_component(spec->second, &c.tm_sec); if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { c.tm_min ++; c.tm_sec = 0; continue; } *tm = c; return 0; } }
int parse_timestamp(const char *t, usec_t *usec) { static const struct { const char *name; const int nr; } day_nr[] = { { "Sunday", 0 }, { "Sun", 0 }, { "Monday", 1 }, { "Mon", 1 }, { "Tuesday", 2 }, { "Tue", 2 }, { "Wednesday", 3 }, { "Wed", 3 }, { "Thursday", 4 }, { "Thu", 4 }, { "Friday", 5 }, { "Fri", 5 }, { "Saturday", 6 }, { "Sat", 6 }, }; const char *k, *utc, *tzn = NULL; struct tm tm, copy; time_t x; usec_t x_usec, plus = 0, minus = 0, ret; int r, weekday = -1, dst = -1; unsigned i; /* * Allowed syntaxes: * * 2012-09-22 16:34:22 * 2012-09-22 16:34 (seconds will be set to 0) * 2012-09-22 (time will be set to 00:00:00) * 16:34:22 (date will be set to today) * 16:34 (date will be set to today, seconds to 0) * now * yesterday (time is set to 00:00:00) * today (time is set to 00:00:00) * tomorrow (time is set to 00:00:00) * +5min * -5days * @2147483647 (seconds since epoch) * */ assert(t); assert(usec); if (t[0] == '@') return parse_sec(t + 1, usec); ret = now(CLOCK_REALTIME); if (streq(t, "now")) goto finish; else if (t[0] == '+') { r = parse_sec(t+1, &plus); if (r < 0) return r; goto finish; } else if (t[0] == '-') { r = parse_sec(t+1, &minus); if (r < 0) return r; goto finish; } else if ((k = endswith(t, " ago"))) { t = strndupa(t, k - t); r = parse_sec(t, &minus); if (r < 0) return r; goto finish; } else if ((k = endswith(t, " left"))) { t = strndupa(t, k - t); r = parse_sec(t, &plus); if (r < 0) return r; goto finish; } /* See if the timestamp is suffixed with UTC */ utc = endswith_no_case(t, " UTC"); if (utc) t = strndupa(t, utc - t); else { const char *e = NULL; int j; tzset(); /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because * there are no nice APIs available to cover this. By accepting the local time zone strings, we make * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't * support arbitrary timezone specifications. */ for (j = 0; j <= 1; j++) { if (isempty(tzname[j])) continue; e = endswith_no_case(t, tzname[j]); if (!e) continue; if (e == t) continue; if (e[-1] != ' ') continue; break; } if (IN_SET(j, 0, 1)) { /* Found one of the two timezones specified. */ t = strndupa(t, e - t - 1); dst = j; tzn = tzname[j]; } } x = (time_t) (ret / USEC_PER_SEC); x_usec = 0; if (!localtime_or_gmtime_r(&x, &tm, utc)) return -EINVAL; tm.tm_isdst = dst; if (tzn) tm.tm_zone = tzn; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } else if (streq(t, "yesterday")) { tm.tm_mday--; tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } else if (streq(t, "tomorrow")) { tm.tm_mday++; tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; if (!startswith_no_case(t, day_nr[i].name)) continue; skip = strlen(day_nr[i].name); if (t[skip] != ' ') continue; weekday = day_nr[i].nr; t += skip + 1; break; } copy = tm; k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); if (k) { if (*k == '.') goto parse_usec; else if (*k == 0) goto from_tm; } tm = copy; k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); if (k) { if (*k == '.') goto parse_usec; else if (*k == 0) goto from_tm; } tm = copy; k = strptime(t, "%y-%m-%d %H:%M", &tm); if (k && *k == 0) { tm.tm_sec = 0; goto from_tm; } tm = copy; k = strptime(t, "%Y-%m-%d %H:%M", &tm); if (k && *k == 0) { tm.tm_sec = 0; goto from_tm; } tm = copy; k = strptime(t, "%y-%m-%d", &tm); if (k && *k == 0) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } tm = copy; k = strptime(t, "%Y-%m-%d", &tm); if (k && *k == 0) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; goto from_tm; } tm = copy; k = strptime(t, "%H:%M:%S", &tm); if (k) { if (*k == '.') goto parse_usec; else if (*k == 0) goto from_tm; } tm = copy; k = strptime(t, "%H:%M", &tm); if (k && *k == 0) { tm.tm_sec = 0; goto from_tm; } return -EINVAL; parse_usec: { unsigned add; k++; r = parse_fractional_part_u(&k, 6, &add); if (r < 0) return -EINVAL; if (*k) return -EINVAL; x_usec = add; } from_tm: x = mktime_or_timegm(&tm, utc); if (x == (time_t) -1) return -EINVAL; if (weekday >= 0 && tm.tm_wday != weekday) return -EINVAL; ret = (usec_t) x * USEC_PER_SEC + x_usec; finish: ret += plus; if (ret > minus) ret -= minus; else ret = 0; *usec = ret; return 0; }