summaryrefslogtreecommitdiffstats
path: root/src/corelib/time/qlocaltime.cpp
blob: 609a5a4b37bfb830871eedb24476f788027b4b8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
// Copyright (C) 2022 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

#include "qlocaltime_p.h"
#include "qplatformdefs.h"

#include "private/qcalendarmath_p.h"
#if QT_CONFIG(datetimeparser)
#include "private/qdatetimeparser_p.h"
#endif
#include "private/qgregoriancalendar_p.h"
#include "private/qnumeric_p.h"
#include "private/qtenvironmentvariables_p.h"
#if QT_CONFIG(timezone)
#include "private/qtimezoneprivate_p.h"
#endif

#include <time.h>
#ifdef Q_OS_WIN
#  include <qt_windows.h>
#endif

#ifdef __GLIBC__ // Extends struct tm with some extra fields:
#define HAVE_TM_GMTOFF // tm_gmtoff is the UTC offset.
#define HAVE_TM_ZONE // tm_zone is the zone abbreviation.
#endif

QT_BEGIN_NAMESPACE

using namespace QtPrivate::DateTimeConstants;
namespace {
/*
    Qt represents n BCE as -n, whereas struct tm's tm_year field represents a
    year by the number of years after (negative for before) 1900, so that 1+m
    BCE is -1900 -m; so treating 1 BCE as 0 CE. We thus shift by different
    offsets depending on whether the year is BCE or CE.
*/
constexpr int tmYearFromQYear(int year) { return year - (year < 0 ? 1899 : 1900); }
constexpr int qYearFromTmYear(int year) { return year + (year < -1899 ? 1899 : 1900); }

constexpr inline qint64 tmSecsWithinDay(const struct tm &when)
{
    return (when.tm_hour * MINS_PER_HOUR + when.tm_min) * SECS_PER_MIN + when.tm_sec;
}

/* Call mktime() and make sense of the result.

   This packages the call to mktime() with the needed determination of whether
   that succeeded and whether the call has materially perturbed, including
   normalizing, the struct tm it was passed (as opposed to merely filling in
   details).
*/
class MkTimeResult
{
    // mktime()'s return on error; or last second of 1969 UTC:
    static constexpr time_t maybeError = -1;
    inline bool meansEnd1969();
    bool changed(const struct tm &prior) const;

public:
    struct tm local = {}; // Describes the local time in familiar form.
    time_t utcSecs = maybeError; // Seconds since UTC epoch.
    bool good = false; // Ignore the rest unless this is true.
    bool adjusted = true; // Is local at odds with prior ?
    MkTimeResult() { local.tm_isdst = -1; }

    // Note: the calls to qMkTime() and meansEnd1969() potentially modify local.
    explicit MkTimeResult(const struct tm &prior)
    : local(prior), utcSecs(qMkTime(&local)),
      good(utcSecs != maybeError || meansEnd1969()),
      adjusted(changed(prior))
    {}
};

/* If mktime() returns -1, is it really an error ?

   It might return -1 because we're looking at the last second of 1969 and
   mktime does support times before 1970 (POSIX says "If the year is <1970 or
   the value is negative, the relationship is undefined" and MS rejects the
   value, consistent with that; so we don't call mktime() on MS in this case and
   can't get -1 unless it's a real error). However, on UNIX, that's -1 UTC time
   and all we know, aside from mktime's return, is the local time. (We could
   check errno, but we call mktime from within a qt_scoped_lock(QBasicMutex),
   whose unlocking and destruction of the locker might frob errno.)

   We can assume time-zone offsets are less than a day, so this can only arise
   if the struct tm describes either the last day of 1969 or the first day of
   1970. When we do know the offset (a glibc extension supplies it as a member
   of struct tm), we can determine whether we're on the last second of the day,
   refining that check. That makes for a cheap pre-test; if it holds, we can ask
   mktime() about the preceding second; if it gives us -2, then the -1 we
   originally saw is not (or at least didn't need to be) an error. We can then
   synthesize a corrected value for local using the -2 result.
*/
inline bool MkTimeResult::meansEnd1969()
{
#ifdef Q_OS_WIN
    return false;
#else
    if (local.tm_year < 69 || local.tm_year > 70
#  ifdef HAVE_TM_GMTOFF
        // Africa/Monrovia had offset 00:44:30 at the epoch, so (although all
        // other zones' offsets were round multiples of five minutes) we need
        // the offset to determine whether the time might match:
        || (tmSecsWithinDay(local) - local.tm_gmtoff + 1) % SECS_PER_DAY
#  endif
        || (local.tm_year == 69 // ... and less than a day:
            ? local.tm_mon < 11 || local.tm_mday < 31
            : local.tm_mon > 0 || local.tm_mday > 1)) {
        return false;
    }
    struct tm copy = local;
    copy.tm_sec--; // Preceding second should get -2, not -1
    if (qMkTime(&copy) != -2)
        return false;
    // The original call to qMkTime() may have returned -1 as failure, not
    // updating local, even though it could have; so fake it here. Assumes there
    // was no transition in the last minute of the day !
    local = copy;
    local.tm_sec++; // Advance back to the intended second
    return true;
#endif
}

bool MkTimeResult::changed(const struct tm &prior) const
{
    // If mktime() has been passed a copy of prior and local is its value on
    // return, this checks whether mktime() has made a material change
    // (including normalization) to the value, as opposed to merely filling in
    // the fields that it's specified to fill in. It returns true if there has
    // been any material change.
    return !(prior.tm_year == local.tm_year && prior.tm_mon == local.tm_mon
             && prior.tm_mday == local.tm_mday && prior.tm_hour == local.tm_hour
             && prior.tm_min == local.tm_min && prior.tm_sec == local.tm_sec
             && (prior.tm_isdst == -1
                 ? local.tm_isdst >= 0 : prior.tm_isdst == local.tm_isdst));
}

struct tm timeToTm(qint64 localDay, int secs)
{
    Q_ASSERT(0 <= secs && secs < SECS_PER_DAY);
    const auto ymd = QGregorianCalendar::partsFromJulian(JULIAN_DAY_FOR_EPOCH + localDay);
    struct tm local = {};
    local.tm_year = tmYearFromQYear(ymd.year);
    local.tm_mon = ymd.month - 1;
    local.tm_mday = ymd.day;
    local.tm_hour = secs / 3600;
    local.tm_min = (secs % 3600) / 60;
    local.tm_sec = (secs % 60);
    local.tm_isdst = -1;
    return local;
}

// Transitions account for a small fraction of 1% of the time.
// So mark functions only used in handling them as cold.
Q_DECL_COLD_FUNCTION
struct tm matchYearMonth(struct tm when, const struct tm &base)
{
    // Adjust *when to be a denormal representation of the same point in time
    // but with tm_year and tm_mon the same as base. In practice this will
    // represent an adjacent month, so don't worry too much about optimising for
    // any other case; we almost certainly run zero or one iteration of one of
    // the year loops then zero or one iteration of one of the month loops.
    while (when.tm_year > base.tm_year) {
        --when.tm_year;
        when.tm_mon += 12;
    }
    while (when.tm_year < base.tm_year) {
        ++when.tm_year;
        when.tm_mon -= 12;
    }
    Q_ASSERT(when.tm_year == base.tm_year);
    while (when.tm_mon > base.tm_mon) {
        const auto yearMon = QRoundingDown::qDivMod<12>(when.tm_mon);
        int year = yearMon.quotient;
        // We want the month before's Qt month number, which is the tm_mon mod 12:
        int month = yearMon.remainder;
        if (month == 0) {
            --year;
            month = 12;
        }
        year += when.tm_year;
        when.tm_mday += QGregorianCalendar::monthLength(month, qYearFromTmYear(year));
        --when.tm_mon;
    }
    while (when.tm_mon < base.tm_mon) {
        const auto yearMon = QRoundingDown::qDivMod<12>(when.tm_mon);
        // Qt month number is offset from tm_mon by one:
        when.tm_mday -= QGregorianCalendar::monthLength(
            yearMon.remainder + 1, qYearFromTmYear(yearMon.quotient + when.tm_year));
        ++when.tm_mon;
    }
    Q_ASSERT(when.tm_mon == base.tm_mon);
    return when;
}

Q_DECL_COLD_FUNCTION
struct tm adjacentDay(struct tm when, int dayStep)
{
    // Before we adjust it, when is a return from timeToTm(), so in normal form.
    Q_ASSERT(dayStep * dayStep == 1);
    when.tm_mday += dayStep;
    // That may have bumped us across a month boundary or even a year one.
    // So now we normalize it.

    if (dayStep < 0) {
        if (when.tm_mday <= 0) {
            // Month before's day-count; but tm_mon's value is one less than Qt's
            // month numbering so, before we decrement it, it has the value we need,
            // unless it's 0.
            int daysInMonth = when.tm_mon
                ? QGregorianCalendar::monthLength(when.tm_mon, qYearFromTmYear(when.tm_year))
                : QGregorianCalendar::monthLength(12, qYearFromTmYear(when.tm_year - 1));
            when.tm_mday += daysInMonth;
            if (--when.tm_mon < 0) {
                --when.tm_year;
                when.tm_mon = 11;
            }
            Q_ASSERT(when.tm_mday >= 1);
        }
    } else if (when.tm_mday > 28) {
        // We have to wind through months one at a time, since their lengths vary.
        int daysInMonth = QGregorianCalendar::monthLength(
            when.tm_mon + 1, qYearFromTmYear(when.tm_year));
        if (when.tm_mday > daysInMonth) {
            when.tm_mday -= daysInMonth;
            if (++when.tm_mon > 11) {
                ++when.tm_year;
                when.tm_mon = 0;
            }
            Q_ASSERT(when.tm_mday <= QGregorianCalendar::monthLength(
                         when.tm_mon + 1, qYearFromTmYear(when.tm_year)));
        }
    }
    return when;
}

Q_DECL_COLD_FUNCTION
qint64 secondsBetween(const struct tm &start, const struct tm &stop)
{
    // Nominal difference between start and stop, in seconds (negative if start
    // is after stop); may differ from actual UTC difference if there's a
    // transition between them.
    struct tm from = matchYearMonth(start, stop);
    qint64 diff = stop.tm_mday - from.tm_mday; // in days
    diff = diff * 24 + stop.tm_hour - from.tm_hour; // in hours
    diff = diff * 60 + stop.tm_min - from.tm_min; // in minutes
    return diff * 60 + stop.tm_sec - from.tm_sec; // in seconds
}

Q_DECL_COLD_FUNCTION
MkTimeResult hopAcrossGap(const MkTimeResult &outside, const struct tm &base)
{
    // base fell in a gap; outside is one resolution
    // This returns the other resolution, if possible.
    const qint64 shift = secondsBetween(outside.local, base);
    struct tm across;
    // Shift is the nominal time adjustment between outside and base; now obtain
    // the actual time that far from outside:
    if (qLocalTime(outside.utcSecs + shift, &across)) {
        const qint64 wider = secondsBetween(outside.local, across);
        // That should be bigger than shift (typically by a factor of two), in
        // the same direction:
        if (shift > 0 ? wider > shift : wider < shift) {
            MkTimeResult result(across);
            if (result.good && !result.adjusted)
                return result;
        }
    }
    // This can surely only arise if the other resolution lies outside the
    // time_t-range supported by the system functions.
    return {};
}

Q_DECL_COLD_FUNCTION
MkTimeResult resolveRejected(struct tm base, MkTimeResult result,
                             QDateTimePrivate::TransitionOptions resolve)
{
    // May result from a time outside the supported range of system time_t
    // functions, or from a gap (on a platform where mktime() rejects them).
    // QDateTime filters on times well outside the supported range, but may
    // pass values only slightly outside the range.

    // The easy case - no need to find a resolution anyway:
    if (!resolve.testAnyFlags(QDateTimePrivate::GapMask))
        return {};

    constexpr time_t twoDaysInSeconds = 2 * 24 * 60 * 60;
    // Bracket base, one day each side (in case the zone skipped a whole day):
    MkTimeResult early(adjacentDay(base, -1));
    MkTimeResult later(adjacentDay(base, +1));
    if (!early.good || !later.good) // Assume out of range, rather than gap.
        return {};

    // OK, looks like a gap.
    Q_ASSERT(twoDaysInSeconds + early.utcSecs > later.utcSecs);
    result.adjusted = true;

    // Extrapolate backwards from later if this option is set:
    QDateTimePrivate::TransitionOption beforeLater = QDateTimePrivate::GapUseBefore;
    if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
        // Reverse DST has DST before a gap and not after:
        if (early.local.tm_isdst == 1 && !later.local.tm_isdst)
            beforeLater = QDateTimePrivate::GapUseAfter;
    }
    if (resolve.testFlag(beforeLater)) // Result will be before the gap:
        result.utcSecs = later.utcSecs - secondsBetween(base, later.local);
    else // Result will be after the gap:
        result.utcSecs = early.utcSecs + secondsBetween(early.local, base);

    if (!qLocalTime(result.utcSecs, &result.local)) // Abandon hope.
        return {};

    return result;
}

Q_DECL_COLD_FUNCTION
bool preferAlternative(QDateTimePrivate::TransitionOptions resolve,
                       // is_dst flags of incumbent and an alternative:
                       int gotDst, int altDst,
                       // True precisely if alternative selects a later UTC time:
                       bool altIsLater,
                       // True for a gap, false for a fold:
                       bool inGap)
{
    // If resolve has this option set, prefer the later candidate, else the earlier:
    QDateTimePrivate::TransitionOption preferLater = inGap ? QDateTimePrivate::GapUseAfter
                                                           : QDateTimePrivate::FoldUseAfter;
    if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)) {
        // gotDst and altDst are {-1: unknown, 0: standard, 1: daylight-saving}
        // So gotDst ^ altDst is 1 precisely if exactly one candidate thinks it's DST.
        if ((altDst ^ gotDst) == 1) {
            // In this case, we can tell whether we have reversed DST: that's a
            // gap with DST before it or a fold with DST after it.
#if 1
            const bool isReversed = (altDst == 1) != (altIsLater == inGap);
#else // Pedagogic version of the same thing:
            bool isReversed;
            if (altIsLater == inGap) // alt is after a gap or before a fold, so summer-time
                isReversed = altDst != 1; // flip if summer-time isn't DST
            else // alt is before a gap or after a fold, so winter-time
                isReversed = altDst == 1; // flip if winter-time is DST
#endif
            if (isReversed) {
                preferLater = inGap ? QDateTimePrivate::GapUseBefore
                                    : QDateTimePrivate::FoldUseBefore;
            }
        } // Otherwise, we can't tell, so assume not.
    }
    return resolve.testFlag(preferLater) == altIsLater;
}

