I'm trying to convert std::string
s such as "2024-11-19 09:30:00.037000"
**that I know to always be in Eastern time) to std::chrono
objects and vice versa, but I can't get anything to work well.
I mostly work with std::chrono::time_point
by calling std::chrono::system_clock::now()
a lot, but whenever I write to disk, I'd like to write everything in Eastern time with microsecond precision.
I know this question has been asked a lot. I've spent a few days trying to adapt answers on this site such as this one and this one, but to no avail.
For example, this won't compile:
timepoint_t parse_eastern_time(const std::string& dtstr) {
std::istringstream iss{dtstr};
zoned_time_t tp{};
iss >> std::chrono::parse("%Y-%m-%d %H:%M:%S.%f",tp);
return tp.get_sys_time();
}
and this runs but chops off everything subseconds
timepoint_t parse_eastern_time(const std::string& dtstr) {
// Format of data stored in eastern time: "2024-11-19 09:30:00.037000"
std::tm tm = {};
std::stringstream ss(dtstr);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S.%f");
timepoint_t tp = clock_type::from_time_t(std::mktime(&tm));
return tp;
}
Here's all my testing code in case you want to run it on Godbolt:
#include <iostream>
#include <sstream>
#include <chrono>
namespace timestuff {
// type alias for clock for entire app
using clock_type = std::chrono::high_resolution_clock;
using duration_t = clock_type::duration;
using timepoint_t = std::chrono::time_point<clock_type, duration_t>;
using zoned_time_t = std::chrono::zoned_time<duration_t>;
std::string to_eastern_string(zoned_time_t tp)
{
std::ostringstream oss;
//oss << std::format("{0:%Y-%m-%d %T%Z}", tp);
//oss << std::format("{0:%Y-%m-%d %T}", tp);
oss << std::format("{0:%Y-%m-%d %T}", tp);
return oss.str();
}
timepoint_t parse_eastern_time(const std::string& dtstr) {
///std::istringstream iss{dtstr};
///zoned_time_t tp{};
///iss >> std::chrono::parse("%Y-%m-%d %H:%M:%S.%f",tp);
///return tp.get_sys_time();
// Format of data stored in eastern time: "2024-11-19 09:30:00.037000"
std::tm tm = {};
std::stringstream ss(dtstr);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S.%f");
timepoint_t tp = clock_type::from_time_t(std::mktime(&tm));
return tp;
}
}
int main() {
using namespace timestuff;
auto ny_time_zone = std::chrono::locate_zone("America/New_York");
timestuff::zoned_time_t ny_time(ny_time_zone, clock_type::now());
std::string now_string = to_eastern_string(ny_time);
std::cout << now_string <<"\n";
// yay
std::string dtstr = "2024-11-19 09:30:00.037000";
std::cout << to_eastern_string(parse_eastern_time(dtstr)) << "\n";
std::string dtstr2 = "2024-11-21 15:59:59.823";
std::cout << to_eastern_string(parse_eastern_time(dtstr2)) << "\n";
return 0;
}
zoned_time
is a higher-level API and often more convenient to work with. However in this case, I suspect zoned_time
might be abstracting so much that it is actually confusing the situation. So I'm going to drop down a level in abstraction and not use it. Instead I'll show a solution with just local_time
and time_zone
.
local_time
is a std::chrono::time_point
that refers to a local time with respect to some not-yet-specified time_zone
. And one can associate a time_zone
with a local_time
by using the time_zone
to translate back and forth between local_time
and sys_time
. sys_time
is just a time_point
based on system_clock
and represents UTC.
Starting at the top of your code:
Don't use high_resolution_clock
. It has no portable relationship with any calendar. In practice it is always a type alias for system_clock
or steady_clock
, and which is dependent on platform. Just choose system_clock
or steady_clock
. For parsing and formatting date/times in the civil calendar, your choice drops to just system_clock
:
using clock_type = std::chrono::system_clock;
Since you want to maintain precision at microseconds
, let's do that uniformly throughout your program:
using duration_t = std::chrono::microseconds;
using timepoint_t = std::chrono::sys_time<duration_t>;
sys_time
is just a type alias for time_point<system_clock, Duration>
.
Because of this expression in main
: to_eastern_string(parse_eastern_time(dtstr2))
, I'm seeing that the return type of parse_eastern_time
must be the same (or implicitly convertible to) the argument for to_eastern_string
. Since parse_eastern_time
returns timepoint_t
, I believe to_eastern_string
should have as its argument type time_point_t
.
std::string to_eastern_string(timepoint_t tp)
{
std::ostringstream oss;
auto tz = std::chrono::locate_zone("America/New_York");
oss << std::format("{:%F %T}", tz->to_local(tp));
return oss.str();
}
Above I use the time_zone
"America/New_York" to translate tp
to a local_time
. The format string {:%F %T}
will output the full precision of tp
. %F
is simply a short cut for %Y-%m-%d
.
timepoint_t parse_eastern_time(std::string dtstr)
{
std::istringstream iss{std::move(dtstr)};
std::chrono::local_time<duration_t> tp;
iss >> std::chrono::parse("%F %T",tp);
return std::chrono::locate_zone("America/New_York")->to_sys(tp);
}
Above I parse into a local_time
and then associate that local_time
with "America/New_York" by using that time_zone
to translate the local_time
to sys_time
. This all using the desired precision duration_t
.
Now main
:
int main() {
using namespace timestuff;
timepoint_t utc_time = floor<duration_t>(clock_type::now());
std::string now_string = to_eastern_string(utc_time);
std::cout << now_string <<"\n";
// yay
std::string dtstr = "2024-11-19 09:30:00.037000";
std::cout << to_eastern_string(parse_eastern_time(dtstr)) << "\n";
std::string dtstr2 = "2024-11-21 15:59:59.823";
std::cout << to_eastern_string(parse_eastern_time(dtstr2)) << "\n";
return 0;
}
As soon as system_clock::now()
is called, that is floored into the desired precision. After this one step, the entire program traffics in microseconds precision.
Example output:
2024-11-24 20:36:50.071210
2024-11-19 09:30:00.037000
2024-11-21 15:59:59.823000