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