/*
    Determine UTC time and offset, if possible, at a given local time.

    The local time is specified as a number of seconds since the epoch (so, in
    effect, a time_t, albeit delivered as qint64). If the specified local time
    falls in a transition, resolve determines what to do.

    If the specified local time is outside what the system time_t APIs will
    handle, this fails.
*/
MkTimeResult resolveLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve)
{
    const auto localDaySecs = QRoundingDown::qDivMod<SECS_PER_DAY>(local);
    struct tm base = timeToTm(localDaySecs.quotient, localDaySecs.remainder);

    // Get provisional result (correct > 99.9 % of the time):
    MkTimeResult result(base);

    // Our callers (mostly) deal with questions of being within the range that
    // system time_t functions can handle, and timeToTm() gave us data in
    // normalized form, so the only excuse for !good or a change to the HH:mm:ss
    // fields (aside from being at the boundary of time_t's supported range) is
    // that we hit a gap, although we have to handle these cases differently:
    if (!result.good) {
        // Rejected. The tricky case: maybe mktime() doesn't resolve gaps.
        return resolveRejected(base, result, resolve);
    } else if (result.local.tm_isdst < 0) {
        // Apparently success without knowledge of whether this is DST or not.
        // Should not happen, but that means our usual understanding of what the
        // system is up to has gone out the window. So just let it be.
    } else if (result.adjusted) {
        // Shunted out of a gap.
        if (!resolve.testAnyFlags(QDateTimePrivate::GapMask)) {
            result = {};
            return result;
        }

        // Try to obtain a matching point on the other side of the gap:
        const MkTimeResult flipped = hopAcrossGap(result, base);
        // Even if that failed, result may be the correct resolution

        if (preferAlternative(resolve, result.local.tm_isdst, flipped.local.tm_isdst,
                              flipped.utcSecs > result.utcSecs, true)) {
            // If hopAcrossGap() failed and we do need its answer, give up.
            if (!flipped.good || flipped.adjusted)
                return {};

            // As resolution of local, flipped involves adjustment (across gap):
            result = flipped;
            result.adjusted = true;
        }
    } else if (resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
               // In fold, DST counts as before and standard as after -
               // we may not need to check whether we're in a transition:
               && resolve.testFlag(result.local.tm_isdst ? QDateTimePrivate::FoldUseBefore
                                                         : QDateTimePrivate::FoldUseAfter)) {
        // We prefer DST or standard and got what we wanted, so we're good.
        // As below, but we don't need to check, because we're on the side of
        // the transition that it would select as valid, if we were near one.
        // NB: this branch is routinely exercised, when QDT::Data::isShort()
        // obliges us to rediscover an offsetFromUtc that ShortData has no space
        // to store, as it does remember the DST status we got before.
    } else {
        // What we gave was valid. However, it might have been in a fall-back.
        // If so, the same input but with tm_isdst flipped should also be valid.
        struct tm copy = base;
        copy.tm_isdst = !result.local.tm_isdst;
        const MkTimeResult flipped(copy);
        if (flipped.good && !flipped.adjusted) {
            // We're in a fall-back
            if (!resolve.testAnyFlags(QDateTimePrivate::FoldMask)) {
                result = {};
                return result;
            }

            // Work out which repeat to use:
            if (preferAlternative(resolve, result.local.tm_isdst, flipped.local.tm_isdst,
                                  flipped.utcSecs > result.utcSecs, false)) {
                result = flipped;
            }
        } // else: not in a transition, nothing to worry about.
    }
    return result;
}

inline std::optional<qint64> tmToJd(const struct tm &date)
{
    return QGregorianCalendar::julianFromParts(qYearFromTmYear(date.tm_year),
                                               date.tm_mon + 1, date.tm_mday);
}

#define IC(N) std::integral_constant<qint64, N>()

// True if combining day and seconds overflows qint64; otherwise, sets *epochSeconds
inline bool daysAndSecondsOverflow(qint64 julianDay, qint64 daySeconds, qint64 *epochSeconds)
{
    return qMulOverflow(julianDay - JULIAN_DAY_FOR_EPOCH, IC(SECS_PER_DAY), epochSeconds)
        || qAddOverflow(*epochSeconds, daySeconds, epochSeconds);
}

