diff --git a/src/Datetime.cpp b/src/Datetime.cpp index b81d397..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 ()) @@ -246,6 +251,8 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) int hour {-1}; int minute {-1}; int second {-1}; + int meridiem {-1}; + bool twelveHourTime {false}; // For parsing, unused. int wday {-1}; @@ -353,6 +360,60 @@ bool Datetime::parse_formatted (Pig& pig, const std::string& format) } break; + case 'i': + twelveHourTime = true; + 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': + twelveHourTime = true; + 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 +600,27 @@ 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) + { + 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 +3476,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 +3726,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': 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");