Skip to content

date::parse ignores %p (AM/PM) when parsing sys_time with %I:%M:%S %p %z #893

@matigarciadev

Description

@matigarciadev

Summary

Parsing 12‑hour times with AM/PM directly into date::sys_time ignores %p: PM inputs produce the same UTC result as AM. The C++ locale facet appears correct (std::get_time handles AM/PM). Using %r instead of %I:%M:%S %p works, so this looks specific to %I…%p in date::parse/from_stream for sys_time when combined with %z/%Ez.

Minimal repro (fails with %I:%M:%S %p)

main.cpp

#include <iostream>
#include <sstream>
#include <string>
#include <chrono>
#include <locale>
#include <date/date.h>

template <typename Dur>
void test(const std::string& s, const std::string& fmt) {
    std::istringstream in{s};
    in.imbue(std::locale("en_US.utf8")); // AM/PM facet
    date::sys_time<Dur> tp{};
    in >> date::parse(fmt.c_str(), tp);
    if (!in) {
        std::cerr << "Parse failed. fmt='" << fmt << "' input='" << s << "'\n";
        return;
    }
    std::cout << s << "  ->  " << date::format("%FT%TZ", tp) << '\n';
}

int main() {
    using seconds = std::chrono::seconds;

    test<seconds>("Sep 16, 2025 10:29:51 PM -0300",
                  "%b %d, %Y %I:%M:%S %p %z");

    test<seconds>("Sep 16, 2025 10:29:51 PM -03:00",
                  "%b %d, %Y %I:%M:%S %p %Ez");

    test<seconds>("Sep 16, 2025 10:29:51 AM -0300",
                  "%b %d, %Y %I:%M:%S %p %z");
}

Actual output (incorrect)

Sep 16, 2025 10:29:51 PM -0300  ->  2025-09-16T13:29:51Z
Sep 16, 2025 10:29:51 PM -03:00  ->  2025-09-16T13:29:51Z
Sep 16, 2025 10:29:51 AM -0300  ->  2025-09-16T13:29:51Z

Expected output

Sep 16, 2025 10:29:51 PM -0300  ->  2025-09-17T01:29:51Z
Sep 16, 2025 10:29:51 PM -03:00  ->  2025-09-17T01:29:51Z
Sep 16, 2025 10:29:51 AM -0300  ->  2025-09-16T13:29:51Z

Control repro (works with %r)

main_r.cpp

#include <iostream>
#include <sstream>
#include <string>
#include <chrono>
#include <locale>
#include <date/date.h>

template <typename Dur>
void test(const std::string& s, const std::string& fmt) {
    std::istringstream in{s};
    in.imbue(std::locale("en_US.utf8"));
    date::sys_time<Dur> tp{};
    in >> date::parse(fmt.c_str(), tp);
    if (!in) {
        std::cerr << "Parse failed. fmt='" << fmt << "' input='" << s << "'\n";
        return;
    }
    std::cout << s << "  ->  " << date::format("%FT%TZ", tp) << '\n';
}

int main() {
    using seconds = std::chrono::seconds;

    test<seconds>("Sep 16, 2025 10:29:51 PM -0300",
                  "%b %d, %Y %r %z");

    test<seconds>("Sep 16, 2025 10:29:51 PM -03:00",
                  "%b %d, %Y %r %Ez");

    test<seconds>("Sep 16, 2025 10:29:51 AM -0300",
                  "%b %d, %Y %r %z");
}

Actual output (correct)

Sep 16, 2025 10:29:51 PM -0300  ->  2025-09-17T01:29:51Z
Sep 16, 2025 10:29:51 PM -03:00  ->  2025-09-17T01:29:51Z
Sep 16, 2025 10:29:51 AM -0300  ->  2025-09-16T13:29:51Z

Facet sanity check (std lib handles %p correctly)

diag.cpp

#include <locale>
#include <iomanip>
#include <sstream>
#include <ctime>
#include <iostream>

int main() {
    std::locale loc("en_US.utf8");

    // %p formats correctly
    std::ostringstream os; os.imbue(loc);
    std::tm tm{}; tm.tm_hour = 13;
    os << std::put_time(&tm, "%p");
    std::cout << "put_time(%p) for 13:00 -> '" << os.str() << "' (expect 'PM')\n";

    // get_time parses AM/PM correctly
    auto parse = [&](const char* s){
        std::tm t{}; std::istringstream is(s); is.imbue(loc);
        is >> std::get_time(&t, "%I:%M:%S %p");
        std::cout << "get_time('" << s << "') -> tm_hour=" << t.tm_hour << "\n";
    };
    parse("10:29:51 AM"); // -> 10
    parse("10:29:51 PM"); // -> 22
    parse("12:00:00 AM"); // -> 0
    parse("12:00:00 PM"); // -> 12
}

Observed

put_time(%p) for 13:00 -> 'PM' (expect 'PM')
get_time('10:29:51 AM') -> tm_hour=10
get_time('10:29:51 PM') -> tm_hour=22
get_time('12:00:00 AM') -> tm_hour=0
get_time('12:00:00 PM') -> tm_hour=12

Environment

Compiler: GCC 13.3.0
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

libc: glibc 2.39
$ ldd --version
ldd (Ubuntu GLIBC 2.39-0ubuntu8.5) 2.39

Locales available (excerpt):
$ locale -a
C
C.utf8
…
en_US.utf8
…
  • en_US.utf8 loads successfully and is used in the tests above.
  • date version tested: v3.0.4 (also reproducible with v3.0.1).

Notes / Workarounds

  • Using %r (12‑hour time with AM/PM) instead of %I:%M:%S %p works correctly.
  • Alternatively, normalize AM/PM to 24‑hour in the input and parse with %H:%M:%S %z/%Ez.

Ask

Is %p being dropped when parsing sys_time with %I…%p along with %z/%Ez? Any fix or guidance would be appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions