Replace list<TZ_DB> with tzdb_list

* tzdb_list is a singly linked list with an atomic head
* push_front() and front() are thread safe.
This commit is contained in:
Howard Hinnant 2017-08-06 18:25:07 -04:00
parent 80a142407a
commit 859a50a70e
2 changed files with 159 additions and 41 deletions

110
tz.cpp
View File

@ -96,7 +96,6 @@
#include <fstream>
#include <iostream>
#include <iterator>
#include <list>
#include <memory>
#if USE_OS_TZDB
# include <queue>
@ -320,21 +319,54 @@ struct undocumented {explicit undocumented() = default;};
static_assert(min_year <= max_year, "Configuration error");
#endif
static TZ_DB init_tzdb();
static std::unique_ptr<TZ_DB> init_tzdb();
tzdb_list::~tzdb_list()
{
const TZ_DB* ptr = head_;
head_ = nullptr;
while (ptr != nullptr)
{
auto next = ptr->next;
delete ptr;
ptr = next;
}
}
tzdb_list::tzdb_list(tzdb_list&& x) noexcept
: head_{x.head_.exchange(nullptr)}
{
}
void
tzdb_list::push_front(TZ_DB* tzdb) noexcept
{
tzdb->next = head_;
head_ = tzdb;
}
tzdb_list::const_iterator
tzdb_list::erase_after(const_iterator p) noexcept
{
auto t = p.p_->next;
p.p_->next = p.p_->next->next;
delete t;
return ++p;
}
static
std::list<TZ_DB>
tzdb_list
create_tzdb()
{
std::list<TZ_DB> tz_db;
tz_db.push_back(init_tzdb());
tzdb_list tz_db;
tz_db.push_front(init_tzdb().release());
return tz_db;
}
std::list<TZ_DB>&
tzdb_list&
get_tzdb_list()
{
static std::list<TZ_DB> tz_db = create_tzdb();
static tzdb_list tz_db = create_tzdb();
return tz_db;
}
@ -2493,10 +2525,10 @@ get_version()
# endif
static
TZ_DB
std::unique_ptr<TZ_DB>
init_tzdb()
{
TZ_DB db;
std::unique_ptr<TZ_DB> db(new TZ_DB);
//Iterate through folders
std::queue<std::string> subfolders;
@ -2535,22 +2567,22 @@ init_tzdb()
}
else
{
db.zones.emplace_back(subname.substr(sizeof(tz_dir)),
detail::undocumented{});
db->zones.emplace_back(subname.substr(sizeof(tz_dir)),
detail::undocumented{});
}
}
}
closedir(dir);
}
db.zones.shrink_to_fit();
std::sort(db.zones.begin(), db.zones.end());
db->zones.shrink_to_fit();
std::sort(db->zones.begin(), db->zones.end());
# if !MISSING_LEAP_SECONDS
std::ifstream in(tz_dir + std::string(1, folder_delimiter) + "right/UTC",
std::ios_base::binary);
if (in)
{
in.exceptions(std::ios::failbit | std::ios::badbit);
db.leaps = load_just_leaps(in);
db->leaps = load_just_leaps(in);
}
else
{
@ -2559,11 +2591,11 @@ init_tzdb()
if (!in)
throw std::runtime_error("Unable to extract leap second information");
in.exceptions(std::ios::failbit | std::ios::badbit);
db.leaps = load_just_leaps(in);
db->leaps = load_just_leaps(in);
}
# endif // !MISSING_LEAP_SECONDS
# ifdef __APPLE__
db.version = get_version();
db->version = get_version();
# endif
return db;
}
@ -3200,7 +3232,7 @@ get_version(const std::string& path)
}
static
TZ_DB
std::unique_ptr<TZ_DB>
init_tzdb()
{
using namespace date;
@ -3208,7 +3240,7 @@ init_tzdb()
const std::string path = install + folder_delimiter;
std::string line;
bool continue_zone = false;
TZ_DB db;
std::unique_ptr<TZ_DB> db(new TZ_DB);
#if AUTO_DOWNLOAD
if (!file_exists(install))
@ -3233,18 +3265,18 @@ init_tzdb()
msg += "\"";
throw std::runtime_error(msg);
}
db.version = get_version(path);
db->version = get_version(path);
}
else
{
db.version = get_version(path);
db->version = get_version(path);
auto rv = remote_version();
if (!rv.empty() && db.version != rv)
if (!rv.empty() && db->version != rv)
{
if (remote_download(rv))
{
remote_install(rv);
db.version = get_version(path);
db->version = get_version(path);
}
}
}
@ -3256,7 +3288,7 @@ init_tzdb()
msg += "\"";
throw std::runtime_error(msg);
}
db.version = get_version(path);
db->version = get_version(path);
#endif // !AUTO_DOWNLOAD
CONSTDATA char*const files[] =
@ -3278,27 +3310,27 @@ init_tzdb()
in >> word;
if (word == "Rule")
{
db.rules.push_back(Rule(line));
db->rules.push_back(Rule(line));
continue_zone = false;
}
else if (word == "Link")
{
db.links.push_back(link(line));
db->links.push_back(link(line));
continue_zone = false;
}
else if (word == "Leap")
{
db.leaps.push_back(leap(line, detail::undocumented{}));
db->leaps.push_back(leap(line, detail::undocumented{}));
continue_zone = false;
}
else if (word == "Zone")
{
db.zones.push_back(time_zone(line, detail::undocumented{}));
db->zones.push_back(time_zone(line, detail::undocumented{}));
continue_zone = true;
}
else if (line[0] == '\t' && continue_zone)
{
db.zones.back().add(line);
db->zones.back().add(line);
}
else
{
@ -3307,19 +3339,19 @@ init_tzdb()
}
}
}
std::sort(db.rules.begin(), db.rules.end());
Rule::split_overlaps(db.rules);
std::sort(db.zones.begin(), db.zones.end());
db.zones.shrink_to_fit();
std::sort(db.links.begin(), db.links.end());
db.links.shrink_to_fit();
std::sort(db.leaps.begin(), db.leaps.end());
db.leaps.shrink_to_fit();
std::sort(db->rules.begin(), db->rules.end());
Rule::split_overlaps(db->rules);
std::sort(db->zones.begin(), db->zones.end());
db->zones.shrink_to_fit();
std::sort(db->links.begin(), db->links.end());
db->links.shrink_to_fit();
std::sort(db->leaps.begin(), db->leaps.end());
db->leaps.shrink_to_fit();
#ifdef _WIN32
std::string mapping_file = get_install() + folder_delimiter + "windowsZones.xml";
db.mappings = load_timezone_mappings_from_xml_file(mapping_file);
sort_zone_mappings(db.mappings);
db->mappings = load_timezone_mappings_from_xml_file(mapping_file);
sort_zone_mappings(db->mappings);
#endif // _WIN32
return db;
@ -3333,7 +3365,7 @@ reload_tzdb()
if (!v.empty() && v == remote_version())
return get_tzdb_list().front();
#endif // AUTO_DOWNLOAD
get_tzdb_list().push_front(init_tzdb());
get_tzdb_list().push_front(init_tzdb().release());
return get_tzdb_list().front();
}

