I used this open-source preview of the C++20 chrono library (which works with C++11/14/17) to achieve about 100ns per call on macOS.
#include "date/tz.h"
#include <algorithm>
#include <array>
#include <iostream>
void
to_chars(char*, char* l, unsigned long long x)
{
do
{
*--l = static_cast<char>((x % 10) + '0');
x /= 10;
} while(x != 0);
}
// yyyy-mm-dd hh:mm:ss.ffffff
// 01234567890123456789012345
auto
format_current_time()
{
using namespace std;
using namespace std::chrono;
using namespace date;
auto tp = system_clock::now();
static auto const tz = current_zone();
static auto info = tz->get_info(tp);
if (tp >= info.end)
info = tz->get_info(tp);
auto tpl = local_days{} + (tp + info.offset - sys_days{});
auto tpd = floor<days>(tpl);
year_month_day ymd{tpd};
hh_mm_ss hms{tpl-tpd};
array<char, 21+hms.fractional_width> result{};
auto r = result.data();
fill(r, r + result.size()-1, '0');
r[4] = r[7] = '-';
r[10] = ' ';
r[13] = r[16] = ':';
r[19] = '.';
::to_chars(r, r+4, int{ymd.year()});
::to_chars(r+5, r+7, unsigned{ymd.month()});
::to_chars(r+8, r+10, unsigned{ymd.day()});
::to_chars(r+11, r+13, hms.hours().count());
::to_chars(r+14, r+16, hms.minutes().count());
::to_chars(r+17, r+19, hms.seconds().count());
::to_chars(r+20, r+20+hms.fractional_width, hms.subseconds().count());
return result;
}
int
main()
{
auto t0 = format_current_time();
auto t1 = format_current_time();
auto t2 = format_current_time();
auto t3 = format_current_time();
auto t4 = format_current_time();
auto t5 = format_current_time();
auto t6 = format_current_time();
auto t7 = format_current_time();
auto t8 = format_current_time();
auto t9 = format_current_time();
auto t10 = format_current_time();
auto t11 = format_current_time();
auto t12 = format_current_time();
auto t13 = format_current_time();
auto t14 = format_current_time();
auto t15 = format_current_time();
auto t16 = format_current_time();
auto t17 = format_current_time();
auto t18 = format_current_time();
auto t19 = format_current_time();
std::cout << t0.data() << '
';
std::cout << t1.data() << '
';
std::cout << t2.data() << '
';
std::cout << t3.data() << '
';
std::cout << t4.data() << '
';
std::cout << t5.data() << '
';
std::cout << t6.data() << '
';
std::cout << t7.data() << '
';
std::cout << t8.data() << '
';
std::cout << t9.data() << '
';
std::cout << t10.data() << '
';
std::cout << t11.data() << '
';
std::cout << t12.data() << '
';
std::cout << t13.data() << '
';
std::cout << t14.data() << '
';
std::cout << t15.data() << '
';
std::cout << t16.data() << '
';
std::cout << t17.data() << '
';
std::cout << t18.data() << '
';
std::cout << t19.data() << '
';
}
This just output for me:
2021-01-09 17:44:55.778356
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034500
2021-01-09 17:44:56.034500
2021-01-09 17:44:56.034500
2021-01-09 17:44:56.034500
This code assumes that the device does not change the current time zone during the application run (which may not be true for a mobile device). If that assumption can't be made, then the time per call balloons to about 50us. However a compromise could be made to check current_zone()
say only once per second (or whatever) to bring the average time back down.
As written, the first step is to get the current UTC time:
auto tp = system_clock::now();
The next step is to get the current local time zone setting, and all the information there is to know about this time zone setting at this time. All of this information is stored in function-local statics as it is assumed that the time zone doesn't change at all, and that the UTC offset changes rarely (e.g. twice a year):
static auto const tz = current_zone();
static auto info = tz->get_info(tp);
info
contains an end
member that is a UTC timestamp that marks the end of when this information is valid. If tp
exceeds the range of validity, then the information is updated (this typically happens at most twice a year):
if (tp >= info.end)
info = tz->get_info(tp);
Next the UTC offset is applied to the current system_clock
time_point
and stored in distinct chrono::time_point
type called local_time
(with the same precision as system_clock::time_point
):
auto tpl = local_days{} + (tp + info.offset - sys_days{});
Then this chrono::time_point
is broken down into two data structures. The first is a {year, month, day}
data structure:
auto tpd = floor<days>(tpl);
year_month_day ymd{tpd};
The first line simply truncates the time_point
to days
precision (a count of days since the local epoch). Then the second line converts the count of days into a {year, month, day}
data structure in the civil calendar.
Then the local time of day is converted into a {hours, minutes, seconds, subseconds}
data structure:
hh_mm_ss hms{tpl-tpd};
The units of "subseconds" depends on system_clock::duration
. On macOS it is 1us. On Windows it is 100ns.
Everything after this is formatting the values in ymd
and hms
. The results are stored in a std::array<char, N>
to avoid an allocation. I know you have your own formatting, but I show mine so that you know how to access the values in ymd
and hms
. On Windows, hms.fractional_width
is the compile-time integral constant 7 (6 on macOS, 9 when using gcc).