// True if combining seconds and millis overflows; otherwise sets *epochMillis
inline bool secondsAndMillisOverflow(qint64 epochSeconds, qint64 millis, qint64 *epochMillis)
{
    return qMulOverflow(epochSeconds, IC(MSECS_PER_SEC), epochMillis)
        || qAddOverflow(*epochMillis, millis, epochMillis);
}

#undef IC

} // namespace

namespace QLocalTime {

#ifndef QT_BOOTSTRAPPED
// Even if local time is currently in DST, this returns the standard time offset
// (in seconds) nominally in effect at present:
int getCurrentStandardUtcOffset()
{
#ifdef Q_OS_WIN
    TIME_ZONE_INFORMATION tzInfo;
    if (GetTimeZoneInformation(&tzInfo) != TIME_ZONE_ID_INVALID) {
        int bias = tzInfo.Bias; // In minutes.
        // StandardBias is usually zero, but include it if given:
        if (tzInfo.StandardDate.wMonth) // Zero month means ignore StandardBias.
            bias += tzInfo.StandardBias;
        // MS's bias is +ve in the USA, so minutes *behind* UTC - we want seconds *ahead*:
        return -bias * SECS_PER_MIN;
    }
#else
    qTzSet();
    const time_t curr = time(nullptr);
    if (curr != -1) {
        /* Set t to the UTC representation of curr; the time whose local
           standard time representation coincides with that differs from curr by
           local time's standard offset.  Note that gmtime() leaves the tm_isdst
           flag set to 0, so mktime() will, even if local time is currently
           using DST, return the time since epoch at which local standard time
           would have the same representation as UTC's representation of
           curr. The fact that mktime() also flips tm_isdst and updates the time
           fields to the DST-equivalent time needn't concern us here; all that
           matters is that it returns the time after epoch at which standard
           time's representation would have matched UTC's, had it been in
           effect.
        */
#  if defined(_POSIX_THREAD_SAFE_FUNCTIONS)
        struct tm t;
        if (gmtime_r(&curr, &t)) {
            time_t mkt = qMkTime(&t);
            int offset = int(curr - mkt);
            Q_ASSERT(std::abs(offset) <= SECS_PER_DAY);
            return offset;
        }
#  else
        if (struct tm *tp = gmtime(&curr)) {
            struct tm t = *tp; // Copy it quick, hopefully before it can get stomped
            time_t mkt = qMkTime(&t);
            int offset = int(curr - mkt);
            Q_ASSERT(std::abs(offset) <= SECS_PER_DAY);
            return offset;
        }
#  endif
    } // else, presumably: errno == EOVERFLOW
#endif // Platform choice
    qDebug("Unable to determine current standard time offset from UTC");
    // We can't tell, presume UTC.
    return 0;
}

// This is local time's offset (in seconds), at the specified time, including
// any DST part.
int getUtcOffset(qint64 atMSecsSinceEpoch)
{
    return QDateTimePrivate::expressUtcAsLocal(atMSecsSinceEpoch).offset;
}
#endif // QT_BOOTSTRAPPED

// Calls the platform variant of localtime() for the given utcMillis, and
// returns the local milliseconds, offset from UTC and DST status.
QDateTimePrivate::ZoneState utcToLocal(qint64 utcMillis)
{
    const auto epoch = QRoundingDown::qDivMod<MSECS_PER_SEC>(utcMillis);
    const time_t epochSeconds = epoch.quotient;
    const int msec = epoch.remainder;
    Q_ASSERT(msec >= 0 && msec < MSECS_PER_SEC);
    if (qint64(epochSeconds) * MSECS_PER_SEC + msec != utcMillis) // time_t range too narrow
        return {utcMillis};

    tm local;
    if (!qLocalTime(epochSeconds, &local))
        return {utcMillis};

    auto jd = tmToJd(local);
    if (Q_UNLIKELY(!jd))
        return {utcMillis};

    const qint64 daySeconds = tmSecsWithinDay(local);
    Q_ASSERT(0 <= daySeconds && daySeconds < SECS_PER_DAY);
    qint64 localSeconds, localMillis;
    if (Q_UNLIKELY(daysAndSecondsOverflow(*jd, daySeconds, &localSeconds)
                   || secondsAndMillisOverflow(localSeconds, qint64(msec), &localMillis))) {
        return {utcMillis};
    }
    const auto dst
        = local.tm_isdst ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;
    return { localMillis, int(localSeconds - epochSeconds), dst };
}

QString localTimeAbbbreviationAt(qint64 local, QDateTimePrivate::TransitionOptions resolve)
{
    auto use = resolveLocalTime(QRoundingDown::qDiv<MSECS_PER_SEC>(local), resolve);
    if (!use.good)
        return {};
#ifdef HAVE_TM_ZONE
    if (use.local.tm_zone)
        return QString::fromLocal8Bit(use.local.tm_zone);
#endif
    return qTzName(use.local.tm_isdst > 0 ? 1 : 0);
}

QDateTimePrivate::ZoneState mapLocalTime(qint64 local, QDateTimePrivate::TransitionOptions resolve)
{
    // Revised later to match what use.local tells us:
    qint64 localSecs = local / MSECS_PER_SEC;
    auto use = resolveLocalTime(localSecs, resolve);
    if (!use.good)
        return {local};

    qint64 millis = local - localSecs * MSECS_PER_SEC;
    // Division is defined to round towards zero:
    Q_ASSERT(local < 0 ? (millis <= 0 && millis > -MSECS_PER_SEC)
                       : (millis >= 0 && millis < MSECS_PER_SEC));

    QDateTimePrivate::DaylightStatus dst =
        use.local.tm_isdst > 0 ? QDateTimePrivate::DaylightTime : QDateTimePrivate::StandardTime;

#ifdef HAVE_TM_GMTOFF
    const int offset = use.local.tm_gmtoff;
    localSecs = offset + use.utcSecs;
#else
    // Provisional offset, until we have a revised localSecs:
    int offset = localSecs - use.utcSecs;
    auto jd = tmToJd(use.local);
    if (Q_UNLIKELY(!jd))
        return {local, offset, dst, false};

    qint64 daySecs = tmSecsWithinDay(use.local);
    Q_ASSERT(0 <= daySecs && daySecs < SECS_PER_DAY);
    if (daySecs > 0 && *jd < JULIAN_DAY_FOR_EPOCH) {
        jd = *jd + 1;
        daySecs -= SECS_PER_DAY;
    }
    if (Q_UNLIKELY(daysAndSecondsOverflow(*jd, daySecs, &localSecs)))
        return {local, offset, dst, false};

    // Use revised localSecs to refine offset:
    offset = localSecs - use.utcSecs;
#endif // HAVE_TM_GMTOFF

    // The only way localSecs and millis can now have opposite sign is for
    // resolution of the local time to have kicked us across the epoch, in which
    // case there's no danger of overflow. So if overflow is in danger of
    // happening, we're already doing the best we can to avoid it.
    qint64 revised;
    if (secondsAndMillisOverflow(localSecs, millis, &revised))
        return {local, offset, QDateTimePrivate::UnknownDaylightTime, false};
    return {revised, offset, dst, true};
}

/*!
    \internal
    Determine the range of the system time_t functions.

    On MS-systems (where time_t is 64-bit by default), the start-point is the
    epoch, the end-point is the end of the year 3000 (for mktime(); for
    _localtime64_s it's 18 days later, but we ignore that here). Darwin's range
    runs from the beginning of 1900 to the end of its 64-bit time_t and Linux
    uses the full range of time_t (but this might still be 32-bit on some
    embedded systems).

    (One potential constraint might appear to be the range of struct tm's int
    tm_year, only allowing time_t to represent times from the start of year
    1900+INT_MIN to the end of year INT_MAX. The 26-bit number of seconds in a
    year means that a 64-bit time_t can indeed represent times outside the range
    of 32-bit years, by a factor of 32 - but the range of representable
    milliseconds needs ten more bits than that of seconds, so can't reach the
    ends of the 32-bit year range.)

    Given the diversity of ranges, we conservatively estimate the actual
    supported range by experiment on the first call to qdatetime.cpp's
    millisInSystemRange() by exploration among the known candidates, converting
    the result to milliseconds and flagging whether each end is the qint64
    range's bound (so millisInSystemRange will know not to try to pad beyond
    those bounds). The probed date-times are somewhat inside the range, but
    close enough to the relevant bound that we can be fairly sure the bound is
    reached, if the probe succeeds.
*/
SystemMillisRange computeSystemMillisRange()
{
    // Assert this here, as this is called just once, in a static initialization.
    Q_ASSERT(QGregorianCalendar::julianFromParts(1970, 1, 1) == JULIAN_DAY_FOR_EPOCH);

    constexpr qint64 TIME_T_MAX = std::numeric_limits<time_t>::max();
    using Bounds = std::numeric_limits<qint64>;
    constexpr bool isNarrow = Bounds::max() / MSECS_PER_SEC > TIME_T_MAX;
    if constexpr (isNarrow) {
        const qint64 msecsMax = quint64(TIME_T_MAX) * MSECS_PER_SEC - 1 + MSECS_PER_SEC;
        const qint64 msecsMin = -1 - msecsMax; // TIME_T_MIN is -1 - TIME_T_MAX
        // If we reach back to msecsMin, use it; otherwise, assume 1970 cut-off (MS).
        struct tm local = {};
        local.tm_year = tmYearFromQYear(1901);
        local.tm_mon = 11;
        local.tm_mday = 15; // A day and a bit after the start of 32-bit time_t:
        local.tm_isdst = -1;
        return {qMkTime(&local) == -1 ? 0 : msecsMin, msecsMax, false, false};
    } else {
        const struct { int year; qint64 millis; } starts[] = {
            { int(QDateTime::YearRange::First) + 1, Bounds::min() },
            // Beginning of the Common Era:
            { 1, -Q_INT64_C(62135596800000) },
            // Invention of the Gregorian calendar:
            { 1582, -Q_INT64_C(12244089600000) },
            // Its adoption by the anglophone world:
            { 1752, -Q_INT64_C(6879427200000) },
            // Before this, struct tm's tm_year is negative (Darwin):
            { 1900, -Q_INT64_C(2208988800000) },
        }, ends[] = {
            { int(QDateTime::YearRange::Last) - 1, Bounds::max() },
            // MS's end-of-range, end of year 3000:
            { 3000, Q_INT64_C(32535215999999) },
        };
        // Assume we do at least reach the end of a signed 32-bit time_t (since
        // our actual time_t is bigger than that):
        qint64 stop =
            quint64(std::numeric_limits<qint32>::max()) * MSECS_PER_SEC - 1 + MSECS_PER_SEC;
        // Cleared if first pass round loop fails:
        bool stopMax = true;
        for (const auto c : ends) {
            struct tm local = {};
            local.tm_year = tmYearFromQYear(c.year);
            local.tm_mon = 11;
            local.tm_mday = 31;
            local.tm_hour = 23;
            local.tm_min = local.tm_sec = 59;
            local.tm_isdst = -1;
            if (qMkTime(&local) != -1) {
                stop = c.millis;
                break;
            }
            stopMax = false;
        }
        bool startMin = true;
        for (const auto c : starts) {
            struct tm local {};
            local.tm_year = tmYearFromQYear(c.year);
            local.tm_mon = 1;
            local.tm_mday = 1;
            local.tm_isdst = -1;
            if (qMkTime(&local) != -1)
                return {c.millis, stop, startMin, stopMax};
            startMin = false;
        }
        return {0, stop, false, stopMax};
    }
}

} // QLocalTime

QT_END_NAMESPACE