Last Update:
Around the World in C++: Exploring Time Zones with std::chrono
Table of Contents
While most time zones use simple hour offsets from UTC, some regions have chosen unusual time differences. In this blog post, we’ll explore how we can discover such zones using C++20’s chrono library.
We’ll use GCC 14.2 as it fully supports C++20 chrono and also std::print
from C++23.
First Attempt: Basic Zone Iteration
C++20 introduced comprehensive time zone support through the <chrono>
library. The implementation relies on the IANA Time Zone Database (also known as the “tz database” or “zoneinfo”), which is the de facto standard for time zone information used by most operating systems and programming languages.
The Time Zone Database
In C++20, the time zone database is represented by the tzdb
class:
namespace std::chrono {
struct tzdb {
string version; // IANA time zone database version
vector<time_zone> zones; // Time zone container
vector<time_zone_link> links; // Alternative names
vector<leap_second> leap_seconds; // Leap second info
};
}
Essential aspects of the database:
- It’s read-only - users cannot construct or modify a
tzdb
- Access is provided through two global functions:
get_tzdb()
: Returns the current databaseget_tzdb_list()
: Returns the list of available database versions
Here’s how to access the database:
#include <chrono>
#include <print>
#include <cassert>
int main() {
const auto& db = std::chrono::get_tzdb();
std::print("Current DB version: {}\n", db.version);
const auto& db_list = std::chrono::get_tzdb_list();
auto size = std::distance(db_list.begin(), db_list.end());
std::print("Available versions: {}\n", size);
assert(&db_list.front() == &db);
}
See at Compiler Explorer
The database provides several key components:
Zones (db.zones
):
- Primary container of time zone information
- Each zone contains complete historical and current rules
- Examples: “America/New_York”, “Europe/London”
Links (db.links
):
- Alternative names for existing zones
- Useful for backward compatibility
- Example: “EST” links to “America/New_York”
Leap Seconds (db.leap_seconds
):
- Records of historical leap second adjustments
- Used for precise astronomical calculations
Let’s start with a simple approach to explore the zones:
#include <chrono>
#include <print>
int main() {
const auto& db = std::chrono::get_tzdb();
auto now = std::chrono::system_clock::now();
std::print("Time Zone Database Version: {}\n", db.version);
std::print("Number of zones: {}\n", db.zones.size());
std::print("Number of links: {}\n", db.links.size());
std::print("Number of leap seconds: {}\n\n", db.leap_seconds.size());
for (const auto& zone : db.zones) {
try {
auto info = zone.get_info(now);
std::print("{:<30} offset: {:>6}s abbrev: {:<6} ",
zone.name(),
info.offset.count(),
info.abbrev);
// Show if DST is active
if (info.save != std::chrono::minutes{0})
std::print("DST: {} min", info.save.count());
std::print("\n");
}
catch (const std::exception& e) {
std::print("Error with zone {}: {}\n", zone.name(), e.what());
}
}
}
See at Compiler Explorer
Sample output:
Time Zone Database Version: 2024a
Number of zones: 447
Number of links: 150
Number of leap seconds: 27
Africa/Abidjan offset: 0s abbrev: GMT
Africa/Accra offset: 0s abbrev: GMT
Africa/Addis_Ababa offset: 10800s abbrev: EAT
...
While this basic iteration gives us a good overview, the raw offset numbers make it difficult to spot unusual cases. Some zones use fractional hours, and others have unique DST rules. Let’s improve our search to find these special cases.
Finding Unusual Offsets
What makes a time zone unusual? Usually, it’s:
- Non-hour offsets (like UTC+5:45)
- Unusual DST rules (like 30-minute DST changes)
Here’s our improved code:
#include <chrono>
#include <print>
#include <vector>
#include <set>
void find_unusual_zones() {
const auto& db = std::chrono::get_tzdb();
auto now = std::chrono::system_clock::now();
// Track unique unusual offsets
std::set<std::chrono::seconds> unusual_offsets;
for (const auto& zone : db.zones) {
try {
auto info = zone.get_info(now);
// Check if offset is not a whole hour
if (info.offset % std::chrono::hours{1} != std::chrono::seconds{0}) {
unusual_offsets.insert(info.offset);
std::print("Found unusual zone: {}\n", zone.name());
std::print(" Offset: {:+.2f} hours\n",
static_cast<double>(info.offset.count()) / 3600.0);
std::print(" DST adjustment: {} minutes\n\n",
info.save.count());
}
}
catch (const std::exception&) {
// Skip problematic zones
}
}
std::print("Total unusual offsets found: {}\n", unusual_offsets.size());
}
int main() {
find_unusual_zones();
}
Running this code reveals several interesting time zones:
Found unusual zone: America/St_Johns
Offset: -3.50 hours
DST adjustment: 0 minutes
Found unusual zone: Asia/Colombo
Offset: +5.50 hours
DST adjustment: 0 minutes
Found unusual zone: Asia/Kabul
Offset: +4.50 hours
DST adjustment: 0 minutes
Found unusual zone: Asia/Kathmandu
Offset: +5.75 hours
DST adjustment: 0 minutes
...
Total unusual offsets found: 11
Exploring Unusual Zones
Now let’s examine two particularly interesting zones we found using TimeZoneExplorer:
#include <chrono>
#include <print>
#include <string_view>
class TimeZoneExplorer {
public:
static void explore_zone(std::string_view zone_name) {
try {
using namespace std::chrono;
const auto zone = locate_zone(zone_name);
auto now = system_clock::now();
zoned_time zt{zone, now};
auto info = zone->get_info(now);
std::print("\n=== {} ===\n", zone_name);
std::print("Current time: {:%F %T %Z}\n", zt);
std::print("Valid period: [{}, {})\n", info.begin, info.end);
std::print("UTC offset: {}s ({:+.2f} hours)\n",
info.offset.count(),
static_cast<double>(info.offset.count()) / 3600.0);
if (info.save == 0min) {
std::print("DST: No (Standard Time)\n");
}
else {
std::print("DST: Yes (Saving: {} minutes)\n", info.save.count());
auto standard_offset = info.offset - info.save;
std::print("Standard Time offset would be: {}s ({:+.2f} hours)\n",
standard_offset.count(),
static_cast<double>(standard_offset.count()) / 3600.0);
}
std::print("Abbreviation: {} (Note: abbreviations are not unique)\n",
info.abbrev);
}
catch (const std::exception& e) {
std::print("Error exploring {}: {}\n", zone_name, e.what());
}
}
};
int main() {
TimeZoneExplorer::explore_zone("Asia/Kathmandu");
TimeZoneExplorer::explore_zone("Australia/Lord_Howe");
}
Output:
=== Asia/Kathmandu ===
Current time: 2024-11-11 02:00:42.203971363 +0545
Valid period: [1986-01-01 00:00:00, 32767-12-31 00:00:00)
UTC offset: 20700s (+5.75 hours)
DST: No (Standard Time)
Abbreviation: +0545 (Note: abbreviations are not unique)
=== Australia/Lord_Howe ===
Current time: 2024-11-11 07:15:42.209946634 +11
Valid period: [2024-10-05 15:30:00, 2025-04-05 15:00:00)
UTC offset: 39600s (+11.00 hours)
DST: Yes (Saving: 30 minutes)
Standard Time offset would be: 37800s (+10.50 hours)
Abbreviation: +11 (Note: abbreviations are not unique)
Let’s examine these unusual cases:
-
Nepal (Asia/Kathmandu):
- Uses UTC+5:45
- One of the few zones with a 45-minute offset
- No DST changes
- Offset hasn’t changed since 1986
-
Lord Howe Island (Australia/Lord_Howe):
- Base offset is UTC+10:30
- Uses 30-minute DST adjustment
- Only place in the world with a 30-minute DST change
Summary
C++20’s <chrono>
library and its time zone support make it easy to explore and work with the world’s diverse time zones. While most regions use simple hour offsets from UTC, we discovered several exceptions, like Nepal’s UTC+5:45 and Lord Howe Island’s unique 30-minute DST adjustment, showing how rich and complex our world’s timekeeping really is.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: