I have written the below small program that tries to initialize a tzdb before the main()
runs. So once the main function runs, it checks the two global variables (that are already initialized at that stage) to determine if an error has occurred during the initialization process.
This is done using three functions:
try_initialize_timezone_database
allocate_and_get_buffer_for_timezone_names
retrieve_all_timezone_names
and two constant global variables
is_tzdb_initialized
all_timezone_names
that I have defined.
I have also intentionally made the functions have internal linkage and the variables have external linkage since the functions will only be called once but the variables will be accessed throughout the program.
The four main goals here are:
- to initialize the timezone database;
- to get a
span
(the 2nd global variable) that gives aconst
view to thepmr::vector
that contains all the names stored on its static buffer; - all the operations must be
noexcept
(and actually not throw!!); - and lastly, efficiency.
Here:
#include <chrono>
#include <stdexcept>
#include <expected>
#include <functional>
#include <vector>
#include <string_view>
#include <system_error>
#include <array>
#include <memory_resource>
#include <new>
#include <exception>
#include <span>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <cstddef>
namespace
{
[[ nodiscard ]] bool
try_initialize_timezone_database( ) noexcept
{
bool is_initialized { };
try
{
[[ maybe_unused ]] const auto& tzdb_list { std::chrono::get_tzdb_list( ) };
is_initialized = true;
}
catch ( const std::runtime_error& )
{ }
return is_initialized;
}
[[ nodiscard ]] std::expected<std::reference_wrapper<std::pmr::vector<std::string_view>>, std::errc>
allocate_and_get_buffer_for_timezone_names( const std::size_t timezone_names_count ) noexcept
{
constexpr auto timezone_names_default_count { 1024uz };
constexpr auto buffer_size_in_bytes { timezone_names_default_count * sizeof( std::string_view ) };
static std::array<std::byte, buffer_size_in_bytes> buffer;
static std::pmr::monotonic_buffer_resource buffer_resource { std::data( buffer ), std::size( buffer ) };
static std::pmr::vector<std::string_view> names_buffer { &buffer_resource };
try
{
names_buffer.reserve( timezone_names_count );
}
catch ( const std::bad_alloc& )
{
return std::unexpected { std::errc::not_enough_memory };
}
catch ( const std::length_error& )
{
return std::unexpected { std::errc::value_too_large };
}
catch ( const std::exception& )
{
return std::unexpected { std::errc::resource_unavailable_try_again };
}
return names_buffer;
}
[[ nodiscard ]] std::expected<std::span<const std::string_view>, std::errc>
retrieve_all_timezone_names( const bool is_tzdb_initialized ) noexcept
{
if ( is_tzdb_initialized == false ) return std::unexpected { std::errc::state_not_recoverable };
const auto timezone_names_count { std::chrono::get_tzdb( ).zones.size( ) +
std::chrono::get_tzdb( ).links.size( ) };
auto timezone_names_buffer { allocate_and_get_buffer_for_timezone_names( timezone_names_count ) };
if ( timezone_names_buffer.has_value( ) == false ) return std::unexpected { timezone_names_buffer.error( ) };
auto& names_buffer { timezone_names_buffer->get( ) };
constexpr auto get_name { [ ]( const auto& tz ) noexcept { return tz.name( ); } };
std::ranges::transform( std::chrono::get_tzdb( ).zones, std::back_inserter( names_buffer ), get_name );
std::ranges::transform( std::chrono::get_tzdb( ).links, std::back_inserter( names_buffer ), get_name );
return names_buffer;
}
}
const auto is_tzdb_initialized { try_initialize_timezone_database( ) };
const auto all_timezone_names { retrieve_all_timezone_names( is_tzdb_initialized ) };
int main( )
{
if ( is_tzdb_initialized == false )
{
std::cout << "The timezone database could not be initialized\n";
return 1;
}
if ( all_timezone_names.has_value( ) == false )
{
std::cout << "An error occurred during buffer allocation\n";
return 1;
}
const auto timezone_names { *all_timezone_names };
std::ranges::for_each( timezone_names, [ ]( const auto timezone_name ) { std::cout << timezone_name << '\n'; } );
}
Note 1: As can be seen, the std::expected
in the return type of the function allocate_and_get_buffer_for_timezone_names
stores the vector as a reference to the actual vector object that has static storage duration (due to obvious reasons). I made this decision to avoid losing the vector and causing UB. I'm really not sure if this makes the program correct.
Note 2: I have used a static std::pmr::vector
and a static std::array
(as the underlying buffer) to avoid a dynamic allocation if possible. The size of the aforementioned array is 1024 * 16 (16384 bytes, aka 4 memory pages). In the case the actuall count of the timezone names is greater than 1024 (currently 597 on my machine), the call to reserve
should not use that buffer and instead allocate a bigger one (typically on the heap). This is my expectation.
Is this a satisfactory way of achieving the above goals? Is there any UB anywhere in the code? Or anything that can be improved?