diff options
Diffstat (limited to 'src/corelib/time/qcalendar.cpp')
-rw-r--r-- | src/corelib/time/qcalendar.cpp | 224 |
1 files changed, 139 insertions, 85 deletions
diff --git a/src/corelib/time/qcalendar.cpp b/src/corelib/time/qcalendar.cpp index 0cf350254e..c87d4b7cf3 100644 --- a/src/corelib/time/qcalendar.cpp +++ b/src/corelib/time/qcalendar.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 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 "qcalendar.h" #include "qcalendarbackend_p.h" #include "qgregoriancalendar_p.h" @@ -50,36 +14,26 @@ #include "qislamiccivilcalendar_p.h" #endif +#include <private/qflatmap_p.h> #include "qatomic.h" #include "qdatetime.h" #include "qcalendarmath_p.h" #include <qhash.h> #include <qreadwritelock.h> -#include <qdebug.h> #include <vector> QT_BEGIN_NAMESPACE -namespace { - -struct CalendarName : public QString +struct QCalendarRegistryCaseInsensitiveAnyStringViewLessThan { - CalendarName(const QString &name) : QString(name) {} + struct is_transparent {}; + bool operator()(QAnyStringView lhs, QAnyStringView rhs) const + { + return QAnyStringView::compare(lhs, rhs, Qt::CaseInsensitive) < 0; + } }; -inline bool operator==(const CalendarName &u, const CalendarName &v) -{ - return u.compare(v, Qt::CaseInsensitive) == 0; -} - -inline size_t qHash(const CalendarName &key, size_t seed = 0) noexcept -{ - return qHash(key.toLower(), seed); -} - -} // anonymous namespace - namespace QtPrivate { /* @@ -90,6 +44,8 @@ class QCalendarRegistry { Q_DISABLE_COPY_MOVE(QCalendarRegistry); // This is a singleton. + static constexpr qsizetype ExpectedNumberOfBackends = qsizetype(QCalendar::System::Last) + 1; + /* Lock protecting the registry from concurrent modification. */ @@ -109,7 +65,12 @@ class QCalendarRegistry Each backend may be registered with several names associated with it. The names are case-insensitive. */ - QHash<CalendarName, QCalendarBackend *> byName; + QFlatMap< + QString, QCalendarBackend *, + QCalendarRegistryCaseInsensitiveAnyStringViewLessThan, + QStringList, + std::vector<QCalendarBackend *> + > byName; /* Pointer to the Gregorian backend for faster lockless access to it. @@ -139,7 +100,11 @@ class QCalendarRegistry QCalendar::System system); public: - QCalendarRegistry() { byId.resize(int(QCalendar::System::Last) + 1); } + QCalendarRegistry() + { + byId.resize(ExpectedNumberOfBackends); + byName.reserve(ExpectedNumberOfBackends * 2); // assume one alias on average + } ~QCalendarRegistry(); @@ -340,12 +305,11 @@ void QCalendarRegistry::registerBackendLockHeld(QCalendarBackend *backend, const // Register any names. for (const auto &name : names) { - if (byName.contains(name)) { + auto [it, inserted] = byName.try_emplace(name, backend); + if (!inserted) { Q_ASSERT(system == QCalendar::System::User); - qWarning() << "Cannot register name" << name << "(already in use) for" - << backend->name(); - } else { - byName[name] = backend; + qWarning("Cannot register name %ls (already in use) for %ls", + qUtf16Printable(name), qUtf16Printable(backend->name())); } } } @@ -363,7 +327,7 @@ QStringList QCalendarRegistry::availableCalendars() ensurePopulated(); QReadLocker locker(&lock); - return QStringList(byName.keyBegin(), byName.keyEnd()); + return byName.keys(); } /* @@ -380,7 +344,7 @@ const QCalendarBackend *QCalendarRegistry::fromName(QAnyStringView name) ensurePopulated(); QReadLocker locker(&lock); - return byName.value(name.toString(), nullptr); + return byName.value(name, nullptr); } /* @@ -448,10 +412,18 @@ const QCalendarBackend *QCalendarRegistry::fromEnum(QCalendar::System system) QStringList QCalendarRegistry::backendNames(const QCalendarBackend *backend) { QStringList l; - - QHashIterator<CalendarName, QCalendarBackend *> i(byName); - while (i.findNext(const_cast<QCalendarBackend *>(backend))) - l.push_back(i.key()); + l.reserve(byName.size()); // too large, but never really large, so ok + + QT_WARNING_PUSH + // Clang complains about the reference still causing a copy. The reference is idiomatic, but + // runs afoul of QFlatMap's iterators which return a pair of references instead of a reference + // to pair. Suppress the warning, because `const auto [key, value]` would look wrong. + QT_WARNING_DISABLE_CLANG("-Wrange-loop-analysis") + for (const auto &[key, value] : byName) { + if (value == backend) + l.push_back(key); + } + QT_WARNING_POP return l; } @@ -526,8 +498,8 @@ Q_GLOBAL_STATIC(QtPrivate::QCalendarRegistry, calendarRegistry); base-classes for custom calendar backends, but cannot be instantiated themselves. - \sa calendarId(), QDate, QDateTime, QDateEdit, - QDateTimeEdit, QCalendarWidget + \sa calendarId(), QDate, QDateTime, QDateEdit, QDateTimeEdit, + QCalendarWidget, {The Low-Level API: Extending Qt Applications} */ /*! @@ -881,18 +853,73 @@ int QCalendarBackend::maximumMonthsInYear() const already used in QDate::dayOfWeek() to mean an invalid date). The calendar should treat the numbers used as an \c enum, whose values need not be contiguous, nor need they follow closely from the 1 through 7 of the usual - returns. It suffices that weekDayName() can recognize each such number as - identifying a distinct name, that it returns to identify the particular - intercallary day. + returns. It suffices that; + \list + \li weekDayName() can recognize each such number as identifying a distinct + name, that it returns to identify the particular intercallary day; and + \li matchCenturyToWeekday() can determine what century adjustment aligns a + given date within a century to a given day of the week, where this is + relevant and possible. + \endlist This base implementation uses the day-numbering that various calendars have borrowed off the Hebrew calendar. - \sa weekDayName(), standaloneWeekDayName(), QDate::dayOfWeek() - */ + \sa weekDayName(), standaloneWeekDayName(), QDate::dayOfWeek(), Qt::DayOfWeek +*/ int QCalendarBackend::dayOfWeek(qint64 jd) const { - return QRoundingDown::qMod(jd, 7) + 1; + return QRoundingDown::qMod<7>(jd) + 1; +} + +/*! + \since 6.7 + Adjusts century of \a parts to match \a dow. + + Preserves parts.month and parts.day while adjusting parts.year by a multiple + of 100 (taking the absence of year zero into account, when relevant) to + obtain a date for which dayOfWeek() is \a dow. Prefers smaller changes over + larger and increases to the century over decreases of the same + magnitude. Returns the Julian Day number for the selected date or + std::numeric_limits<qint64>::min(), a.k.a. QDate::nullJd(), if there is no + date matching these requirements. + + The base-class provides a brute-force implementation that steps outwards + from the given date by centures, above and below by up to 14 centuries, in + search of a matching date. This is neither computationally efficient nor + elegant but should work as advertised for calendars in which every month-day + combination does appear on all days of the week, across sufficiently many + centuries. +*/ +qint64 QCalendarBackend::matchCenturyToWeekday(const QCalendar::YearMonthDay &parts, int dow) const +{ + Q_ASSERT(parts.isValid()); + // Brute-force solution as fall-back. + const auto checkOffset = [parts, dow, this](int centuries) -> std::optional<qint64> { + // Offset parts.year by the given number of centuries: + int year = parts.year + centuries * 100; + // but take into account the effect of crossing zero, if we did: + if (!hasYearZero() && (parts.year > 0) != (year > 0)) + year += parts.year > 0 ? -1 : +1; + qint64 jd; + if (isDateValid(year, parts.month, parts.day) + && dateToJulianDay(year, parts.month, parts.day, &jd) + && dayOfWeek(jd) == dow) { + return jd; + } + return std::nullopt; + }; + // Empirically, aside from Gregorian, each calendar finds every dow within + // any 29-century run, so 14 centuries is the biggest offset we ever need. + for (int offset = 0; offset < 15; ++offset) { + if (auto jd = checkOffset(offset)) + return *jd; + if (offset) { + if (auto jd = checkOffset(-offset)) + return *jd; + } + } + return (std::numeric_limits<qint64>::min)(); } // Month and week-day name look-ups (implemented in qlocale.cpp): @@ -1125,6 +1152,7 @@ const QCalendarBackend *QCalendarBackend::gregorian() /*! \class QCalendar::SystemId + \inmodule QtCore \since 6.2 This is an opaque type used to identify custom calendar implementations. The @@ -1163,8 +1191,7 @@ const QCalendarBackend *QCalendarBackend::gregorian() /*! \fn QCalendar::QCalendar() \fn QCalendar::QCalendar(QCalendar::System system) - \fn QCalendar::QCalendar(QLatin1String name) - \fn QCalendar::QCalendar(QStringView name) + \fn QCalendar::QCalendar(QAnyStringView name) Constructs a calendar object. @@ -1174,6 +1201,9 @@ const QCalendarBackend *QCalendarBackend::gregorian() calendar being constructed by other means first. With no argument, the default constructor returns the Gregorian calendar. + \note In Qt versions before 6.4, the constructor by \a name accepted only + QStringView and QLatin1String, not QAnyStringView. + \sa QCalendar, System, isValid() */ @@ -1207,13 +1237,7 @@ QCalendar::QCalendar(QCalendar::SystemId id) Q_ASSERT(!d_ptr || d_ptr->calendarId().index() == id.index()); } -QCalendar::QCalendar(QLatin1String name) - : d_ptr(QCalendarBackend::fromName(name)) -{ - Q_ASSERT(!d_ptr || d_ptr->calendarId().isValid()); -} - -QCalendar::QCalendar(QStringView name) +QCalendar::QCalendar(QAnyStringView name) : d_ptr(QCalendarBackend::fromName(name)) { Q_ASSERT(!d_ptr || d_ptr->calendarId().isValid()); @@ -1456,6 +1480,32 @@ QDate QCalendar::dateFromParts(const QCalendar::YearMonthDay &parts) const } /*! + \since 6.7 + Adjusts the century of a date to match a given day of the week. + + For use when given a date's day of week, day of month, month and last two + digits of the year. Returns a QDate instance with the given \a dow as its \l + {QDate::}{dayOfWeek()}, matching the given \a parts in month and day of the + month. The returned QDate's \l {QDate::}{year()} shall differ from + \c{parts.year} by a multiple of 100, preferring small multiples over larger + and positive multiples over their negations. + + If no date matches these conditions, an invalid QDate is returned: the day + of week is incompatible with the other data given. This arises, for example, + with the Gregorian calendar, whose 400-year cycle is a whole number of weeks + long, so any given month and day of that month only ever falls, in years + with a given last two digits, on four days of the week. (In the special case + of February 29th at the turn of a century, when that is a leap year, only + one day of the week is possible: Tuesday.) +*/ +QDate QCalendar::matchCenturyToWeekday(const QCalendar::YearMonthDay &parts, int dow) const +{ + SAFE_D(); + return d && parts.isValid() + ? QDate::fromJulianDay(d->matchCenturyToWeekday(parts, dow)) : QDate(); +} + +/*! Converts a QDate to a year, month, and day of the month. The returned structure's isValid() shall be false if the calendar is unable @@ -1631,3 +1681,7 @@ QStringList QCalendar::availableCalendars() } QT_END_NAMESPACE + +#ifndef QT_BOOTSTRAPPED +#include "moc_qcalendar.cpp" +#endif |