90
tz.h
View File

@ -110,10 +110,10 @@ static_assert(HAS_REMOTE_API == 0 ? AUTO_DOWNLOAD == 0 : true,
#endif
#include <algorithm>
#include <atomic>
#include <cassert>
#include <chrono>
#include <istream>
#include <list>
#include <locale>
#include <memory>
#include <mutex>
@ -1144,6 +1144,7 @@ struct TZ_DB
#ifdef _WIN32
std::vector<detail::timezone_mapping> mappings;
#endif
TZ_DB* next = nullptr;
TZ_DB() = default;
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
@ -1183,7 +1184,92 @@ DATE_API std::ostream&
operator<<(std::ostream& os, const TZ_DB& db);
DATE_API const TZ_DB& get_tzdb();
DATE_API std::list<TZ_DB>& get_tzdb_list();
class tzdb_list
{
std::atomic<TZ_DB*> head_{nullptr};
public:
~tzdb_list();
tzdb_list() = default;
tzdb_list(tzdb_list&& x) noexcept;
void push_front(TZ_DB* tzdb) noexcept;
const TZ_DB& front() const noexcept {return *head_;}
class const_iterator;
const_iterator begin() const noexcept;
const_iterator end() const noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
const_iterator erase_after(const_iterator p) noexcept;
};
class tzdb_list::const_iterator
{
TZ_DB* p_ = nullptr;
explicit const_iterator(TZ_DB* p) noexcept : p_{p} {}
public:
const_iterator() = default;
using iterator_category = std::forward_iterator_tag;
using value_type = TZ_DB;
using reference = const value_type&;
using pointer = const value_type*;
using difference_type = std::ptrdiff_t;
reference operator*() const noexcept {return *p_;}
pointer operator->() const noexcept {return p_;}
const_iterator& operator++() noexcept {p_ = p_->next; return *this;}
const_iterator operator++(int) noexcept {auto t = *this; ++(*this); return t;}
friend
bool
operator==(const const_iterator& x, const const_iterator& y) noexcept
{return x.p_ == y.p_;}
friend
bool
operator!=(const const_iterator& x, const const_iterator& y) noexcept
{return !(x == y);}
friend class tzdb_list;
};
inline
tzdb_list::const_iterator
tzdb_list::begin() const noexcept
{
return const_iterator{head_};
}
inline
tzdb_list::const_iterator
tzdb_list::end() const noexcept
{
return const_iterator{nullptr};
}
inline
tzdb_list::const_iterator
tzdb_list::cbegin() const noexcept
{
return begin();
}
inline
tzdb_list::const_iterator
tzdb_list::cend() const noexcept
{
return end();
}
DATE_API tzdb_list& get_tzdb_list();
#if !USE_OS_TZDB