I have a UTC offset that is not known until run-time, and is not necessarily an integral number of hours. The IANA time zone database isn't a good match for this.
What is the simplest user-written time zone that can handle this?
Occasionally the IANA time zone database doesn't quite do everything you want.
The <chrono> library allows you to use zoned_time with a time zone and/or
pointer to time_zone of your own making. Below is an example which supplies a custom
time_zone called OffsetZone which can hold a UTC offset with minutes
precision determined at run time.
#include <chrono>
class OffsetZone
{
std::chrono::minutes offset_;
public:
explicit OffsetZone(std::chrono::minutes offset)
: offset_{offset}
{}
template <class Duration>
auto
to_local(std::chrono::sys_time<Duration> tp) const
{
return std::chrono::local_time{(tp + offset_).time_since_epoch()};
}
template <class Duration>
auto
to_sys(std::chrono::local_time<Duration> tp) const
{
return std::chrono::sys_time{(tp - offset_).time_since_epoch()};
}
};
This is about as simple as it gets with a user-written time_zone. But it can
get much more elaborate as you need more functionality. But with just this
simple example one can:
time_zone.time_zone into a zoned_time.sys_time out of the zoned_time.local_time out of the zoned_time.Example:
#include <iostream>
int
main()
{
using namespace std::chrono;
auto offset = 3h + 45min;
OffsetZone tz{offset};
auto tp_utc = sys_days{2025y/5/3} + 22h;
zoned_time zt{&tz, tp_utc};
std::cout << zt.get_sys_time() << " UTC\n";
std::cout << zt.get_local_time() << '\n';
}
The example above creates a UTC offset of 3 hours plus 45 minutes. This is
stored in a non-constexpr variable to emphasize that this does not have to be
compile-time information (maybe it was parsed from a file). This offset is used
to construct the OffsetZone on the stack. And then a pointer to this "time
zone" is used as the first argument to std::chrono::zoned_time.
Output:
2025-05-03 22:00:00 UTC
2025-05-04 01:45:00
Since we constructed the zoned_time with a sys_time, when we get the
sys_time back out, it is the same time that was input. And the local_time
is 3 hours, 45 minutes later. But one could instead construct the
zoned_time with a local_time just by using local_days in place of sys_days:
Example:
#include <iostream>
int
main()
{
using namespace std::chrono;
auto offset = 3h + 45min;
OffsetZone tz{offset};
auto tp_loc = local_days{2025y/5/4} + 1h + 45min;
zoned_time zt{&tz, tp_loc};
std::cout << zt.get_sys_time() << " UTC\n";
std::cout << zt.get_local_time() << '\n';
}
Output:
2025-05-03 22:00:00 UTC
2025-05-04 01:45:00
You don't have to use a built-in pointer to your time zone. You could just as
easily use unique_ptr, shared_ptr, or whatever smart pointer is right for your
application.
One can even have OffsetZone serve as its own smart pointer by giving it a
member operator->() that returns itself:
const OffsetZone* operator->() const {return this;}
This allows you to embed the OffsetZone directly into the zoned_time instead
of pointing to an externally held OffsetZone:
zoned_time zt{OffsetZone{offset}, tp_utc};
Once constructed in this fashion, use of the zoned_time continues as shown before.
I should also emphasize that one can use the time zone directly, without the use of a zoned_time (just like with std::chrono::time_zone):
Example:
#include <cassert>
#include <iostream>
int
main()
{
using namespace std::chrono;
auto offset = 3h + 45min;
OffsetZone tz{offset};
auto tp_loc = local_days{2025y/5/4} + 1h + 45min;
auto tp_utc = tz.to_sys(tp_loc);
auto tp_loc2 = tz.to_local(tp_utc);
assert(tp_loc == tp_loc2);
std::cout << tp_utc << " UTC\n";
std::cout << tp_loc2 << '\n';
}
Output:
2025-05-03 22:00:00 UTC
2025-05-04 01:45:00