From 38c7d8b496c4a7c1d8390b15ef6f2aa3b4e96284 Mon Sep 17 00:00:00 2001 From: hamzah Date: Wed, 3 Jun 2026 20:07:49 -0700 Subject: [PATCH 1/2] initial working 12h datetime addition!! Signed-off-by: hamzah --- src/Datetime.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/Datetime.cpp b/src/Datetime.cpp index b81d397..99403c9 100644 --- a/src/Datetime.cpp +++ b/src/Datetime.cpp @@ -246,6 +246,7 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) int hour {-1}; int minute {-1}; int second {-1}; + int meridiem {-1}; // For parsing, unused. int wday {-1}; @@ -353,6 +354,58 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) } break; + case 'i': + if (pig.getDigit (hour)) + { + if (hour == 0) + pig.getDigit (hour); + + if (hour == 1) + { + int tens = hour; + if (pig.getDigit (hour)) + hour += 10 * tens; + } + + if (hour < 1 || hour > 12) + { + pig.restoreTo (checkpoint); + return false; + } + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'I': + if (! pig.getDigit2 (hour) || hour < 1 || hour > 12) + { + pig.restoreTo (checkpoint); + return false; + } + break; + + case 'p': + if (compare (pig.peek (2), "AM", false)) + { + meridiem = 0; + pig.skipN (2); + } + else if (compare (pig.peek (2), "PM", false)) + { + meridiem = 1; + pig.skipN (2); + } + else + { + pig.restoreTo (checkpoint); + return false; + } + break; + case 'n': if (pig.getDigit (minute)) { @@ -539,6 +592,20 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) if (minute == -1) minute = 0; if (second == -1) second = 0; + if (meridiem != -1) + { + if (hour < 1 || hour > 12) + { + pig.restoreTo (checkpoint); + return false; + } + + if (meridiem == 0 && hour == 12) + hour = 0; + else if (meridiem == 1 && hour != 12) + hour += 12; + } + _year = year; _month = month; _day = day; @@ -3394,6 +3461,9 @@ std::string Datetime::toString (const std::string& format) const case 'V': formatted << std::setw (2) << std::setfill ('0') << week (); break; case 'h': formatted << hour (); break; case 'H': formatted << std::setw (2) << std::setfill ('0') << hour (); break; + case 'i': formatted << ((hour () % 12) == 0 ? 12 : (hour () % 12)); break; + case 'I': formatted << std::setw (2) << std::setfill ('0') << ((hour () % 12) == 0 ? 12 : (hour () % 12)); break; + case 'p': formatted << (hour () < 12 ? "AM" : "PM"); break; case 'n': formatted << minute (); break; case 'N': formatted << std::setw (2) << std::setfill ('0') << minute (); break; case 's': formatted << second (); break; @@ -3641,6 +3711,9 @@ int Datetime::length (const std::string& format) case 'V': case 'h': case 'H': + case 'i': + case 'I': + case 'p': case 'n': case 'N': case 's': From 47863455b326c9808603110bf4d5a647b4c4d6f2 Mon Sep 17 00:00:00 2001 From: hamzah Date: Wed, 3 Jun 2026 20:23:01 -0700 Subject: [PATCH 2/2] add ambigious handling + add tests Signed-off-by: hamzah --- src/Datetime.cpp | 19 +++++++++++++++++-- src/Datetime.h | 2 +- test/datetime.t.cpp | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Datetime.cpp b/src/Datetime.cpp index 99403c9..6188947 100644 --- a/src/Datetime.cpp +++ b/src/Datetime.cpp @@ -166,7 +166,8 @@ bool Datetime::parse ( return true; } - if (parse_formatted (pig, format)) + bool ambiguous {false}; + if (parse_formatted (pig, format, ambiguous)) { // Check the values and determine time_t. if (validate ()) @@ -176,6 +177,10 @@ bool Datetime::parse ( return true; } } + else if (ambiguous) + { + return false; + } // Allow parse_date_time and parse_date_time_ext regardless of // Datetime::isoEnabled setting, because these formats are relied upon by @@ -232,7 +237,7 @@ void Datetime::clear () } //////////////////////////////////////////////////////////////////////////////// -bool Datetime::parse_formatted (Pig& pig, const std::string& format) +bool Datetime::parse_formatted (Pig& pig, const std::string& format, bool& ambiguous) { // Short-circuit on missing format. if (format.empty ()) @@ -247,6 +252,7 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) int minute {-1}; int second {-1}; int meridiem {-1}; + bool twelveHourTime {false}; // For parsing, unused. int wday {-1}; @@ -355,6 +361,7 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) break; case 'i': + twelveHourTime = true; if (pig.getDigit (hour)) { if (hour == 0) @@ -381,6 +388,7 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) break; case 'I': + twelveHourTime = true; if (! pig.getDigit2 (hour) || hour < 1 || hour > 12) { pig.restoreTo (checkpoint); @@ -592,6 +600,13 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) if (minute == -1) minute = 0; if (second == -1) second = 0; + if (twelveHourTime && meridiem == -1) + { + ambiguous = true; + pig.restoreTo (checkpoint); + return false; + } + if (meridiem != -1) { if (hour < 1 || hour > 12) diff --git a/src/Datetime.h b/src/Datetime.h index 265ae8b..17638bb 100644 --- a/src/Datetime.h +++ b/src/Datetime.h @@ -116,7 +116,7 @@ class Datetime private: void clear (); - bool parse_formatted (Pig&, const std::string&); + bool parse_formatted (Pig&, const std::string&, bool& ambiguous); bool parse_named (Pig&); bool parse_epoch (Pig&); bool parse_date_time_ext (Pig&); diff --git a/test/datetime.t.cpp b/test/datetime.t.cpp index 5a43be7..570907c 100644 --- a/test/datetime.t.cpp +++ b/test/datetime.t.cpp @@ -1070,6 +1070,9 @@ int main (int, char**) t.is (Datetime::length ("V"), 2, "length 'V' --> 2"); t.is (Datetime::length ("h"), 2, "length 'h' --> 2"); t.is (Datetime::length ("H"), 2, "length 'H' --> 2"); + t.is (Datetime::length ("i"), 2, "length 'i' --> 2"); + t.is (Datetime::length ("I"), 2, "length 'I' --> 2"); + t.is (Datetime::length ("p"), 2, "length 'p' --> 2"); t.is (Datetime::length ("n"), 2, "length 'n' --> 2"); t.is (Datetime::length ("N"), 2, "length 'N' --> 2"); t.is (Datetime::length ("s"), 2, "length 's' --> 2"); @@ -1095,6 +1098,9 @@ int main (int, char**) t.is (r32.toString ("d"), "28", "2015-10-28T12:55:01 -> d -> 28"); t.is (r32.toString ("H"), "12", "2015-10-28T12:55:01 -> H -> 12"); t.is (r32.toString ("h"), "12", "2015-10-28T12:55:01 -> h -> 12"); + t.is (r32.toString ("I"), "12", "2015-10-28T12:55:01 -> I -> 12"); + t.is (r32.toString ("i"), "12", "2015-10-28T12:55:01 -> i -> 12"); + t.is (r32.toString ("p"), "PM", "2015-10-28T12:55:01 -> p -> PM"); t.is (r32.toString ("N"), "55", "2015-10-28T12:55:01 -> N -> 55"); t.is (r32.toString ("n"), "55", "2015-10-28T12:55:01 -> n -> 55"); t.is (r32.toString ("S"), "00", "2015-10-28T12:55:01 -> S -> 01"); @@ -1109,6 +1115,16 @@ int main (int, char**) t.is (r32.toString ("j"), "301", "2015-10-28T12:55:01 -> j -> 301"); t.is (r32.toString ("w"), "3", "2015-10-28T12:55:01 -> w -> 3"); + Datetime r32a ("2015-10-28T00:05:00"); + t.is (r32a.toString ("I"), "12", "2015-10-28T00:05:00 -> I -> 12"); + t.is (r32a.toString ("i"), "12", "2015-10-28T00:05:00 -> i -> 12"); + t.is (r32a.toString ("p"), "AM", "2015-10-28T00:05:00 -> p -> AM"); + + Datetime r32b ("2015-10-28T13:05:00"); + t.is (r32b.toString ("I"), "01", "2015-10-28T13:05:00 -> I -> 01"); + t.is (r32b.toString ("i"), "1", "2015-10-28T13:05:00 -> i -> 1"); + t.is (r32b.toString ("p"), "PM", "2015-10-28T13:05:00 -> p -> PM"); + // Test all parse options. Datetime r33 ("2015 10 28 19 28 01", "Y M D H N S"); t.is(r33.year (), 2015, "Y works"); @@ -1126,6 +1142,24 @@ int main (int, char**) t.is(r34.minute (), 2, "n works"); t.is(r34.second (), 1, "s works"); + Datetime r34a ("2015 10 28 12 00 AM", "Y M D I N p"); + t.is(r34a.hour (), 0, "12 AM parses as hour 0"); + t.is(r34a.minute (), 0, "12 AM minute works"); + + Datetime r34b ("2015 10 28 12 00 PM", "Y M D I N p"); + t.is(r34b.hour (), 12, "12 PM parses as hour 12"); + t.is(r34b.minute (), 0, "12 PM minute works"); + + Datetime r34c ("2015 10 28 1 30 PM", "Y M D i N p"); + t.is(r34c.hour (), 13, "1 PM parses as hour 13"); + t.is(r34c.minute (), 30, "1 PM minute works"); + + Datetime r34d ("2015 10 28 11 45 pm", "Y M D i N p"); + t.is(r34d.hour (), 23, "lowercase pm parses as hour 23"); + t.is(r34d.minute (), 45, "lowercase pm minute works"); + + t.notok (Datetime::valid ("2015_10_28_1_30", "Y_M_D_i_N"), "ambiguous 12-hour time without AM/PM is invalid"); + Datetime r35 ("Wednesday October 28 2015", "A B D Y"); t.is(r35.year (), 2015, "Y works"); t.is(r35.month (), 10, "B works");