Revision control
Copy as Markdown
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qtimezoneprivate_p.h"
#include <chrono>
#include <optional>
QT_BEGIN_NAMESPACE
using namespace std::chrono;
using namespace std::chrono_literals;
#define EXCEPTION_CHECKED(expression, fallback) \
QT_TRY { \
expression; \
} QT_CATCH (const std::runtime_error &) { \
fallback; \
}
static std::chrono::sys_time<std::chrono::milliseconds>
chronoForEpochMillis(qint64 millis)
{
return sys_time<milliseconds>(milliseconds(millis));
}
static std::optional<std::chrono::sys_info>
infoAtEpochMillis(const std::chrono::time_zone *zone, qint64 millis)
{
if (!zone)
return std::nullopt;
EXCEPTION_CHECKED(return zone->get_info(chronoForEpochMillis(millis)), return std::nullopt);
}
static const std::chrono::time_zone *currentZone()
{
EXCEPTION_CHECKED(return std::chrono::current_zone(), return std::nullptr);
}
static const std::chrono::time_zone *idToZone(std::string_view id)
{
EXCEPTION_CHECKED(return get_tzdb().locate_zone(id), return nullptr);
}
static const std::vector<std::string_view> getAllZoneIds(std::optional<int> offset = std::nullopt)
{
const tzdb &db = get_tzdb(); // May throw std::runtime_error.
std::vector<std::string_view> raw, links, all;
auto now = system_clock::now();
for (const time_zone &zone : db.zones) {
if (offset) {
const sys_info info = zone.get_info(now);
if (info.offset.count() != *offset)
continue;
}
raw.push_back(zone.name());
}
for (const time_zone_link &link : db.links) {
if (offset) {
const std::string_view target = link.target();
const time_zone *zone = db.locate_zone(target);
const sys_info info = zone->get_info(now);
if (info.offset.count() != *offset)
continue;
}
links.push_back(link.name());
}
// Each is sorted, so we can unite them; and the result shall be sorted.
all.reserve(raw.size() + links.size());
std::set_union(raw.begin(), raw.end(), links.begin(), links.end(), std::back_inserter(all));
// We could in principle merge in also such of matchingTimeZoneIds(offset)
// as are known zones - our CLDR data says those also have this offset - but
// hopefully that's redundant.
return all;
}
static const std::optional<std::vector<std::string_view>>
allZoneIds(std::optional<int> offset = std::nullopt)
{
EXCEPTION_CHECKED(return getAllZoneIds(offset), return std::nullopt);
}
static QChronoTimeZonePrivate::Data
fromSysInfo(std::chrono::sys_info info, qint64 atMSecsSinceEpoch)
{
QString abbreviation = QString::fromLatin1(info.abbrev);
int offsetFromUtc = info.offset.count();
// offset is in seconds, save is in minutes
int standardTimeOffset = offsetFromUtc - seconds(info.save).count();
return QChronoTimeZonePrivate::Data(abbreviation, atMSecsSinceEpoch,
offsetFromUtc, standardTimeOffset);
}
QChronoTimeZonePrivate::QChronoTimeZonePrivate()
: m_timeZone(currentZone())
{
if (m_timeZone)
m_id.assign(m_timeZone->name());
}
QChronoTimeZonePrivate::QChronoTimeZonePrivate(QByteArrayView id)
: m_timeZone(idToZone(std::string_view(id.data(), id.size())))
{
if (m_timeZone)
m_id.assign(m_timeZone->name());
}
QChronoTimeZonePrivate *QChronoTimeZonePrivate::clone() const
{
return new QChronoTimeZonePrivate(*this);
}
QChronoTimeZonePrivate::~QChronoTimeZonePrivate()
= default;
QByteArray QChronoTimeZonePrivate::systemTimeZoneId() const
{
if (const time_zone *zone = currentZone()) {
std::string_view name = zone->name();
return {name.data(), qsizetype(name.size())};
}
return {};
}
bool QChronoTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId) const
{
return idToZone(std::string_view(ianaId.data(), ianaId.size())) != nullptr;
}
QList<QByteArray> QChronoTimeZonePrivate::availableTimeZoneIds() const
{
QList<QByteArray> result;
if (auto ids = allZoneIds()) {
result.reserve(ids->size());
for (std::string_view id : *ids)
result << QByteArray{id.data(), qsizetype(id.size())};
// Order preserved; they were already sorted and unique.
}
return result;
}
QList<QByteArray> QChronoTimeZonePrivate::availableTimeZoneIds(int utcOffset) const
{
QList<QByteArray> result;
if (auto ids = allZoneIds(utcOffset)) {
result.reserve(ids->size());
for (std::string_view id : *ids)
result << QByteArray{id.data(), qsizetype(id.size())};
// Order preserved; they were already sorted and unique.
}
return padSortedWithAliases(result);
}
QString QChronoTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
{
if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch))
return fromSysInfo(*info, atMSecsSinceEpoch).abbreviation;
return {};
}
int QChronoTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
{
if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch))
return fromSysInfo(*info, atMSecsSinceEpoch).offsetFromUtc;
return invalidSeconds();
}
int QChronoTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
{
// Subtracting minutes from seconds will convert the minutes to seconds.
if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch))
return int((info->offset - info->save).count());
return invalidSeconds();
}
int QChronoTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
{
if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch))
return int(std::chrono::seconds(info->save).count());
return invalidSeconds();
}
bool QChronoTimeZonePrivate::hasDaylightTime() const
{
Data data = QTimeZonePrivate::data(QTimeZone::DaylightTime);
return data.daylightTimeOffset != 0
&& data.daylightTimeOffset != invalidSeconds();
}
bool QChronoTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
{
if (auto info = infoAtEpochMillis(m_timeZone, atMSecsSinceEpoch))
return info->save != 0min;
return false;
}
QChronoTimeZonePrivate::Data
QChronoTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
{
if (auto info = infoAtEpochMillis(m_timeZone, forMSecsSinceEpoch))
return fromSysInfo(*info, forMSecsSinceEpoch);
return {};
}
bool QChronoTimeZonePrivate::hasTransitions() const
{
return true;
}
QChronoTimeZonePrivate::Data
QChronoTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
{
if (const auto info = infoAtEpochMillis(m_timeZone, afterMSecsSinceEpoch)) {
const auto tran = info->end;
qint64 when = milliseconds(tran.time_since_epoch()).count();
if (when > afterMSecsSinceEpoch)
return fromSysInfo(m_timeZone->get_info(tran), when);
// else we were already at (or after) the end-of-time "transition"
}
return {};
}
QChronoTimeZonePrivate::Data
QChronoTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
{
if (const auto info = infoAtEpochMillis(m_timeZone, beforeMSecsSinceEpoch - 1)) {
qint64 when = milliseconds(info->begin.time_since_epoch()).count();
if (when < beforeMSecsSinceEpoch)
return fromSysInfo(*info, when);
// else we were already at (or before) the start-of-time "transition"
}
return {};
}
QT_END_NAMESPACE