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_mac.mm
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2013 John Layt <jlayt@kde.org>
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
8#include "private/qcore_mac_p.h"
9#include "qstringlist.h"
10
11#include <Foundation/NSTimeZone.h>
12
13#include <qdebug.h>
14
15#include <algorithm>
16
18
19/*
20 Private
21
22 Darwin system implementation
23 https://developer.apple.com/documentation/foundation/nstimezone
24*/
25
26// Create the system default time zone
27QMacTimeZonePrivate::QMacTimeZonePrivate()
28{
29 // Reset the cached system tz then instantiate it:
30 [NSTimeZone resetSystemTimeZone];
31 m_nstz = [NSTimeZone.systemTimeZone retain];
32 Q_ASSERT(m_nstz);
33 m_id = QString::fromNSString(m_nstz.name).toUtf8();
34}
35
36// Create a named time zone
37QMacTimeZonePrivate::QMacTimeZonePrivate(const QByteArray &ianaId)
38 : m_nstz(nil)
39{
40 init(ianaId);
41}
42
43QMacTimeZonePrivate::QMacTimeZonePrivate(const QMacTimeZonePrivate &other)
44 : QTimeZonePrivate(other), m_nstz([other.m_nstz copy])
45{
46}
47
48QMacTimeZonePrivate::~QMacTimeZonePrivate()
49{
50 [m_nstz release];
51}
52
53QMacTimeZonePrivate *QMacTimeZonePrivate::clone() const
54{
55 return new QMacTimeZonePrivate(*this);
56}
57
58void QMacTimeZonePrivate::init(const QByteArray &ianaId)
59{
60 m_nstz = [[NSTimeZone timeZoneWithName:QString::fromUtf8(ianaId).toNSString()] retain];
61 if (m_nstz) {
62 m_id = ianaId;
63 } else {
64 // macOS has been seen returning a systemTimeZone which reports its name
65 // as Asia/Kolkata, which doesn't appear in knownTimeZoneNames (which
66 // calls the zone Asia/Calcutta). So explicitly check for the name
67 // systemTimeZoneId() returns, and use systemTimeZone if we get it:
68 m_nstz = [NSTimeZone.systemTimeZone retain];
69 Q_ASSERT(m_nstz);
70 if (QString::fromNSString(m_nstz.name).toUtf8() == ianaId)
71 m_id = ianaId;
72 }
73}
74
75QString QMacTimeZonePrivate::comment() const
76{
77 return QString::fromNSString(m_nstz.description);
78}
79
80QString QMacTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
81 QTimeZone::NameType nameType,
82 const QLocale &locale) const
83{
84 // TODO Mac doesn't support OffsetName yet so use standard offset name
85 if (nameType == QTimeZone::OffsetName) {
87 // TODO Cheat for now, assume if has dst the offset if 1 hour
88 if (timeType == QTimeZone::DaylightTime && hasDaylightTime())
89 return isoOffsetFormat(nowData.standardTimeOffset + 3600);
90 else
91 return isoOffsetFormat(nowData.standardTimeOffset);
92 }
93
94 NSTimeZoneNameStyle style = NSTimeZoneNameStyleStandard;
95
96 switch (nameType) {
97 case QTimeZone::ShortName :
98 if (timeType == QTimeZone::DaylightTime)
99 style = NSTimeZoneNameStyleShortDaylightSaving;
100 else if (timeType == QTimeZone::GenericTime)
101 style = NSTimeZoneNameStyleShortGeneric;
102 else
103 style = NSTimeZoneNameStyleShortStandard;
104 break;
105 case QTimeZone::DefaultName :
106 case QTimeZone::LongName :
107 if (timeType == QTimeZone::DaylightTime)
108 style = NSTimeZoneNameStyleDaylightSaving;
109 else if (timeType == QTimeZone::GenericTime)
110 style = NSTimeZoneNameStyleGeneric;
111 else
112 style = NSTimeZoneNameStyleStandard;
113 break;
114 case QTimeZone::OffsetName :
115 Q_UNREACHABLE();
116 break;
117 }
118
119 NSString *macLocaleCode = locale.name().toNSString();
120 NSLocale *macLocale = [[NSLocale alloc] initWithLocaleIdentifier:macLocaleCode];
121 const QString result = QString::fromNSString([m_nstz localizedName:style locale:macLocale]);
122 [macLocale release];
123 return result;
124}
125
126QString QMacTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
127{
128 const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0;
129 return QString::fromNSString([m_nstz abbreviationForDate:[NSDate dateWithTimeIntervalSince1970:seconds]]);
130}
131
132int QMacTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
133{
134 const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0;
135 return [m_nstz secondsFromGMTForDate:[NSDate dateWithTimeIntervalSince1970:seconds]];
136}
137
138int QMacTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
139{
140 return offsetFromUtc(atMSecsSinceEpoch) - daylightTimeOffset(atMSecsSinceEpoch);
141}
142
143int QMacTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
144{
145 const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0;
146 return [m_nstz daylightSavingTimeOffsetForDate:[NSDate dateWithTimeIntervalSince1970:seconds]];
147}
148
149bool QMacTimeZonePrivate::hasDaylightTime() const
150{
151 // TODO Scan transitions for one after which isDaylightSavingTimeForDate is true.
152 return hasTransitions();
153}
154
155bool QMacTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
156{
157 const NSTimeInterval seconds = atMSecsSinceEpoch / 1000.0;
158 return [m_nstz isDaylightSavingTimeForDate:[NSDate dateWithTimeIntervalSince1970:seconds]];
159}
160
161QTimeZonePrivate::Data QMacTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
162{
163 const NSTimeInterval seconds = forMSecsSinceEpoch / 1000.0;
164 NSDate *date = [NSDate dateWithTimeIntervalSince1970:seconds];
165 Data data;
166 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
167 data.offsetFromUtc = [m_nstz secondsFromGMTForDate:date];
168 data.daylightTimeOffset = [m_nstz daylightSavingTimeOffsetForDate:date];
169 data.standardTimeOffset = data.offsetFromUtc - data.daylightTimeOffset;
170 data.abbreviation = QString::fromNSString([m_nstz abbreviationForDate:date]);
171 return data;
172}
173
174bool QMacTimeZonePrivate::hasTransitions() const
175{
176 // TODO No direct Mac API, so return if has next after 1970, i.e. since start of tz
177 // TODO Not sure what is returned in event of no transitions, assume will be before requested date
178 NSDate *epoch = [NSDate dateWithTimeIntervalSince1970:0];
179 const NSDate *date = [m_nstz nextDaylightSavingTimeTransitionAfterDate:epoch];
180 const bool result = (date.timeIntervalSince1970 > epoch.timeIntervalSince1970);
181 return result;
182}
183
184QTimeZonePrivate::Data QMacTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
185{
187 const NSTimeInterval seconds = afterMSecsSinceEpoch / 1000.0;
188 NSDate *nextDate = [NSDate dateWithTimeIntervalSince1970:seconds];
189 nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
190 const NSTimeInterval nextSecs = nextDate.timeIntervalSince1970;
191 if (nextDate == nil || nextSecs <= seconds) {
192 [nextDate release];
193 return {};
194 }
195 tran.atMSecsSinceEpoch = nextSecs * 1000;
196 tran.offsetFromUtc = [m_nstz secondsFromGMTForDate:nextDate];
197 tran.daylightTimeOffset = [m_nstz daylightSavingTimeOffsetForDate:nextDate];
198 tran.standardTimeOffset = tran.offsetFromUtc - tran.daylightTimeOffset;
199 tran.abbreviation = QString::fromNSString([m_nstz abbreviationForDate:nextDate]);
200 return tran;
201}
202
203QTimeZonePrivate::Data QMacTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
204{
205 // The native API only lets us search forward, so we need to find an early-enough start:
206 constexpr NSTimeInterval lowerBound = std::numeric_limits<NSTimeInterval>::lowest();
207 const qint64 endSecs = beforeMSecsSinceEpoch / 1000;
208 const int year = 366 * 24 * 3600; // a (long) year, in seconds
209 NSTimeInterval prevSecs = endSecs; // sentinel for later check
210 NSTimeInterval nextSecs = prevSecs - year;
211 NSTimeInterval tranSecs = lowerBound; // time at a transition; may be > endSecs
212
213 NSDate *nextDate = [NSDate dateWithTimeIntervalSince1970:nextSecs];
214 nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
215 if (nextDate != nil
216 && (tranSecs = nextDate.timeIntervalSince1970) < endSecs) {
217 // There's a transition within the last year before endSecs:
218 nextSecs = tranSecs;
219 } else {
220 // Need to start our search earlier:
221 nextDate = [NSDate dateWithTimeIntervalSince1970:lowerBound];
222 nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
223 if (nextDate != nil) {
224 NSTimeInterval lateSecs = nextSecs;
225 nextSecs = nextDate.timeIntervalSince1970;
226 Q_ASSERT(nextSecs <= endSecs - year || nextSecs == tranSecs);
227 /*
228 We're looking at the first ever transition for our zone, at
229 nextSecs (and our zone *does* have at least one transition). If
230 it's later than endSecs - year, then we must have found it on the
231 initial check and therefore set tranSecs to the same transition
232 time (which, we can infer here, is >= endSecs). In this case, we
233 won't enter the binary-chop loop, below.
234
235 In the loop, nextSecs < lateSecs < endSecs: we have a transition
236 at nextSecs and there is no transition between lateSecs and
237 endSecs. The loop narrows the interval between nextSecs and
238 lateSecs by looking for a transition after their mid-point; if it
239 finds one < endSecs, nextSecs moves to this transition; otherwise,
240 lateSecs moves to the mid-point. This soon enough narrows the gap
241 to within a year, after which walking forward one transition at a
242 time (the "Wind through" loop, below) is good enough.
243 */
244
245 // Binary chop to within a year of last transition before endSecs:
246 while (nextSecs + year < lateSecs) {
247 // Careful about overflow, not fussy about rounding errors:
248 NSTimeInterval middle = nextSecs / 2 + lateSecs / 2;
249 NSDate *split = [NSDate dateWithTimeIntervalSince1970:middle];
250 split = [m_nstz nextDaylightSavingTimeTransitionAfterDate:split];
251 if (split != nil && (tranSecs = split.timeIntervalSince1970) < endSecs) {
252 nextDate = split;
253 nextSecs = tranSecs;
254 } else {
255 lateSecs = middle;
256 }
257 }
258 Q_ASSERT(nextDate != nil);
259 // ... and nextSecs < endSecs unless first transition ever was >= endSecs.
260 } // else: we have no data - prevSecs is still endSecs, nextDate is still nil
261 }
262 // Either nextDate is nil or nextSecs is at its transition.
263
264 // Wind through remaining transitions (spanning at most a year), one at a time:
265 while (nextDate != nil && nextSecs < endSecs) {
266 prevSecs = nextSecs;
267 nextDate = [m_nstz nextDaylightSavingTimeTransitionAfterDate:nextDate];
268 nextSecs = nextDate.timeIntervalSince1970;
269 if (nextSecs <= prevSecs) // presumably no later data available
270 break;
271 }
272 if (prevSecs < endSecs) // i.e. we did make it into that while loop
273 return data(qint64(prevSecs * 1e3));
274
275 // No transition data; or first transition later than requested time.
276 return {};
277}
278
279QByteArray QMacTimeZonePrivate::systemTimeZoneId() const
280{
281 // Reset the cached system tz then return the name
282 [NSTimeZone resetSystemTimeZone];
283 Q_ASSERT(NSTimeZone.systemTimeZone);
284 return QString::fromNSString(NSTimeZone.systemTimeZone.name).toUtf8();
285}
286
287bool QMacTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray& ianaId) const
288{
290 return [NSTimeZone timeZoneWithName:QString::fromUtf8(ianaId).toNSString()] != nil;
291}
292
293QList<QByteArray> QMacTimeZonePrivate::availableTimeZoneIds() const
294{
295 NSEnumerator *enumerator = NSTimeZone.knownTimeZoneNames.objectEnumerator;
296 QByteArray tzid = QString::fromNSString(enumerator.nextObject).toUtf8();
297
298 QList<QByteArray> list;
299 while (!tzid.isEmpty()) {
300 list << tzid;
301 tzid = QString::fromNSString(enumerator.nextObject).toUtf8();
302 }
303
304 std::sort(list.begin(), list.end());
305 list.erase(std::unique(list.begin(), list.end()), list.end());
306
307 return list;
308}
309
310NSTimeZone *QMacTimeZonePrivate::nsTimeZone() const
311{
312 return m_nstz;
313}
314
\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
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
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
QDate date
[1]
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.
static jboolean copy(JNIEnv *, jobject)
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static void split(QT_FT_Vector *b)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
long long qint64
Definition qtypes.h:60
QList< int > list
[14]
sem release()
QSharedPointer< T > other(t)
[5]