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.cpp1116
1 files changed, 665 insertions, 451 deletions
diff --git a/src/corelib/time/qcalendar.cpp b/src/corelib/time/qcalendar.cpp
index e6094cb2af..415203ee17 100644
--- a/src/corelib/time/qcalendar.cpp
+++ b/src/corelib/time/qcalendar.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 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,196 +14,244 @@
#include "qislamiccivilcalendar_p.h"
#endif
+#include <private/qflatmap_p.h>
#include "qatomic.h"
#include "qdatetime.h"
#include "qcalendarmath_p.h"
#include <qhash.h>
-#include <qmutex.h>
-#include <private/qlocking_p.h>
-#include <qdebug.h>
+#include <qreadwritelock.h>
#include <vector>
QT_BEGIN_NAMESPACE
-static const QCalendarBackend *backendFromEnum(QCalendar::System system);
-
-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;
-}
+namespace QtPrivate {
-inline size_t qHash(const CalendarName &key, size_t seed = 0) noexcept
+/*
+ \internal
+ Handles calendar backend registration.
+*/
+class QCalendarRegistry
{
- return qHash(key.toLower(), seed);
-}
+ Q_DISABLE_COPY_MOVE(QCalendarRegistry); // This is a singleton.
-constexpr size_t invalidBackendId = ~size_t(0);
-static QBasicMutex registryMutex; // Protects registry from concurrent access
+ static constexpr qsizetype ExpectedNumberOfBackends = qsizetype(QCalendar::System::Last) + 1;
-struct Registry
-{
+ /*
+ Lock protecting the registry from concurrent modification.
+ */
+ QReadWriteLock lock;
+
+ /*
+ Vector containing all registered backends.
+
+ The indices 0 to \c QCalendar::System::Last inclusive are allocated
+ for system backends and always present (but may be null).
+ */
std::vector<QCalendarBackend *> byId;
- QHash<CalendarName, QCalendarBackend *> byName;
+
+ /*
+ Backends registered by name.
+
+ Each backend may be registered with several names associated with it.
+ The names are case-insensitive.
+ */
+ QFlatMap<
+ QString, QCalendarBackend *,
+ QCalendarRegistryCaseInsensitiveAnyStringViewLessThan,
+ QStringList,
+ std::vector<QCalendarBackend *>
+ > byName;
+
+ /*
+ Pointer to the Gregorian backend for faster lockless access to it.
+
+ This pointer may be null if the Gregorian backend is not yet registered.
+ This pointer may only be set once and only when write lock is held on
+ the registry.
+ */
QAtomicPointer<const QCalendarBackend> gregorianCalendar = nullptr;
- QAtomicInteger<int> status = 0; // 1: populated, 2: destructing
- Registry()
- {
- byId.resize(int(QCalendar::System::Last) + 1);
- }
+ enum : int {
+ Unpopulated, // The standard backends may not yet be created
+ Populated, // All standard backends were created
+ IsBeingDestroyed, // The registry and the backends are being destroyed
+ };
- ~Registry()
- {
- status.storeRelaxed(2);
- const auto lock = qt_scoped_lock(registryMutex);
- qDeleteAll(byId);
- }
+ /*
+ Fast way to check whether the standard calendars were populated.
+
+ The status should only be changed while the write lock is held.
+ */
+ QAtomicInt status = Unpopulated;
+
+ void ensurePopulated();
+ QCalendarBackend *registerSystemBackendLockHeld(QCalendar::System system);
+ void registerBackendLockHeld(QCalendarBackend *backend, const QStringList &names,
+ QCalendar::System system);
- bool registerName(QCalendarBackend *calendar, const QString &name)
+public:
+ QCalendarRegistry()
{
- Q_ASSERT(!name.isEmpty());
- if (status.loadRelaxed() > 1 || name.isEmpty() || calendar->calendarId() == invalidBackendId)
- return false;
- const auto lock = qt_scoped_lock(registryMutex);
-
- const auto found = byName.find(name);
- if (found != byName.end()) {
- // Re-registering a calendar with a name it has already is OK (and
- // can be used to test whether its constructor successfully
- // registered its primary name).
- return found.value() == calendar;
- }
- byName.insert(name, calendar);
- return true;
- }
- /*
- \internal
- Registers a backend by ID and returns its ID.
-
- If \a id is QCalendar::System::User, a new unique ID is allocated;
- otherwise, \a id must be a member of the QCalendar::System enumeration
- and the \a calendar is registered with that as its ID unless this ID is
- already in use, in which case registration fails and \c{~size_t(0)} is
- returned. In (only) this last case, the caller is responsible for
- destruction. Otherwise, registry destruction will take care of it on
- program exit.
- */
- size_t registerId(QCalendarBackend *calendar, QCalendar::System system)
- {
- if (status.loadRelaxed() > 1)
- return invalidBackendId;
- const auto lock = qt_scoped_lock(registryMutex);
-
- size_t index = size_t(system);
- if (system == QCalendar::System::User) {
- index = byId.size();
- byId.push_back(calendar);
- populate(); // So that name registration can't pre-empt a standard calendar.
- } else if (byId[index] == nullptr) {
- Q_ASSERT(byId.size() > index);
- Q_ASSERT(byId[index] == nullptr);
- byId[index] = calendar;
- } else {
- return invalidBackendId;
- }
- if (system == QCalendar::System::Gregorian) {
- // Only the first QGregorianCalendar can get here, so this should succeed:
- const bool ok = gregorianCalendar.testAndSetRelease(nullptr, calendar);
-#if defined(QT_FORCE_ASSERTS) || !defined(QT_NO_DEBUG)
- Q_ASSERT(ok);
-#else
- Q_UNUSED(ok);
-#endif
- }
- return index;
+ byId.resize(ExpectedNumberOfBackends);
+ byName.reserve(ExpectedNumberOfBackends * 2); // assume one alias on average
}
+
+ ~QCalendarRegistry();
+
+ bool isBeingDestroyed() const { return status.loadRelaxed() == IsBeingDestroyed; }
+
+ void registerCustomBackend(QCalendarBackend *backend, const QStringList &names);
+
+ QStringList availableCalendars();
+
/*
- \internal
- Should not normally be needed and is expensive.
+ Returns backend for Gregorian calendar.
- Removes all references to this calendar from the registry.
+ The backend is returned without locking the registry if possible.
*/
- void unRegister(QCalendarBackend *calendar)
+ const QCalendarBackend *gregorian()
{
- if (status.loadRelaxed() > 1)
- return;
- const auto lock = qt_scoped_lock(registryMutex);
-
- const size_t id = calendar->calendarId();
- if (~id && byId[id] == calendar)
- byId[id] = nullptr; // potentially purge nullptr entries from tail of byId[Last+1:] ?
- for (auto i = byName.begin(); i != byName.end(); ) {
- if (i.value() == calendar)
- i = byName.erase(i);
- else
- ++i;
- }
+ const QCalendarBackend *backend = gregorianCalendar.loadAcquire();
+ if (Q_LIKELY(backend != nullptr))
+ return backend;
+ return fromEnum(QCalendar::System::Gregorian);
}
+
/*
- \internal
- Ensures each \c{enum}-available calendar has been instantiated.
+ Returns \a true if the argument matches the registered Gregorian backend.
- This arranges for each to register itself by name; it only does anything on
- its first call, which ensures that name-based lookups can always find all
- the calendars available via the \c enum.
+ \a backend should not be \nullptr.
*/
- void populate()
+ bool isGregorian(const QCalendarBackend *backend) const
{
- if (status.loadRelaxed())
- return;
-
- for (int i = 0; i <= int(QCalendar::System::Last); ++i) {
- {
- const auto lock = qt_scoped_lock(registryMutex); // so we can check byId[i]
- if (status.loadRelaxed()) // Might as well check while we're locked
- return;
- if (byId[i])
- continue;
- }
- (void)backendFromEnum(QCalendar::System(i));
- }
-
- status.testAndSetRelease(0, 1);
+ return backend == gregorianCalendar.loadRelaxed();
}
+
+ const QCalendarBackend *fromName(QAnyStringView name);
+ const QCalendarBackend *fromIndex(size_t index);
+ const QCalendarBackend *fromEnum(QCalendar::System system);
+
+ QStringList backendNames(const QCalendarBackend *backend);
};
-} // anonymous namespace
+/*
+ Destroy the registry.
-Q_GLOBAL_STATIC(Registry, calendarRegistry);
+ This destroys all registered backends. This destructor should only be called
+ in a single-threaded context at program exit.
+*/
+QCalendarRegistry::~QCalendarRegistry()
+{
+ QWriteLocker locker(&lock);
+
+ status.storeRelaxed(IsBeingDestroyed);
+
+ qDeleteAll(byId);
+}
+
+/*
+ Registers a custom backend.
-// Must not be called in a thread that's holding registryMutex locked,
-// since it calls constructors, which need to register.
-static const QCalendarBackend *backendFromEnum(QCalendar::System system)
+ A new unique ID is allocated for the \a backend. The registry takes
+ ownership of the \a backend.
+
+ The \a names of the backend are also registered. Already registered
+ names are not updated.
+
+ The \a backend should not be already registered.
+
+ The \a backend should be fully initialized. It becomes available
+ to other threads before this function returns.
+*/
+void QCalendarRegistry::registerCustomBackend(QCalendarBackend *backend, const QStringList &names)
{
+ Q_ASSERT(!backend->calendarId().isValid());
+
+ ensurePopulated();
+
+ QWriteLocker locker(&lock);
+ registerBackendLockHeld(backend, names, QCalendar::System::User);
+}
+
+/*
+ Ensures all system calendars have been instantiated.
+
+ This arranges for each system backend to be registered. The method only
+ does anything on its first call, which ensures that name-based lookups can
+ always find all the calendars available via the \c QCalendar::System other
+ than \c QCalendar::System::User.
+*/
+void QCalendarRegistry::ensurePopulated()
+{
+ if (Q_LIKELY(status.loadAcquire() != Unpopulated))
+ return;
+
+ QWriteLocker locker(&lock);
+ if (status.loadAcquire() != Unpopulated)
+ return;
+
+ for (int i = 0; i <= int(QCalendar::System::Last); ++i) {
+ if (byId[i] == nullptr)
+ registerSystemBackendLockHeld(QCalendar::System(i));
+ }
+
+#if defined(QT_FORCE_ASSERTS) || !defined(QT_NO_DEBUG)
+ auto oldValue = status.fetchAndStoreRelease(Populated);
+ Q_ASSERT(oldValue == Unpopulated);
+#else
+ status.storeRelease(Populated);
+#endif
+}
+
+/*
+ Helper functions for system backend registration.
+
+ This function must be called with write lock held on the registry.
+
+ \sa registerSystemBackend
+*/
+QCalendarBackend *QCalendarRegistry::registerSystemBackendLockHeld(QCalendar::System system)
+{
+ Q_ASSERT(system != QCalendar::System::User);
+
QCalendarBackend *backend = nullptr;
+ QStringList names;
+
switch (system) {
case QCalendar::System::Gregorian:
backend = new QGregorianCalendar;
+ names = QGregorianCalendar::nameList();
break;
#ifndef QT_BOOTSTRAPPED
case QCalendar::System::Julian:
backend = new QJulianCalendar;
+ names = QJulianCalendar::nameList();
break;
case QCalendar::System::Milankovic:
backend = new QMilankovicCalendar;
+ names = QMilankovicCalendar::nameList();
break;
#endif
#if QT_CONFIG(jalalicalendar)
case QCalendar::System::Jalali:
backend = new QJalaliCalendar;
+ names = QJalaliCalendar::nameList();
break;
#endif
#if QT_CONFIG(islamiccivilcalendar)
case QCalendar::System::IslamicCivil:
backend = new QIslamicCivilCalendar;
+ names = QIslamicCivilCalendar::nameList();
break;
#else // When highest-numbered system isn't enabled, ensure we have a case for Last:
case QCalendar::System::Last:
@@ -248,21 +260,178 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
Q_UNREACHABLE();
}
if (!backend)
- return backend;
- // Check for successful registration:
- if (~backend->calendarId()) {
+ return nullptr;
+
+ registerBackendLockHeld(backend, names, system);
+ Q_ASSERT(backend == byId[size_t(system)]);
+
+ return backend;
+}
+
+/*
+ Helper function for backend registration.
+
+ This function must be called with write lock held on the registry.
+
+ \sa registerBackend
+*/
+void QCalendarRegistry::registerBackendLockHeld(QCalendarBackend *backend, const QStringList &names,
+ QCalendar::System system)
+{
+ Q_ASSERT(!backend->calendarId().isValid());
+
+ auto index = size_t(system);
+
+ // Note: it is important to update the calendar ID before making
+ // the calendar available for queries.
+ if (system == QCalendar::System::User) {
+ backend->setIndex(byId.size());
+ byId.push_back(backend);
+ } else if (byId[index] == nullptr) {
+ backend->setIndex(index);
+ if (system == QCalendar::System::Gregorian) {
#if defined(QT_FORCE_ASSERTS) || !defined(QT_NO_DEBUG)
- const auto lock = qt_scoped_lock(registryMutex);
- Q_ASSERT(backend == calendarRegistry->byId[size_t(system)]);
-#endif // else Q_ASSERT() is a no-op and we don't need the lock
- return backend;
+ auto oldValue = gregorianCalendar.fetchAndStoreRelease(backend);
+ Q_ASSERT(oldValue == nullptr);
+#else
+ gregorianCalendar.storeRelease(backend);
+#endif
+ }
+
+ Q_ASSERT(byId.size() > index);
+ Q_ASSERT(byId[index] == nullptr);
+ byId[index] = backend;
+ }
+
+ // Register any names.
+ for (const auto &name : names) {
+ auto [it, inserted] = byName.try_emplace(name, backend);
+ if (!inserted) {
+ Q_ASSERT(system == QCalendar::System::User);
+ qWarning("Cannot register name %ls (already in use) for %ls",
+ qUtf16Printable(name), qUtf16Printable(backend->name()));
+ }
}
- // Duplicate registration: caller can be sure that byId[system] is correctly
- // set, provided system <= Last.
- delete backend;
+}
+
+/*
+ Returns a list of names of the available calendar systems.
+
+ Any QCalendarBackend sub-class must be registered before being exposed to Date
+ and Time APIs.
+
+ \sa fromName()
+*/
+QStringList QCalendarRegistry::availableCalendars()
+{
+ ensurePopulated();
+
+ QReadLocker locker(&lock);
+ return byName.keys();
+}
+
+/*
+ Returns a pointer to a named calendar backend.
+
+ If the given \a name is present in availableCalendars(), the backend
+ matching it is returned. Otherwise, \nullptr is returned. Matching of
+ names ignores case.
+
+ \sa availableCalendars(), fromEnum(), fromIndex()
+*/
+const QCalendarBackend *QCalendarRegistry::fromName(QAnyStringView name)
+{
+ ensurePopulated();
+
+ QReadLocker locker(&lock);
+ return byName.value(name, nullptr);
+}
+
+/*
+ Returns a pointer to a calendar backend, specified by index.
+
+ If a calendar with ID \a index is known to the calendar registry, the backend
+ with this ID is returned. Otherwise, \nullptr is returned.
+
+ \sa fromEnum(), calendarId()
+*/
+const QCalendarBackend *QCalendarRegistry::fromIndex(size_t index)
+{
+ {
+ QReadLocker locker(&lock);
+
+ if (index >= byId.size())
+ return nullptr;
+
+ if (auto backend = byId[index])
+ return backend;
+ }
+
+ if (index <= size_t(QCalendar::System::Last))
+ return fromEnum(QCalendar::System(index));
+
return nullptr;
}
+/*
+ Returns a pointer to a calendar backend, specified by \a system.
+
+ This will instantiate the indicated calendar (which will enable fromName()
+ to return it subsequently), but only for the Qt-supported calendars for
+ which (where relevant) the appropriate feature has been enabled.
+
+ \a system should be a member of \a QCalendar::System other than
+ \a QCalendar::System::User.
+
+ \sa fromName(), fromId()
+*/
+const QCalendarBackend *QCalendarRegistry::fromEnum(QCalendar::System system)
+{
+ Q_ASSERT(system <= QCalendar::System::Last);
+ auto index = size_t(system);
+
+ {
+ QReadLocker locker(&lock);
+ Q_ASSERT(byId.size() > index);
+ if (auto backend = byId[index])
+ return backend;
+ }
+
+ QWriteLocker locker(&lock);
+
+ // Check if the backend was registered after releasing the read lock above.
+ if (auto backend = byId[index])
+ return backend;
+
+ return registerSystemBackendLockHeld(system);
+}
+
+/*
+ Returns a list of names \a backend was registered with.
+*/
+QStringList QCalendarRegistry::backendNames(const QCalendarBackend *backend)
+{
+ QStringList l;
+ 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;
+}
+
+} // namespace QtPrivate
+
+Q_GLOBAL_STATIC(QtPrivate::QCalendarRegistry, calendarRegistry);
+
/*!
\since 5.14
@@ -273,15 +442,16 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
\brief The QCalendarBackend class provides basic calendaring functions.
QCalendarBackend provides the base class on which all calendar types are
- implemented. On construction, the backend is registered with its primary
- name.
+ implemented. The backend must be registered before it is available via
+ QCalendar API. The registration for system backends is arranged by
+ the calendar registry. Custom backends may be registered using the
+ \c registerCustomBackend() method.
- A backend, once successfully registered with its primary name, may also be
- registered with aliases, where the calendar is known by several
- names. Registering with the name used by CLDR (the Unicode consortium's
- Common Locale Data Repository) is recommended, particularly when interacting
- with third-party software. Once a backend is registered for a name,
- QCalendar can be constructed using that name to select the backend.
+ A backend may also be registered by one or more names. Registering with the
+ name used by CLDR (the Unicode consortium's Common Locale Data Repository)
+ is recommended, particularly when interacting with third-party software.
+ Once a backend is registered for a name, QCalendar can be constructed using
+ that name to select the backend.
Each built-in backend has a distinct primary name and all built-in backends
are instantiated before any custom backend is registered, to prevent custom
@@ -292,174 +462,145 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
needed.
Most backends are pure code, with only one data element (this base-classe's
- \c m_id). Such backends should normally be implemented as singletons. For a
- backend to be added to the QCalendar::System \c enum, it must be such a
- singleton, with a case in backendFromEnum()'s switch statement (above) to
- instantiate it.
+ \c m_id). Such backends should normally be implemented as singletons.
+
+ The backends may be used by multiple threads simultaneously. The virtual
+ methods must be implemented in a \l {thread-safe} way.
\section1 Instantiating backends
Backends may be defined by third-party, plugin or user code. When such
- custom backends are instantiated, in their calls to the QCalendarBackend
- base-class constructor, each instance should pass a distinct primary name
- and omit the \c system parameter. Each shall be alloced a unique ID, by
- which client code may access it. A custom backend instance can be created
- with an empty name 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 sense). Otherwise, each backend should be created
- with a distinct name, as only the first with each name will be registered by
- name. On the other hand, if a backend already exists for the given name,
- most likely it implements the same calendar, so there may be no need for the
- new backend.
+ 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
+ sense). If a custom backend has names that are already registered for
+ another backend, those names are ignored.
A backend class that has instance variables as well as code may be
- instantiated many times, each with a distinct primary name, to implement
+ instantiated many times, each with a distinct set of names, to implement
distinct backends - presumably variants on some parameterized calendar.
Each instance is then a distinct backend. A pure code backend class shall
typically only be instantiated once, as it is only capable of representing
one backend.
Each backend should be instantiated exactly once, on the heap (using the C++
- \c new operator); this will register it with the QCalendar implementation
- code and ensure it is available, by its primary name, to all code that may
- subsequently need it. It will be deleted on program termination along with
- the registry in which QCalendar records backends.
-
- The single exception to this is that each backend's instantiator should
- verify that it was registered successfully. If its calendarId() is
- \c{~size_t(0)}, it has not been registered and attempts to register aliases
- for it shall fail. In such a case, the instantiator retains ownership of the
- backend instance; it will not be accessible to QCalendar. Note that (since
- Qt 6.1) a backend may be successfully registered by ID without being
- registered by name with its primary name, if that name was already in use by
- some backend registered before it.
-
- Built-in backends, identified by QCalendar::System values other than User,
- should only be instantiated by code in the implementation of QCalendar; no
- other code should ever instantiate one. As noted above, such a backend must
- be a singleton. Its constructor passes down the \c enum member that
- identifies it as \c system to the base-class constructor; this is also its
- calendarSystem(). Its unique ID (see \l calendarId()) shall be the \c size_t
- equivalent to this \c system value.
+ \c new operator), so that the registry can take ownership of it after
+ registration.
+
+ Built-in backends, identified by \c QCalendar::System values other than
+ \c{User}, should only be registered by \c{QCalendarRegistry::fromEnum()};
+ no other code should ever register one, this guarantees that such a backend
+ will be a singleton.
The shareable base-classes for backends, QRomanCalendar and QHijriCalendar,
are not themselves identified by QCalendar::System and may be used as
base-classes for custom calendar backends, but cannot be instantiated
themselves.
- \sa registerAlias(), calendarId(), QDate, QDateTime, QDateEdit,
+ \sa calendarId(), QDate, QDateTime, QDateEdit,
QDateTimeEdit, QCalendarWidget
*/
/*!
- Constructs the calendar backend and registers it.
-
- On successful registration, the calendar backend registry takes over
- ownership of the instance and shall delete it on program exit in the course
- of the registry's own destruction. The instance can determine whether it was
- successfully registered by testing \c{~calendarId()}: this shall be zero
- precisely if registration failed. QCalendar cannot use an unregistered
- backend and the code that instantiated it is responsible for deleting it.
-
- The \a system is optional and should only be passed by built-in
- implementations of the standard calendars documented in \l
- QCalendar::System. When \a system is passed, unless a backend is already
- registered for this \c enum value, the backend is registered with
- \c{size_t(system)} as its ID. Custom backends should not pass \a
- system. When it is not passed, the backend is allocated a unique calendar
- ID, which shall be accessible as \l calendarId(). This ID can (since Qt 6.1)
- be used to instantiate a QCalendar using the given backend, avoiding
- problems with potential clashes of name or alias.
-
- Only one backend instance should ever be registered for any given \a system:
- in the event of a backend being created when one with the same \a system
- already exists, the new backend is not registered and calendarSystem() shall
- return \l{QCalendar::System}{User}. Code that instantiates a backend with a
- \a system other than \l{QCalendar::System}{User} must check calendarSystem()
- when doing so and take responsibility for destruction of the instance if
- this shows it has not been registered. The \a name passed with a \a system
- (other than \l{QCalendar::System}{User}) must be the \c{name()} of the
- backend constructed.
-
- The \a name may be empty, for example when the backend is only used by ID.
- Otherwise, the \a name should be unique; after one backend has been
- registered for a name or alias, no other backend can be registered with that
- name or alias. The backend can test whether instantiation successfully
- registered its primary name by calling registerAlias() on that name. The
- presence of another backend registered with the same name may mean the
- backend is redundant, as the system already has a backend to handle the
- given calendar type. Even when a custom backend fails to register by name,
- it is still registered by ID and can (since Qt 6.1) register aliases
- (although these will also fail if another backend registered for them
- first), despite failing to register its primary name.
-
- QCalendar can only access a custom backend using its ID or a name or alias
- which the backend has successfully registered. If a backend fails to
- register with its primary name or any aliases, the code that instantiated it
- may delete it, if it has not shared the ID with any code that may have
- instantiated a QCalendar using the ID. There is no need to do this, as the
- registry shall take care of deleting it on program termination. Once the ID
- has been shared with other code, however, or if the backend does succeed in
- registering a name or any aliases, other code may have QCalendar instances
- using the backend, so deletion would lead to undefined behavior.
-
- \note \c{QCalendar(name).isValid()} will return true precisely when the
- given \c name is in use already. This can be used as a test before
- instantiating a backend with the given \c name.
-
- \sa calendarId(), calendarSystem(), registerAlias()
-*/
-QCalendarBackend::QCalendarBackend(const QString &name, QCalendar::System system)
- // Will lock the registry mutex on its own, so no need to do it here:
- : m_id(calendarRegistry->registerId(this, system))
-{
- // System calendars *must* pass non-empty name:
- Q_ASSERT(system == QCalendar::System::User || !name.isEmpty());
- if (calendarSystem() == system) { // Successfully registered by ID:
- if (!name.isEmpty())
- calendarRegistry->registerName(this, name);
- } else {
- Q_ASSERT(size_t(system) <= size_t(QCalendar::System::Last));
- }
-}
-
-/*!
Destroys the calendar backend.
Each calendar backend, once instantiated and successfully registered by ID,
- shall exist for the lifetime of the program. Its destruction is taken care
- of by the calendar backend registry on program termination. Destroying a
+ shall exist until it is destroyed by the registry. Destroying a
successfully-registered backend otherwise may leave existing QCalendar
instances referencing the destroyed calendar, with undefined results.
- However, if a backend, when instantiated, has calendarId() equal to
- \c{~size_t(0)}, it has not been registered and cannot be accessed by
- QCalendar. Code that instantiates backends must check for this and delete
- the unusable backend object, as it shall be leaked otherwise. This is the
- only case in which client code needs to delete a backend.
-
- If a backend has not been registered for any names (and, from 6.1, its ID
- has not been shared with other code that could have used it to instantiate a
- QCalendar) it may safely be deleted, but there is no need to do so.
+ If a backend has not been registered it may safely be deleted.
\sa calendarId()
*/
QCalendarBackend::~QCalendarBackend()
{
- // No registration to undo unless registry exists and gave this a valid ID:
- if (calendarRegistry.exists() && ~calendarId())
- calendarRegistry->unRegister(this);
+ Q_ASSERT(!m_id.isValid() || calendarRegistry.isDestroyed()
+ || calendarRegistry->isBeingDestroyed());
+}
+
+/*!
+ \fn QString QCalendarBackend::name() const
+ Returns the primary name of the calendar.
+ */
+
+/*!
+ Returns list of names this backend was registered with.
+
+ The list is a subset of the names passed to \c registerCustomBackend().
+ Some names passed during the registration may not be associated
+ with a backend if they were claimed by another backend first.
+
+ \sa registerCustomBackend()
+*/
+QStringList QCalendarBackend::names() const
+{
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed()))
+ return {};
+
+ return calendarRegistry->backendNames(this);
+}
+
+/*!
+ Set the internal index of the backed to the specified value.
+
+ This method exists to allow QCalendarRegistry to update the backend ID
+ after registration without exposing it in public API for QCalendar.
+ */
+void QCalendarBackend::setIndex(size_t index)
+{
+ Q_ASSERT(!m_id.isValid());
+ m_id.id = index;
+}
+
+/*!
+ Register this backend as a custom backend.
+
+ The backend should not already be registered. This method should only be
+ called on objects that are completely initialized because they become
+ available to other threads immediately. In particular, this function should
+ not be called from backend constructors.
+
+ The backend is also registered by names passed in \a names. Only the names
+ that are not already registered are associated with the backend. The name
+ matching is case-insensitive. The list of names associated with the backend
+ can be queried using \c names() method after successful registration.
+
+ Returns the new ID assigned to this backend. If its isValid() is \c true,
+ the calendar registry has taken ownership of the object; this ID can then
+ be used to create \c QCalendar instances. Otherwise, registration failed
+ and the caller is responsible for destruction of the backend, which shall
+ not be available for use by \c QCalendar. Failure should normally only
+ happen if registration is attempted during program termination.
+
+ \sa names()
+*/
+QCalendar::SystemId QCalendarBackend::registerCustomBackend(const QStringList &names)
+{
+ Q_ASSERT(!m_id.isValid());
+
+ if (Q_LIKELY(!calendarRegistry.isDestroyed()))
+ calendarRegistry->registerCustomBackend(this, names);
+
+ return m_id;
+}
+
+bool QCalendarBackend::isGregorian() const
+{
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed()))
+ return false;
+
+ return calendarRegistry->isGregorian(this);
}
/*!
\since 6.2
- \fn size_t QCalendarBackend::calendarId() const
+ \fn QCalendar::SystemId QCalendarBackend::calendarId() const
- Each backend is allocated an ID when successfully registered as part of
- instantiation. A backend for which calendarId() is \c{~size_t(0)} has not
- been successfully registered and the code that created it must take care to
- ensure it is deleted; it also cannot be used, as it is not known to any of
- the available ways to create a QCalendar.
+ Each backend is allocated an ID when successfully registered. A backend whose
+ calendarId() has isValid() \c{false} has not been registered; it also cannot
+ be used, as it is not known to any of the available ways to create a QCalendar.
\sa calendarSystem(), fromId()
*/
@@ -467,30 +608,21 @@ QCalendarBackend::~QCalendarBackend()
/*!
The calendar system of this calendar.
- For systems successfully registered with a member of QCalendar::System other
- than User, this is just the registered \c enum value; all others have
- User. If a backend tried to register itself with an \c enum member other
- than User and finds its calendarSystem() is User, the code that created it
- shall be responsible for its deletion; otherwise, the calendar register
- shall take responsiblity for deleting it on exit.
-
\sa fromEnum(), calendarId()
*/
QCalendar::System QCalendarBackend::calendarSystem() const
{
- if (m_id > size_t(QCalendar::System::Last))
- return QCalendar::System::User;
- return QCalendar::System(m_id);
+ return m_id.isInEnum() ? QCalendar::System(m_id.index()) : QCalendar::System::User;
}
-/*!
- \fn QString QCalendarBackend::name() const;
-
- This pure virtual method should be overloaded by each backend implementation
- to return a name for which the backend has been successfully registered.
+/*
+ Create local variable d containing the backend associated with a QCalendar
+ instance unless the calendar registry is destroyed together with all backends,
+ then return nullptr.
- \sa registerAlias()
+ This assumes that the registry is only destroyed in single threaded context.
*/
+#define SAFE_D() const auto d = Q_UNLIKELY(calendarRegistry.isDestroyed()) ? nullptr : d_ptr
/*!
The primary name of this calendar.
@@ -501,6 +633,7 @@ QCalendar::System QCalendarBackend::calendarSystem() const
*/
QString QCalendar::name() const
{
+ SAFE_D();
return d ? d->name() : QString();
}
@@ -720,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):
@@ -842,42 +1030,14 @@ int QCalendarBackend::dayOfWeek(qint64 jd) const
QCalendarBackend sub-class must be registered before being exposed to Date
and Time APIs.
- \sa registerAlias(), fromName()
+ \sa fromName()
*/
QStringList QCalendarBackend::availableCalendars()
{
- if (calendarRegistry.isDestroyed())
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed()))
return {};
- calendarRegistry->populate();
- const auto registryLock = qt_scoped_lock(registryMutex);
- return QStringList(calendarRegistry->byName.keyBegin(), calendarRegistry->byName.keyEnd());
-}
-
-/*!
- Registers an alias for this calendar backend.
-
- Once a backend is registered, the alias will be included in the list of
- available calendars and the calendar can be instantiated by name.
- Returns \c false if the given \a name is already in use by a different
- backend or the backend has not been registered successfully by ID or \c true
- if this calendar is already registered with this name. (This can be used,
- with its primary name, to test whether a backend's construction successfully
- registered this name for it.) Otherwise it registers this calendar backend
- for this name and returns \c true.
-
- \sa availableCalendars(), fromName()
-*/
-bool QCalendarBackend::registerAlias(const QString &name)
-{
- if (!calendarRegistry.exists() || name.isEmpty())
- return false;
- // Constructing this accessed the registry, so ensured it exists:
- Q_ASSERT(calendarRegistry.exists());
-
- // Not taking the lock on the registry here because it's just one call
- // (which internally locks anyway).
- return calendarRegistry->registerName(this, name);
+ return calendarRegistry->availableCalendars();
}
/*!
@@ -885,36 +1045,17 @@ bool QCalendarBackend::registerAlias(const QString &name)
Returns a pointer to a named calendar backend.
If the given \a name is present in availableCalendars(), the backend
- matching it is returned; otherwise, \c nullptr is returned. Matching of
- names ignores case. Note that this does not provoke construction of a
- calendar backend, other than those available via \l fromEnum(): it will only
- return ones that have been instantiated (and not yet destroyed) by some
- other means.
+ matching it is returned; otherwise, \nullptr is returned. Matching of
+ names ignores case.
- \sa availableCalendars(), registerAlias(), fromEnum(), fromId()
+ \sa availableCalendars(), fromEnum(), fromId()
*/
-const QCalendarBackend *QCalendarBackend::fromName(QStringView name)
+const QCalendarBackend *QCalendarBackend::fromName(QAnyStringView name)
{
- if (calendarRegistry.isDestroyed())
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed()))
return nullptr;
- calendarRegistry->populate();
- const auto registryLock = qt_scoped_lock(registryMutex);
- auto it = calendarRegistry->byName.find(name.toString());
- return it == calendarRegistry->byName.end() ? nullptr : *it;
-}
-/*!
- \internal
- \overload
- */
-const QCalendarBackend *QCalendarBackend::fromName(QLatin1String name)
-{
- if (calendarRegistry.isDestroyed())
- return nullptr;
- calendarRegistry->populate();
- const auto registryLock = qt_scoped_lock(registryMutex);
- auto it = calendarRegistry->byName.find(QString(name));
- return it == calendarRegistry->byName.end() ? nullptr : *it;
+ return calendarRegistry->fromName(name);
}
/*!
@@ -922,22 +1063,16 @@ const QCalendarBackend *QCalendarBackend::fromName(QLatin1String name)
Returns a pointer to a calendar backend, specified by ID.
If a calendar with ID \a id is known to the calendar registry, the backend
- with this ID is returned; otherwise, \c nullptr is returned. Note that this
- does not provoke construction of a calendar backend, other than those
- available via \l fromEnum(): it will only return ones that have been
- instantiated (and not yet destroyed) by some other means.
+ with this ID is returned; otherwise, \nullptr is returned.
\sa fromEnum(), calendarId()
*/
-const QCalendarBackend *QCalendarBackend::fromId(size_t id)
+const QCalendarBackend *QCalendarBackend::fromId(QCalendar::SystemId id)
{
- if (calendarRegistry.isDestroyed() || !~id)
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed() || !id.isValid()))
return nullptr;
- if (auto *c = id < calendarRegistry->byId.size() ? calendarRegistry->byId[id] : nullptr)
- return c;
- if (id <= size_t(QCalendar::System::Last))
- return fromEnum(QCalendar::System(id));
- return nullptr;
+
+ return calendarRegistry->fromIndex(id.index());
}
/*!
@@ -952,20 +1087,24 @@ const QCalendarBackend *QCalendarBackend::fromId(size_t id)
*/
const QCalendarBackend *QCalendarBackend::fromEnum(QCalendar::System system)
{
- Q_ASSERT(system == QCalendar::System::User
- || size_t(system) <= size_t(QCalendar::System::Last));
- if (calendarRegistry.isDestroyed() || system == QCalendar::System::User)
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed() || system == QCalendar::System::User))
return nullptr;
- {
- const auto registryLock = qt_scoped_lock(registryMutex);
- Q_ASSERT(calendarRegistry->byId.size() > size_t(system));
- if (auto *c = calendarRegistry->byId[size_t(system)])
- return c;
- }
- if (auto *result = backendFromEnum(system))
- return result;
- const auto registryLock = qt_scoped_lock(registryMutex);
- return calendarRegistry->byId[size_t(system)];
+
+ return calendarRegistry->fromEnum(system);
+}
+
+/*!
+ \internal
+ Returns backend for Gregorian calendar.
+
+ The backend is returned without locking the registry if possible.
+*/
+const QCalendarBackend *QCalendarBackend::gregorian()
+{
+ if (Q_UNLIKELY(calendarRegistry.isDestroyed()))
+ return nullptr;
+
+ return calendarRegistry->gregorian();
}
/*!
@@ -1001,21 +1140,58 @@ const QCalendarBackend *QCalendarBackend::fromEnum(QCalendar::System system)
This enumerated type is used to specify a choice of calendar system.
\value Gregorian The default calendar, used internationally.
- \value Julian An ancient Roman calendar with too few leap years.
+ \value Julian An ancient Roman calendar.
\value Milankovic A revised Julian calendar used by some Orthodox churches.
\value Jalali The Solar Hijri calendar (also called Persian).
\value IslamicCivil The (tabular) Islamic Civil calendar.
\omitvalue Last
\omitvalue User
+ \sa QCalendar, QCalendar::SystemId
+*/
+
+/*!
+ \class QCalendar::SystemId
+ \inmodule QtCore
+ \since 6.2
+
+ This is an opaque type used to identify custom calendar implementations. The
+ only supported source for values of this type is the backend's \c
+ calendarId() method. A value of this type whose isValid() is false does not
+ identify a successfully-registered backend. The only valid consumer of
+ values of this type is a QCalendar constructor, which will only produce a
+ valid QCalendar instance if the ID passed to it is valid.
+
+ \sa QCalendar, QCalendar::System
+*/
+
+/*!
+ \fn QCalendar::SystemId::isValid() const
+
+ Returns \c true if this is a valid calendar implementation identifier,
+ \c false otherwise.
+
\sa QCalendar
*/
/*!
+ \internal
+ \fn QCalendar::SystemId::SystemId()
+
+ Constructs an invalid calendar system identifier.
+*/
+
+/*!
+ \internal
+ \fn QCalendar::SystemId::index()
+
+ Returns the internal representation of the identifier.
+*/
+
+/*!
\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.
@@ -1025,30 +1201,23 @@ const QCalendarBackend *QCalendarBackend::fromEnum(QCalendar::System system)
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()
*/
QCalendar::QCalendar()
- : d(nullptr)
+ : d_ptr(QCalendarBackend::gregorian())
{
- if (calendarRegistry.isDestroyed())
- return;
- d = calendarRegistry->gregorianCalendar.loadAcquire();
- if (!d) {
- auto fresh = new QGregorianCalendar;
- if (!calendarRegistry->gregorianCalendar.testAndSetOrdered(fresh, fresh, d))
- delete fresh;
- Q_ASSERT(d);
- }
- Q_ASSERT(~d->calendarId());
+ Q_ASSERT(!d_ptr || d_ptr->calendarId().isValid());
}
-QCalendar::QCalendar(QCalendar::System system)
- : d(QCalendarBackend::fromEnum(system))
+QCalendar::QCalendar(QCalendar::System system) : d_ptr(QCalendarBackend::fromEnum(system))
{
// If system is valid, we should get a valid d for that system.
- Q_ASSERT(uint(system) > uint(QCalendar::System::Last)
- || (d && d->calendarId() == size_t(system)));
+ Q_ASSERT(!d_ptr || (uint(system) > uint(QCalendar::System::Last))
+ || (d_ptr->calendarId().index() == size_t(system)));
}
/*!
@@ -1062,22 +1231,16 @@ QCalendar::QCalendar(QCalendar::System system)
QCalendar using that backend. This can be useful when the backend is not
registered by name.
*/
-QCalendar::QCalendar(size_t id)
- : d(QCalendarBackend::fromId(id))
-{
- Q_ASSERT(!d || d->calendarId() == id);
-}
-
-QCalendar::QCalendar(QLatin1String name)
- : d(QCalendarBackend::fromName(name))
+QCalendar::QCalendar(QCalendar::SystemId id)
+ : d_ptr(QCalendarBackend::fromId(id))
{
- Q_ASSERT(!d || ~d->calendarId());
+ Q_ASSERT(!d_ptr || d_ptr->calendarId().index() == id.index());
}
-QCalendar::QCalendar(QStringView name)
- : d(QCalendarBackend::fromName(name))
+QCalendar::QCalendar(QAnyStringView name)
+ : d_ptr(QCalendarBackend::fromName(name))
{
- Q_ASSERT(!d || ~d->calendarId());
+ Q_ASSERT(!d_ptr || d_ptr->calendarId().isValid());
}
/*!
@@ -1102,6 +1265,7 @@ QCalendar::QCalendar(QStringView name)
*/
int QCalendar::daysInMonth(int month, int year) const
{
+ SAFE_D();
return d ? d->daysInMonth(month, year) : 0;
}
@@ -1112,6 +1276,7 @@ int QCalendar::daysInMonth(int month, int year) const
*/
int QCalendar::daysInYear(int year) const
{
+ SAFE_D();
return d ? d->daysInYear(year) : 0;
}
@@ -1125,6 +1290,7 @@ int QCalendar::daysInYear(int year) const
*/
int QCalendar::monthsInYear(int year) const
{
+ SAFE_D();
return d ? year == Unspecified ? d->maximumMonthsInYear() : d->monthsInYear(year) : 0;
}
@@ -1138,6 +1304,7 @@ int QCalendar::monthsInYear(int year) const
*/
bool QCalendar::isDateValid(int year, int month, int day) const
{
+ SAFE_D();
return d && d->isDateValid(year, month, day);
}
@@ -1149,8 +1316,8 @@ bool QCalendar::isDateValid(int year, int month, int day) const
*/
bool QCalendar::isGregorian() const
{
- Q_ASSERT(calendarRegistry.exists());
- return d == calendarRegistry->gregorianCalendar.loadRelaxed();
+ SAFE_D();
+ return d && d->isGregorian();
}
/*!
@@ -1164,6 +1331,7 @@ bool QCalendar::isGregorian() const
*/
bool QCalendar::isLeapYear(int year) const
{
+ SAFE_D();
return d && d->isLeapYear(year);
}
@@ -1174,6 +1342,7 @@ bool QCalendar::isLeapYear(int year) const
*/
bool QCalendar::isLunar() const
{
+ SAFE_D();
return d && d->isLunar();
}
@@ -1186,6 +1355,7 @@ bool QCalendar::isLunar() const
*/
bool QCalendar::isLuniSolar() const
{
+ SAFE_D();
return d && d->isLuniSolar();
}
@@ -1197,6 +1367,7 @@ bool QCalendar::isLuniSolar() const
*/
bool QCalendar::isSolar() const
{
+ SAFE_D();
return d && d->isSolar();
}
@@ -1211,6 +1382,7 @@ bool QCalendar::isSolar() const
*/
bool QCalendar::isProleptic() const
{
+ SAFE_D();
return d && d->isProleptic();
}
@@ -1241,6 +1413,7 @@ bool QCalendar::isProleptic() const
*/
bool QCalendar::hasYearZero() const
{
+ SAFE_D();
return d && d->hasYearZero();
}
@@ -1251,6 +1424,7 @@ bool QCalendar::hasYearZero() const
*/
int QCalendar::maximumDaysInMonth() const
{
+ SAFE_D();
return d ? d->maximumDaysInMonth() : 0;
}
@@ -1261,6 +1435,7 @@ int QCalendar::maximumDaysInMonth() const
*/
int QCalendar::minimumDaysInMonth() const
{
+ SAFE_D();
return d ? d->minimumDaysInMonth() : 0;
}
@@ -1271,6 +1446,7 @@ int QCalendar::minimumDaysInMonth() const
*/
int QCalendar::maximumMonthsInYear() const
{
+ SAFE_D();
return d ? d->maximumMonthsInYear() : 0;
}
@@ -1292,6 +1468,7 @@ int QCalendar::maximumMonthsInYear() const
*/
QDate QCalendar::dateFromParts(int year, int month, int day) const
{
+ SAFE_D();
qint64 jd;
return d && d->dateToJulianDay(year, month, day, &jd)
? QDate::fromJulianDay(jd) : QDate();
@@ -1303,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
@@ -1313,6 +1516,7 @@ QDate QCalendar::dateFromParts(const QCalendar::YearMonthDay &parts) const
*/
QCalendar::YearMonthDay QCalendar::partsFromDate(QDate date) const
{
+ SAFE_D();
return d && date.isValid() ? d->julianDayToDate(date.toJulianDay()) : YearMonthDay();
}
@@ -1327,6 +1531,7 @@ QCalendar::YearMonthDay QCalendar::partsFromDate(QDate date) const
*/
int QCalendar::dayOfWeek(QDate date) const
{
+ SAFE_D();
return d && date.isValid() ? d->dayOfWeek(date.toJulianDay()) : 0;
}
@@ -1354,6 +1559,7 @@ int QCalendar::dayOfWeek(QDate date) const
QString QCalendar::monthName(const QLocale &locale, int month, int year,
QLocale::FormatType format) const
{
+ SAFE_D();
const int maxMonth = year == Unspecified ? maximumMonthsInYear() : monthsInYear(year);
if (!d || month < 1 || month > maxMonth)
return QString();
@@ -1383,6 +1589,7 @@ QString QCalendar::monthName(const QLocale &locale, int month, int year,
QString QCalendar::standaloneMonthName(const QLocale &locale, int month, int year,
QLocale::FormatType format) const
{
+ SAFE_D();
const int maxMonth = year == Unspecified ? maximumMonthsInYear() : monthsInYear(year);
if (!d || month < 1 || month > maxMonth)
return QString();
@@ -1407,6 +1614,7 @@ QString QCalendar::standaloneMonthName(const QLocale &locale, int month, int yea
QString QCalendar::weekDayName(const QLocale &locale, int day,
QLocale::FormatType format) const
{
+ SAFE_D();
return d ? d->weekDayName(locale, day, format) : QString();
}
@@ -1429,6 +1637,7 @@ QString QCalendar::weekDayName(const QLocale &locale, int day,
QString QCalendar::standaloneWeekDayName(const QLocale &locale, int day,
QLocale::FormatType format) const
{
+ SAFE_D();
return d ? d->standaloneWeekDayName(locale, day, format) : QString();
}
@@ -1455,6 +1664,7 @@ QString QCalendar::dateTimeToString(QStringView format, const QDateTime &datetim
QDate dateOnly, QTime timeOnly,
const QLocale &locale) const
{
+ SAFE_D();
return d ? d->dateTimeToString(format, datetime, dateOnly, timeOnly, locale) : QString();
}
@@ -1471,3 +1681,7 @@ QStringList QCalendar::availableCalendars()
}
QT_END_NAMESPACE
+
+#ifndef QT_BOOTSTRAPPED
+#include "moc_qcalendar.cpp"
+#endif