Implement USE_OS_TZDB for Android

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
This commit is contained in:
Vasyl Gello 2024-09-19 11:54:45 +03:00 committed by Howard Hinnant
parent ac0c58d5da
commit 3e43210885
2 changed files with 176 additions and 4 deletions

View File

@ -139,6 +139,11 @@ namespace date
enum class choose {earliest, latest}; enum class choose {earliest, latest};
#if defined(ANDROID) || defined(__ANDROID__)
struct tzdb;
static std::unique_ptr<tzdb> init_tzdb();
#endif // defined(ANDROID) || defined(__ANDROID__)
namespace detail namespace detail
{ {
struct undocumented; struct undocumented;
@ -821,6 +826,10 @@ public:
#if !USE_OS_TZDB #if !USE_OS_TZDB
DATE_API void add(const std::string& s); DATE_API void add(const std::string& s);
#else
#if defined(ANDROID) || defined(__ANDROID__)
friend std::unique_ptr<tzdb> init_tzdb();
#endif // defined(ANDROID) || defined(__ANDROID__)
#endif // !USE_OS_TZDB #endif // !USE_OS_TZDB
private: private:
@ -844,6 +853,9 @@ private:
DATE_API void DATE_API void
load_data(std::istream& inf, std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt, load_data(std::istream& inf, std::int32_t tzh_leapcnt, std::int32_t tzh_timecnt,
std::int32_t tzh_typecnt, std::int32_t tzh_charcnt); std::int32_t tzh_typecnt, std::int32_t tzh_charcnt);
# if defined(ANDROID) || defined(__ANDROID__)
void parse_from_android_tzdata(std::ifstream& inf, const std::size_t off);
# endif // defined(ANDROID) || defined(__ANDROID__)
#else // !USE_OS_TZDB #else // !USE_OS_TZDB
DATE_API sys_info get_info_impl(sys_seconds tp, int tz_int) const; DATE_API sys_info get_info_impl(sys_seconds tp, int tz_int) const;
DATE_API void adjust_infos(const std::vector<detail::Rule>& rules); DATE_API void adjust_infos(const std::vector<detail::Rule>& rules);

View File

@ -93,8 +93,25 @@
#endif #endif
#if defined(ANDROID) || defined(__ANDROID__) #if defined(ANDROID) || defined(__ANDROID__)
#include <sys/system_properties.h> # include <sys/system_properties.h>
#endif # if USE_OS_TZDB
# define MISSING_LEAP_SECONDS 1
// from https://android.googlesource.com/platform/bionic/+/master/libc/tzcode/bionic.cpp
static constexpr size_t ANDROID_TIMEZONE_NAME_LENGTH = 40;
struct bionic_tzdata_header_t {
char tzdata_version[12];
std::int32_t index_offset;
std::int32_t data_offset;
std::int32_t final_offset;
};
struct index_entry_t {
char buf[ANDROID_TIMEZONE_NAME_LENGTH];
std::int32_t start;
std::int32_t length;
std::int32_t unused; // Was raw GMT offset; always 0 since tzdata2014f (L).
};
# endif // USE_OS_TZDB
#endif // defined(ANDROID) || defined(__ANDROID__)
#if USE_OS_TZDB #if USE_OS_TZDB
# include <dirent.h> # include <dirent.h>
@ -465,7 +482,18 @@ discover_tz_dir()
{ {
struct stat sb; struct stat sb;
using namespace std; using namespace std;
# ifndef __APPLE__ # if defined(ANDROID) || defined(__ANDROID__)
CONSTDATA auto tz_dir_default = "/apex/com.android.tzdata/etc/tz";
CONSTDATA auto tz_dir_fallback = "/system/usr/share/zoneinfo";
// Check updatable path first
if(stat(tz_dir_default, &sb) == 0 && S_ISDIR(sb.st_mode))
return tz_dir_default;
else if(stat(tz_dir_fallback, &sb) == 0 && S_ISDIR(sb.st_mode))
return tz_dir_fallback;
else
throw runtime_error("discover_tz_dir failed to find zoneinfo\n");
# elif !defined(__APPLE__)
CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo"; CONSTDATA auto tz_dir_default = "/usr/share/zoneinfo";
CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc"; CONSTDATA auto tz_dir_buildroot = "/usr/share/zoneinfo/uclibc";
@ -523,7 +551,9 @@ get_tz_dir()
static_assert(min_year <= max_year, "Configuration error"); static_assert(min_year <= max_year, "Configuration error");
#endif #endif
#if !defined(ANDROID) && !defined(__ANDROID__)
static std::unique_ptr<tzdb> init_tzdb(); static std::unique_ptr<tzdb> init_tzdb();
#endif // !defined(ANDROID) && !defined(__ANDROID__)
tzdb_list::~tzdb_list() tzdb_list::~tzdb_list()
{ {
@ -616,7 +646,8 @@ static
bool bool
is_prefix_of(std::string const& key, std::string const& value) is_prefix_of(std::string const& key, std::string const& value)
{ {
return key.compare(0, key.size(), value, 0, key.size()) == 0; const size_t size = std::min(key.size(), value.size());
return key.compare(0, size, value, 0, size) == 0;
} }
static static
@ -2185,6 +2216,9 @@ time_zone::load_data(std::istream& inf,
void void
time_zone::init_impl() time_zone::init_impl()
{ {
#if defined(ANDROID) || defined(__ANDROID__)
return;
#endif // defined(ANDROID) || defined(__ANDROID__)
using namespace std; using namespace std;
using namespace std::chrono; using namespace std::chrono;
auto name = get_tz_dir() + ('/' + name_); auto name = get_tz_dir() + ('/' + name_);
@ -2346,6 +2380,86 @@ time_zone::get_info_impl(local_seconds tp) const
return i; return i;
} }
#if defined(ANDROID) || defined(__ANDROID__)
void
time_zone::parse_from_android_tzdata(std::ifstream& inf, const std::size_t off)
{
using namespace std;
using namespace std::chrono;
if (!inf.is_open())
throw std::runtime_error{"Unable to open tzdata"};
std::size_t restorepos = inf.tellg();
inf.seekg(off, inf.beg);
load_header(inf);
auto v = load_version(inf);
std::int32_t tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
tzh_timecnt, tzh_typecnt, tzh_charcnt;
skip_reserve(inf);
load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
tzh_timecnt, tzh_typecnt, tzh_charcnt);
if (v == 0)
{
load_data<int32_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt);
}
else
{
#if !defined(NDEBUG)
inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
tzh_ttisstdcnt + tzh_ttisgmtcnt);
load_header(inf);
auto v2 = load_version(inf);
assert(v == v2);
skip_reserve(inf);
#else // defined(NDEBUG)
inf.ignore((4+1)*tzh_timecnt + 6*tzh_typecnt + tzh_charcnt + 8*tzh_leapcnt +
tzh_ttisstdcnt + tzh_ttisgmtcnt + (4+1+15));
#endif // defined(NDEBUG)
load_counts(inf, tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt,
tzh_timecnt, tzh_typecnt, tzh_charcnt);
load_data<int64_t>(inf, tzh_leapcnt, tzh_timecnt, tzh_typecnt, tzh_charcnt);
}
#if !MISSING_LEAP_SECONDS
if (tzh_leapcnt > 0)
{
auto& leap_seconds = get_tzdb_list().front().leap_seconds;
auto itr = leap_seconds.begin();
auto l = itr->date();
seconds leap_count{0};
for (auto t = std::upper_bound(transitions_.begin(), transitions_.end(), l,
[](const sys_seconds& x, const transition& ct)
{
return x < ct.timepoint;
});
t != transitions_.end(); ++t)
{
while (t->timepoint >= l)
{
++leap_count;
if (++itr == leap_seconds.end())
l = sys_days(max_year/max_day);
else
l = itr->date() + leap_count;
}
t->timepoint -= leap_count;
}
}
#endif // !MISSING_LEAP_SECONDS
auto b = transitions_.begin();
auto i = transitions_.end();
if (i != b)
{
for (--i; i != b; --i)
{
if (i->info->offset == i[-1].info->offset &&
i->info->abbrev == i[-1].info->abbrev &&
i->info->is_dst == i[-1].info->is_dst)
i = transitions_.erase(i);
}
}
inf.seekg(restorepos, inf.beg);
}
#endif // defined(ANDROID) || defined(__ANDROID__)
std::ostream& std::ostream&
operator<<(std::ostream& os, const time_zone& z) operator<<(std::ostream& os, const time_zone& z)
{ {
@ -2782,6 +2896,16 @@ std::string
get_version() get_version()
{ {
using namespace std; using namespace std;
#if defined(ANDROID) || defined(__ANDROID__)
auto path = get_tz_dir() + string("/tzdata");
ifstream in{path};
bionic_tzdata_header_t hdr{};
if (in)
{
in.read(reinterpret_cast<char*>(&hdr), sizeof(bionic_tzdata_header_t));
return string(hdr.tzdata_version).replace(0, 6, "");
}
#else
auto path = get_tz_dir() + string("/+VERSION"); auto path = get_tz_dir() + string("/+VERSION");
ifstream in{path}; ifstream in{path};
string version; string version;
@ -2797,9 +2921,11 @@ get_version()
in >> version; in >> version;
return version; return version;
} }
#endif // defined(ANDROID) || defined(__ANDROID__)
return "unknown"; return "unknown";
} }
#if !defined(ANDROID) && !defined(__ANDROID__)
static static
std::vector<leap_second> std::vector<leap_second>
find_read_and_leap_seconds() find_read_and_leap_seconds()
@ -2881,6 +3007,7 @@ find_read_and_leap_seconds()
#endif #endif
return {}; return {};
} }
#endif // !defined(ANDROID) && !defined(__ANDROID__)
static static
std::unique_ptr<tzdb> std::unique_ptr<tzdb>
@ -2888,6 +3015,38 @@ init_tzdb()
{ {
std::unique_ptr<tzdb> db(new tzdb); std::unique_ptr<tzdb> db(new tzdb);
#if defined(ANDROID) || defined(__ANDROID__)
auto path = get_tz_dir() + std::string("/tzdata");
std::ifstream in{path};
if (!in)
throw std::runtime_error("Can not open " + path);
bionic_tzdata_header_t hdr{};
in.read(reinterpret_cast<char*>(&hdr), sizeof(bionic_tzdata_header_t));
if (!is_prefix_of(hdr.tzdata_version, "tzdata") || hdr.tzdata_version[11] != 0)
throw std::runtime_error("Malformed tzdata - invalid magic!");
maybe_reverse_bytes(hdr.index_offset);
maybe_reverse_bytes(hdr.data_offset);
maybe_reverse_bytes(hdr.final_offset);
if (hdr.index_offset > hdr.data_offset)
throw std::runtime_error("Malformed tzdata - hdr.index_offset > hdr.data_offset!");
const size_t index_size = hdr.data_offset - hdr.index_offset;
if ((index_size % sizeof(index_entry_t)) != 0)
throw std::runtime_error("Malformed tzdata - index size malformed!");
//Iterate through zone index
index_entry_t index_entry{};
for (size_t idx = 0; idx < index_size; idx += sizeof(index_entry_t)) {
in.read(reinterpret_cast<char*>(&index_entry), sizeof(index_entry_t));
maybe_reverse_bytes(index_entry.start);
maybe_reverse_bytes(index_entry.length);
time_zone timezone{std::string(index_entry.buf),
detail::undocumented{}};
timezone.parse_from_android_tzdata(in, hdr.data_offset + index_entry.start);
db->zones.emplace_back(std::move(timezone));
}
db->zones.shrink_to_fit();
std::sort(db->zones.begin(), db->zones.end());
db->version = std::string(hdr.tzdata_version).replace(0, 6, "");
#else
//Iterate through folders //Iterate through folders
std::queue<std::string> subfolders; std::queue<std::string> subfolders;
subfolders.emplace(get_tz_dir()); subfolders.emplace(get_tz_dir());
@ -2940,6 +3099,7 @@ init_tzdb()
std::sort(db->zones.begin(), db->zones.end()); std::sort(db->zones.begin(), db->zones.end());
db->leap_seconds = find_read_and_leap_seconds(); db->leap_seconds = find_read_and_leap_seconds();
db->version = get_version(); db->version = get_version();
#endif // defined(ANDROID) || defined(__ANDROID__)
return db; return db;
} }