summaryrefslogtreecommitdiffstats
path: root/src/corelib/time/qcalendar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/time/qcalendar.cpp')
-rw-r--r--src/corelib/time/qcalendar.cpp222
1 files changed, 138 insertions, 84 deletions
diff --git a/src/corelib/time/qcalendar.cpp b/src/corelib/time/qcalendar.cpp
index 7fa64d6db3..415203ee17 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;
}
@@ -498,7 +470,7 @@ Q_GLOBAL_STATIC(QtPrivate::QCalendarRegistry, calendarRegistry);
\section1 Instantiating backends
Backends may be defined by third-party, plugin or user code. When such
- custom backends are registered they shall be alloced a unique ID, by
+ custom backends are registered they shall be allocated a unique ID, by
which client code may access it. A custom backend instance can have no names
if access by name is not needed, or impractical (e.g. because the backend
is not a singleton and constructing names for each instance would not make
@@ -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