]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
time-util: extract parse_calendar_date() from sysupdate
authorDylan M. Taylor <dylan@dylanmtaylor.com>
Fri, 6 Mar 2026 12:27:10 +0000 (07:27 -0500)
committerDylan M. Taylor <dylan@dylanmtaylor.com>
Wed, 18 Mar 2026 13:39:06 +0000 (09:39 -0400)
Move the YYYY-MM-DD date parsing and validation logic from
sysupdate-resource.c into a shared parse_calendar_date() function
in time-util, so it can be reused by other subsystems.

src/basic/time-util.c
src/basic/time-util.h
src/sysupdate/sysupdate-resource.c
src/test/test-time-util.c

index 5dd00af952d2939819c48ba9c44c9bfe2ad29965..4ba5bfafd71617a682bc700d9f822a9ce9b8d2e1 100644 (file)
@@ -1892,3 +1892,32 @@ TimestampStyle timestamp_style_from_string(const char *s) {
                 return TIMESTAMP_US_UTC;
         return t;
 }
+
+int parse_calendar_date(const char *s, usec_t *ret) {
+        struct tm parsed_tm = {}, copy_tm;
+        usec_t usec;
+        const char *k;
+        int r;
+
+        assert(s);
+
+        k = strptime(s, "%Y-%m-%d", &parsed_tm);
+        if (!k || *k)
+                return -EINVAL;
+
+        copy_tm = parsed_tm;
+        r = mktime_or_timegm_usec(&copy_tm, /* utc= */ true, &usec);
+        if (r < 0)
+                return r;
+
+        /* Refuse non-normalized dates, e.g. Feb 30 */
+        if (copy_tm.tm_mday != parsed_tm.tm_mday ||
+            copy_tm.tm_mon  != parsed_tm.tm_mon  ||
+            copy_tm.tm_year != parsed_tm.tm_year)
+                return -EINVAL;
+
+        if (ret)
+                *ret = usec;
+
+        return 0;
+}
index bde0b02d037c4424af60621f92c078cddc1a2925..c39718890131a845ef1d55f74e399a3f6b176161 100644 (file)
@@ -180,6 +180,7 @@ const char* etc_localtime(void);
 
 int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret);
 int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret);
+int parse_calendar_date(const char *s, usec_t *ret);
 
 uint32_t usec_to_jiffies(usec_t usec);
 usec_t jiffies_to_usec(uint32_t jiffies);
index 1cc48201efabed884b26310506318e6d352c3ec1..ba1842b2d6c8e2bb4dea70638d2b5fae8a032c9c 100644 (file)
@@ -433,24 +433,10 @@ static int process_magic_file(
         if (iovec_memcmp(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash) != 0)
                 log_warning("Hash of best before marker file '%s' has unexpected value, proceeding anyway.", fn);
 
-        struct tm parsed_tm = {};
-        const char *n = strptime(e, "%Y-%m-%d", &parsed_tm);
-        if (!n || *n != 0) {
-                /* Doesn't parse? Then it's not a best-before date */
-                log_warning("Found best before marker with an invalid date, ignoring: %s", fn);
-                return 0;
-        }
-
-        struct tm copy_tm = parsed_tm;
         usec_t best_before;
-        r = mktime_or_timegm_usec(&copy_tm, /* utc= */ true, &best_before);
-        if (r < 0)
-                return log_error_errno(r, "Failed to convert best before time: %m");
-        if (copy_tm.tm_mday != parsed_tm.tm_mday ||
-            copy_tm.tm_mon != parsed_tm.tm_mon ||
-            copy_tm.tm_year != parsed_tm.tm_year) {
-                /* date was not normalized? (e.g. "30th of feb") */
-                log_warning("Found best before marker with a non-normalized data, ignoring: %s", fn);
+        r = parse_calendar_date(e, &best_before);
+        if (r < 0) {
+                log_warning_errno(r, "Found best before marker with an invalid date, ignoring: %s", fn);
                 return 0;
         }
 
index d5d4992f827b440ed81eef47f1d4c50767e42ca9..172056a9bf611fca8969fdd020282da50583065f 100644 (file)
@@ -1281,4 +1281,30 @@ static int intro(void) {
         return EXIT_SUCCESS;
 }
 
+TEST(parse_calendar_date) {
+        usec_t usec;
+
+        /* Valid dates */
+        ASSERT_OK(parse_calendar_date("2000-01-01", &usec));
+        ASSERT_OK(parse_calendar_date("1970-01-01", &usec));
+        ASSERT_EQ(usec, 0u); /* epoch */
+        ASSERT_OK(parse_calendar_date("2000-02-29", &usec)); /* leap year */
+
+        /* NULL ret is allowed (validation only) */
+        ASSERT_OK(parse_calendar_date("2000-06-15", NULL));
+
+        /* Non-normalized dates */
+        ASSERT_ERROR(parse_calendar_date("2023-02-29", &usec), EINVAL); /* not a leap year */
+        ASSERT_ERROR(parse_calendar_date("2023-04-31", &usec), EINVAL); /* April has 30 days */
+        ASSERT_ERROR(parse_calendar_date("2023-13-01", &usec), EINVAL); /* month 13 */
+        ASSERT_ERROR(parse_calendar_date("2023-00-01", &usec), EINVAL); /* month 0 */
+
+        /* Malformed input */
+        ASSERT_ERROR(parse_calendar_date("", &usec), EINVAL);
+        ASSERT_ERROR(parse_calendar_date("not-a-date", &usec), EINVAL);
+        ASSERT_ERROR(parse_calendar_date("2023-06-15T00:00:00", &usec), EINVAL); /* trailing time */
+        ASSERT_ERROR(parse_calendar_date("2023/06/15", &usec), EINVAL); /* wrong separator */
+        ASSERT_ERROR(parse_calendar_date("06-15-2023", &usec), EINVAL); /* wrong order */
+}
+
 DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro);