Skip to content

Commit 1dbe167

Browse files
committed
improve AM/PM handling and extend the test cases
1 parent 26aa9b2 commit 1dbe167

File tree

2 files changed

+55
-30
lines changed

2 files changed

+55
-30
lines changed

include/mgutility/chrono/parse.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ MGUTILITY_CNSTXPR auto parse_timezone_offset(detail::tm &result,
396396
*/
397397
MGUTILITY_CNSTXPR auto parse_am_pm(detail::tm &result, string_view date_str,
398398
uint32_t &next) -> std::errc {
399-
if (next + 2 > date_str.size()) {
399+
if (next + 2 > date_str.size() || result.tm_hour < 1 || result.tm_hour > 12) {
400400
return std::errc::invalid_argument;
401401
}
402402
if (date_str.substr(next, 2) == "AM") {

tests/test_chrono_parse.cpp

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,48 +10,73 @@ auto to_milliseconds(T time_point) -> std::chrono::milliseconds {
1010
return std::chrono::duration_cast<std::chrono::milliseconds>(time_point.time_since_epoch());
1111
}
1212

13-
TEST_CASE("testing the iso8601 parsing") {
13+
TEST_CASE("Basic ISO 8601 Parsing") {
1414
using std::chrono::milliseconds;
1515

1616
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2023-04-30T16:22:18")) == milliseconds(1682871738000));
17+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2022-12-31T23:59:59")) == milliseconds(1672531199000));
18+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2023-01-01T00:00:00")) == milliseconds(1672531200000));
19+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2023-05-01T00:00:00")) == milliseconds(1682899200000));
20+
}
21+
22+
TEST_CASE("Timezone Offset Handling") {
23+
using std::chrono::milliseconds;
24+
1725
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2023-04-30T16:22:18Z")) == milliseconds(1682871738000));
1826
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2023-04-30T18:22:18+0200")) == milliseconds(1682871738000));
1927
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2023-04-30T16:22:18-0200")) == milliseconds(1682878938000));
20-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.500")) == milliseconds(1682871738500));
21-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f%z}", "2023-04-30T16:22:18.500+0100")) == milliseconds(1682868138500));
22-
23-
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T", "2023-04-30T16:22:18"));
24-
REQUIRE_THROWS(mgutility::chrono::parse("%FT%T}", "2023-04-30T16:22:18"));
25-
REQUIRE_THROWS(mgutility::chrono::parse("{%F %T}", "2023-04-30T16:22:18"));
26-
REQUIRE_THROWS(mgutility::chrono::parse("{:%F %T}", "2023-04-30T16:22:18"));
27-
28+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2016-02-29T05:00:00-0000")) == milliseconds(1456722000000));
29+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2016-02-29T23:59:59+0000")) == milliseconds(1456790399000));
30+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2016-02-29T12:00:00-1200")) == milliseconds(1456790400000));
31+
}
2832

29-
// Leap year: Feb 29 should work in a leap year
30-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2020-02-29T12:00:00")) == milliseconds(1582977600000));
33+
TEST_CASE("AM/PM Handling") {
34+
using std::chrono::milliseconds;
3135

32-
// Non-leap year: Feb 29 should not exist
33-
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T}", "2021-02-29T12:00:00"));
36+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T12:00:00 AM")) == milliseconds(1682812800000));
37+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T12:00:00 PM")) == milliseconds(1682856000000));
38+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T11:59:59 PM")) == milliseconds(1682899199000));
39+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T01:00:00 AM")) == milliseconds(1682816400000));
40+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T01:00:00 PM")) == milliseconds(1682859600000));
41+
}
3442

35-
// Leap year, with timezone offset: UTC should equal local time with -0000
36-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2016-02-29T05:00:00-0000")) == milliseconds(1456722000000));
43+
TEST_CASE("Fractional Seconds") {
44+
using std::chrono::milliseconds;
3745

38-
// February 28 + 1 day (should rollover to Mar 1 on non-leap year)
39-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2021-03-01T00:00:00")) == milliseconds(1614556800000));
46+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.1")) == milliseconds(1682871738100)); // 100ms
47+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.123")) == milliseconds(1682871738123)); // 123ms
48+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.123456")) == milliseconds(1682871738123)); // 123ms (truncated)
49+
}
4050

41-
// Year rollover: Dec 31 to Jan 1
42-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2022-12-31T23:59:59")) == milliseconds(1672531199000));
43-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2023-01-01T00:00:00")) == milliseconds(1672531200000));
51+
TEST_CASE("Combined AM/PM, Fractional Seconds, and Timezone") {
52+
using std::chrono::milliseconds;
4453

45-
// Month boundary rollover: Apr 30 + 1 day == May 1
46-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2023-05-01T00:00:00")) == milliseconds(1682899200000));
54+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f %p}", "2023-04-30T11:59:59.500 PM")) == milliseconds(1682899199500));
55+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f %p}", "2023-04-30T01:20:00.123 AM")) == milliseconds(1682817600123));
56+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f %p %z}", "2023-04-30T11:59:59.500 PM +0100")) == milliseconds(1682895599500));
57+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T.%f %p %z}", "2023-04-30T01:20:00.123 AM -0200")) == milliseconds(1682824800123));
58+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T %p %z}", "2023-04-30T01:20:00 PM +0200")) == milliseconds(1682853600000));
59+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T %p %z}", "2023-04-30T01:20:00 AM -0200")) == milliseconds(1682824800000));
60+
}
4761

48-
// Invalid date: April 31
49-
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T}", "2023-04-31T12:00:00"));
62+
TEST_CASE("Leap Year and Date Boundaries") {
63+
using std::chrono::milliseconds;
5064

51-
// Edge timezone: Feb 29 with offset
52-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2016-02-29T23:59:59+0000")) == milliseconds(1456790399000));
53-
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T%z}", "2016-02-29T12:00:00-1200")) == milliseconds(1456790400000));
65+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2020-02-29T12:00:00")) == milliseconds(1582977600000)); // Leap year
66+
CHECK(to_milliseconds(mgutility::chrono::parse("{:%FT%T}", "2021-03-01T00:00:00")) == milliseconds(1614556800000)); // Feb 28 + 1 day
67+
}
5468

55-
// Invalid date/time format
56-
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T}", "not-a-date"));
69+
TEST_CASE("Error Handling") {
70+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T", "2023-04-30T16:22:18")); // Malformed format
71+
REQUIRE_THROWS(mgutility::chrono::parse("%FT%T}", "2023-04-30T16:22:18")); // Malformed format
72+
REQUIRE_THROWS(mgutility::chrono::parse("{%F %T}", "2023-04-30T16:22:18")); // Malformed format
73+
REQUIRE_THROWS(mgutility::chrono::parse("{:%F %T}", "2023-04-30T16:22:18")); // Format mismatch
74+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T}", "2021-02-29T12:00:00")); // Invalid date (non-leap year)
75+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T}", "2023-04-31T12:00:00")); // Invalid date (April 31)
76+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T}", "not-a-date")); // Invalid format
77+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T13:00:00 AM")); // Hour > 12
78+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T12:00:00 XM")); // Invalid AM/PM
79+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%H:%M:%S %p}", "2023-04-30T12:00:00")); // Missing AM/PM
80+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.")); // No digits after decimal
81+
REQUIRE_THROWS(mgutility::chrono::parse("{:%FT%T.%f}", "2023-04-30T16:22:18.A")); // Invalid fraction
5782
}

0 commit comments

Comments
 (0)