Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qtimezoneprivate_icu.cpp
Go to the documentation of this file.
1// Copyright (C) 2013 John Layt <jlayt@kde.org>
2// Copyright (C) 2022 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qtimezone.h"
7#include "qtimezonelocale_p.h"
8
9#include <unicode/ucal.h>
10
11#include <qdebug.h>
12#include <qlist.h>
13
14#include <algorithm>
15
17
18/*
19 Private
20
21 ICU implementation
22*/
23
24// ICU utilities
25
26// Qt wrapper around ucal_getDefaultTimeZone()
28{
29 int32_t size = 30;
31 UErrorCode status = U_ZERO_ERROR;
32
33 // size = ucal_getDefaultTimeZone(result, resultLength, status)
34 size = ucal_getDefaultTimeZone(reinterpret_cast<UChar *>(result.data()), size, &status);
35
36 // If overflow, then resize and retry
37 if (status == U_BUFFER_OVERFLOW_ERROR) {
38 result.resize(size);
39 status = U_ZERO_ERROR;
40 size = ucal_getDefaultTimeZone(reinterpret_cast<UChar *>(result.data()), size, &status);
41 }
42
43 // If successful on first or second go, resize and return
44 if (U_SUCCESS(status)) {
45 result.resize(size);
46 return std::move(result).toUtf8();
47 }
48
49 return QByteArray();
50}
51
52// Qt wrapper around ucal_get() for offsets
53static bool ucalOffsetsAtTime(UCalendar *m_ucal, qint64 atMSecsSinceEpoch,
54 int *utcOffset, int *dstOffset)
55{
56 *utcOffset = 0;
57 *dstOffset = 0;
58
59 // Clone the ucal so we don't change the shared object
60 UErrorCode status = U_ZERO_ERROR;
61 UCalendar *ucal = ucal_clone(m_ucal, &status);
62 if (!U_SUCCESS(status))
63 return false;
64
65 // Set the date to find the offset for
66 status = U_ZERO_ERROR;
67 ucal_setMillis(ucal, atMSecsSinceEpoch, &status);
68
69 int32_t utc = 0;
70 if (U_SUCCESS(status)) {
71 status = U_ZERO_ERROR;
72 // Returns msecs
73 utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000;
74 }
75
76 int32_t dst = 0;
77 if (U_SUCCESS(status)) {
78 status = U_ZERO_ERROR;
79 // Returns msecs
80 dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000;
81 }
82
83 ucal_close(ucal);
84 if (U_SUCCESS(status)) {
85 *utcOffset = utc;
86 *dstOffset = dst;
87 return true;
88 }
89 return false;
90}
91
92#if U_ICU_VERSION_MAJOR_NUM >= 50
93// Qt wrapper around qt_ucal_getTimeZoneTransitionDate & ucal_get
94static QTimeZonePrivate::Data ucalTimeZoneTransition(UCalendar *m_ucal,
95 UTimeZoneTransitionType type,
96 qint64 atMSecsSinceEpoch)
97{
99
100 // Clone the ucal so we don't change the shared object
101 UErrorCode status = U_ZERO_ERROR;
102 UCalendar *ucal = ucal_clone(m_ucal, &status);
103 if (!U_SUCCESS(status))
104 return tran;
105
106 // Set the date to find the transition for
107 status = U_ZERO_ERROR;
108 ucal_setMillis(ucal, atMSecsSinceEpoch, &status);
109
110 // Find the transition time
111 UDate tranMSecs = 0;
112 status = U_ZERO_ERROR;
113 bool ok = ucal_getTimeZoneTransitionDate(ucal, type, &tranMSecs, &status);
114
115 // Catch a known violation (in ICU 67) of the specified behavior:
116 if (U_SUCCESS(status) && ok && type == UCAL_TZ_TRANSITION_NEXT) {
117 // At the end of time, that can "succeed" with tranMSecs ==
118 // atMSecsSinceEpoch, which should be treated as a failure.
119 // (At the start of time, previous correctly fails.)
120 ok = qint64(tranMSecs) > atMSecsSinceEpoch;
121 }
122
123 // Set the transition time to find the offsets for
124 if (U_SUCCESS(status) && ok) {
125 status = U_ZERO_ERROR;
126 ucal_setMillis(ucal, tranMSecs, &status);
127 }
128
129 int32_t utc = 0;
130 if (U_SUCCESS(status) && ok) {
131 status = U_ZERO_ERROR;
132 utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000;
133 }
134
135 int32_t dst = 0;
136 if (U_SUCCESS(status) && ok) {
137 status = U_ZERO_ERROR;
138 dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000;
139 }
140
141 ucal_close(ucal);
142 if (!U_SUCCESS(status) || !ok)
143 return tran;
144 tran.atMSecsSinceEpoch = tranMSecs;
145 tran.offsetFromUtc = utc + dst;
146 tran.standardTimeOffset = utc;
147 tran.daylightTimeOffset = dst;
148 // TODO No ICU API, use short name as abbreviation.
149 QTimeZone::TimeType timeType = dst == 0 ? QTimeZone::StandardTime : QTimeZone::DaylightTime;
150 using namespace QtTimeZoneLocale;
151 tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, timeType,
152 QTimeZone::ShortName, QLocale().name());
153 return tran;
154}
155#endif // U_ICU_VERSION_SHORT
156
157// Convert a uenum to a QList<QByteArray>
158static QList<QByteArray> uenumToIdList(UEnumeration *uenum)
159{
160 QList<QByteArray> list;
161 int32_t size = 0;
162 UErrorCode status = U_ZERO_ERROR;
163 // TODO Perhaps use uenum_unext instead?
164 QByteArray result = uenum_next(uenum, &size, &status);
165 while (U_SUCCESS(status) && !result.isEmpty()) {
166 list << result;
167 status = U_ZERO_ERROR;
168 result = uenum_next(uenum, &size, &status);
169 }
170 std::sort(list.begin(), list.end());
171 list.erase(std::unique(list.begin(), list.end()), list.end());
172 return list;
173}
174
175// Qt wrapper around ucal_getDSTSavings()
176static int ucalDaylightOffset(const QByteArray &id)
177{
178 UErrorCode status = U_ZERO_ERROR;
179 const QString utf16 = QString::fromLatin1(id);
180 const int32_t dstMSecs = ucal_getDSTSavings(
181 reinterpret_cast<const UChar *>(utf16.data()), &status);
182 return U_SUCCESS(status) ? dstMSecs / 1000 : 0;
183}
184
185// Create the system default time zone
186QIcuTimeZonePrivate::QIcuTimeZonePrivate()
187 : m_ucal(nullptr)
188{
189 // TODO No ICU C API to obtain system tz, assume default hasn't been changed
191}
192
193// Create a named time zone
194QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QByteArray &ianaId)
195 : m_ucal(nullptr)
196{
197 // ICU misleadingly maps invalid IDs to GMT.
198 if (isTimeZoneIdAvailable(ianaId))
199 init(ianaId);
200}
201
202QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other)
203 : QTimeZonePrivate(other), m_ucal(nullptr)
204{
205 // Clone the ucal so we don't close the shared object
206 UErrorCode status = U_ZERO_ERROR;
207 m_ucal = ucal_clone(other.m_ucal, &status);
208 if (!U_SUCCESS(status)) {
209 m_id.clear();
210 m_ucal = nullptr;
211 }
212}
213
214QIcuTimeZonePrivate::~QIcuTimeZonePrivate()
215{
216 ucal_close(m_ucal);
217}
218
219QIcuTimeZonePrivate *QIcuTimeZonePrivate::clone() const
220{
221 return new QIcuTimeZonePrivate(*this);
222}
223
224void QIcuTimeZonePrivate::init(const QByteArray &ianaId)
225{
226 m_id = ianaId;
227
228 const QString id = QString::fromUtf8(m_id);
229 UErrorCode status = U_ZERO_ERROR;
230 //TODO Use UCAL_GREGORIAN for now to match QLocale, change to UCAL_DEFAULT once full ICU support
231 m_ucal = ucal_open(reinterpret_cast<const UChar *>(id.data()), id.size(),
232 QLocale().name().toUtf8(), UCAL_GREGORIAN, &status);
233
234 if (!U_SUCCESS(status)) {
235 m_id.clear();
236 m_ucal = nullptr;
237 }
238}
239
240QString QIcuTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
241 QTimeZone::NameType nameType,
242 const QLocale &locale) const
243{
244 // Base class has handled OffsetName if we came via the other overload.
245 if (nameType == QTimeZone::OffsetName) {
246 int offset = standardTimeOffset(QDateTime::currentMSecsSinceEpoch());
247 // We can't use transitions reliably to find out right DST offset.
248 // Instead use DST offset API to try to get it, when needed:
249 if (timeType == QTimeZone::DaylightTime)
250 offset += ucalDaylightOffset(m_id);
251 // This is only valid for times since the most recent standard offset
252 // change; for earlier times, caller must use the other overload.
253
254 // Use our own formating for offset names (ICU C API doesn't support it
255 // and we may as well be self-consistent anyway).
256 return isoOffsetFormat(offset);
257 }
258 // Technically this may be suspect, if locale isn't QLocale(), since that's
259 // what we used when constructing m_ucal; does ICU cope with inconsistency ?
260 using namespace QtTimeZoneLocale;
261 return ucalTimeZoneDisplayName(m_ucal, timeType, nameType, locale.name());
262}
263
264int QIcuTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
265{
266 int stdOffset = 0;
267 int dstOffset = 0;
268 ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset);
269 return stdOffset + dstOffset;
270}
271
272int QIcuTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
273{
274 int stdOffset = 0;
275 int dstOffset = 0;
276 ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset);
277 return stdOffset;
278}
279
280int QIcuTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
281{
282 int stdOffset = 0;
283 int dstOffset = 0;
284 ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset);
285 return dstOffset;
286}
287
288bool QIcuTimeZonePrivate::hasDaylightTime() const
289{
290 if (ucalDaylightOffset(m_id) != 0)
291 return true;
292#if U_ICU_VERSION_MAJOR_NUM >= 50
293 for (qint64 when = minMSecs(); when != invalidMSecs(); ) {
294 auto data = nextTransition(when);
295 if (data.daylightTimeOffset && data.daylightTimeOffset != invalidSeconds())
296 return true;
297 when = data.atMSecsSinceEpoch;
298 }
299#endif
300 return false;
301}
302
303bool QIcuTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
304{
305 // Clone the ucal so we don't change the shared object
306 UErrorCode status = U_ZERO_ERROR;
307 UCalendar *ucal = ucal_clone(m_ucal, &status);
308 if (!U_SUCCESS(status))
309 return false;
310
311 // Set the date to find the offset for
312 status = U_ZERO_ERROR;
313 ucal_setMillis(ucal, atMSecsSinceEpoch, &status);
314
315 bool result = false;
316 if (U_SUCCESS(status)) {
317 status = U_ZERO_ERROR;
318 result = ucal_inDaylightTime(ucal, &status);
319 }
320
321 ucal_close(ucal);
322 return result;
323}
324
325QTimeZonePrivate::Data QIcuTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
326{
327 // Available in ICU C++ api, and draft C api in v50
329#if U_ICU_VERSION_MAJOR_NUM >= 50
330 data = ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE,
331 forMSecsSinceEpoch);
332 if (data.atMSecsSinceEpoch == invalidMSecs()) // before first transition
333#endif
334 {
335 ucalOffsetsAtTime(m_ucal, forMSecsSinceEpoch, &data.standardTimeOffset,
336 &data.daylightTimeOffset);
337 data.offsetFromUtc = data.standardTimeOffset + data.daylightTimeOffset;
338 data.abbreviation = abbreviation(forMSecsSinceEpoch);
339 }
340 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
341 return data;
342}
343
344bool QIcuTimeZonePrivate::hasTransitions() const
345{
346 // Available in ICU C++ api, and draft C api in v50
347#if U_ICU_VERSION_MAJOR_NUM >= 50
348 return true;
349#else
350 return false;
351#endif
352}
353
354QTimeZonePrivate::Data QIcuTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
355{
356 // Available in ICU C++ api, and draft C api in v50
357#if U_ICU_VERSION_MAJOR_NUM >= 50
358 return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_NEXT, afterMSecsSinceEpoch);
359#else
360 Q_UNUSED(afterMSecsSinceEpoch);
361 return {};
362#endif
363}
364
365QTimeZonePrivate::Data QIcuTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
366{
367 // Available in ICU C++ api, and draft C api in v50
368#if U_ICU_VERSION_MAJOR_NUM >= 50
369 return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS, beforeMSecsSinceEpoch);
370#else
371 Q_UNUSED(beforeMSecsSinceEpoch);
372 return {};
373#endif
374}
375
376QByteArray QIcuTimeZonePrivate::systemTimeZoneId() const
377{
378 // No ICU C API to obtain system tz
379 // TODO Assume default hasn't been changed and is the latests system
380 return ucalDefaultTimeZoneId();
381}
382
383bool QIcuTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
384{
385 const QString ianaStr = QString::fromUtf8(ianaId);
386 const UChar *const name = reinterpret_cast<const UChar *>(ianaStr.constData());
387 // We are not interested in the value, but we have to pass something.
388 // No known IANA zone name is (up to 2023) longer than 30 characters.
389 constexpr size_t size = 64;
390 UChar buffer[size];
391
392 // TODO: convert to ucal_getIanaTimeZoneID(), new draft in ICU 74, once we
393 // can rely on its availability, assuming it works the same once not draft.
394 UErrorCode status = U_ZERO_ERROR;
395 UBool isSys = false;
396 // Returns the length of the IANA zone name (but we don't care):
397 ucal_getCanonicalTimeZoneID(name, ianaStr.size(), buffer, size, &isSys, &status);
398 // We're only interested if the result is a "system" (i.e. IANA) ID:
399 return isSys;
400}
401
402QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds() const
403{
404 UErrorCode status = U_ZERO_ERROR;
405 UEnumeration *uenum = ucal_openTimeZones(&status);
406 QList<QByteArray> result;
407 if (U_SUCCESS(status))
408 result = uenumToIdList(uenum);
409 uenum_close(uenum);
410 return result;
411}
412
413QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory) const
414{
415 const QLatin1StringView regionCode = QLocalePrivate::territoryToCode(territory);
416 const QByteArray regionCodeUtf8 = QString(regionCode).toUtf8();
417 UErrorCode status = U_ZERO_ERROR;
418 UEnumeration *uenum = ucal_openCountryTimeZones(regionCodeUtf8.data(), &status);
419 QList<QByteArray> result;
420 if (U_SUCCESS(status))
421 result = uenumToIdList(uenum);
422 uenum_close(uenum);
423 return result;
424}
425
426QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const
427{
428// TODO Available directly in C++ api but not C api, from 4.8 onwards new filter method works
429#if U_ICU_VERSION_MAJOR_NUM >= 49 || (U_ICU_VERSION_MAJOR_NUM == 4 && U_ICU_VERSION_MINOR_NUM == 8)
430 UErrorCode status = U_ZERO_ERROR;
431 UEnumeration *uenum = ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr,
432 &offsetFromUtc, &status);
433 QList<QByteArray> result;
434 if (U_SUCCESS(status))
435 result = uenumToIdList(uenum);
436 uenum_close(uenum);
437 return result;
438#else
439 return QTimeZonePrivate::availableTimeZoneIds(offsetFromUtc);
440#endif
441}
442
\inmodule QtCore
Definition qbytearray.h:57
static qint64 currentMSecsSinceEpoch() noexcept
iterator erase(const_iterator begin, const_iterator end)
Definition qlist.h:889
iterator end()
Definition qlist.h:626
iterator begin()
Definition qlist.h:625
static QLatin1StringView territoryToCode(QLocale::Territory territory)
Definition qlocale.cpp:239
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
QByteArray toUtf8() const &
Definition qstring.h:634
virtual QList< QByteArray > availableTimeZoneIds() const
Q_QML_EXPORT QV4::ReturnedValue locale(QV4::ExecutionEngine *engine, const QString &localeName)
Provides locale specific properties and formatted data.
Combined button and popup list for selecting options.
int toUtf8(char16_t u, OutputPtr &dst, InputPtr &src, InputPtr end)
constexpr Initialization Uninitialized
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum type
GLenum GLenum dst
GLenum GLuint GLintptr offset
GLuint name
GLuint64EXT * result
[6]
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
static bool ucalOffsetsAtTime(UCalendar *m_ucal, qint64 atMSecsSinceEpoch, int *utcOffset, int *dstOffset)
static int ucalDaylightOffset(const QByteArray &id)
static QList< QByteArray > uenumToIdList(UEnumeration *uenum)
static QT_BEGIN_NAMESPACE QByteArray ucalDefaultTimeZoneId()
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
QList< int > list
[14]
QObject::connect nullptr
QSharedPointer< T > other(t)
[5]