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 database
    • get_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();
}

See @Compiler Explorer

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");
}

See @Compiler Explorer

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:

  1. 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
  2. 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.