diff options
Diffstat (limited to 'src/corelib/time/qcalendarmath_p.h')
-rw-r--r-- | src/corelib/time/qcalendarmath_p.h | 152 |
1 files changed, 110 insertions, 42 deletions
diff --git a/src/corelib/time/qcalendarmath_p.h b/src/corelib/time/qcalendarmath_p.h index 61c2c5d2b5..c785803ce3 100644 --- a/src/corelib/time/qcalendarmath_p.h +++ b/src/corelib/time/qcalendarmath_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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) 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 #ifndef QCALENDARMATH_P_H #define QCALENDARMATH_P_H @@ -52,26 +16,130 @@ // #include <QtCore/private/qglobal_p.h> +#include <QtCore/QtAlgorithms> QT_BEGIN_NAMESPACE namespace QRoundingDown { +// Note: qgregoriancalendar.cpp contains some static asserts to verify this all works. +namespace QRoundingDownPrivate { +#ifdef Q_CC_MSVC +// MSVC 2019 doesn't believe in the constexpr-ness of the #else clause's version :-( +#define QCALMATH_ISPOW2(b) ((b > 0) && !(b & (b - 1))) // See #else's comment. +#else +// Subtracting one toggles the least significant set bit and any unset bits less +// significant than it, leaving other bits unchanged. Thus the & of this with +// the original number preserves all more significant bits, clearing the least +// significant. If there are no such bits, either our number was 0 or it only +// had one bit set, hence is a power of two. +template <typename Int> +inline constexpr bool isPowerOfTwo(Int b) { return b > 0 && (b & (b - 1)) == 0; } +#define QCALMATH_ISPOW2(b) QRoundingDownPrivate::isPowerOfTwo(b) +#endif +} /* Division, rounding down (rather than towards zero). From C++11 onwards, integer division is defined to round towards zero, so we can rely on that when implementing this. This is only used with denominator b > 0, so we only have to treat negative numerator, a, specially. + + If a is a multiple of b, adding 1 before and subtracting it after dividing by + b gets us to where we should be (albeit by an eccentric path), since the + adding caused rounding up, undone by the subtracting. Otherwise, adding 1 + doesn't change the result of dividing by b; and we want one less than that + result. This is equivalent to subtracting b - 1 and simply dividing, except + when that subtraction would underflow. + + For the remainder, with negative a, aside from having to add one and subtract + it later to deal with the exact multiples, we can simply use the truncating + remainder and then add b. When b is a power of two we can, of course, get the + remainder correctly by the same masking that works for positive a. */ -template<typename Int> constexpr Int qDiv(Int a, unsigned b) -{ return (a - (a < 0 ? int(b - 1) : 0)) / int(b); } +// Fall-back, to ensure intelligible error messages on mis-use: +template <unsigned b, typename Int, std::enable_if_t<(int(b) < 2), bool> = true> +constexpr auto qDivMod(Int) +{ + static_assert(b, "Division by 0 is undefined"); + // Use complement of earlier cases || new check, to ensure only one error: + static_assert(!b || int(b) > 0, "Denominator is too big"); + static_assert(int(b) < 1 || b > 1, "Division by 1 is fautous"); + struct R { Int quotient; Int remainder; }; + return R { 0, 0 }; +} + +template <unsigned b, typename Int, + std::enable_if_t<(b > 1) && !QCALMATH_ISPOW2(b) && (int(b) > 0), + bool> = true> +constexpr auto qDivMod(Int a) +{ + struct R { Int quotient; Int remainder; }; + if constexpr (std::is_signed_v<Int>) { + if (a < 0) { + ++a; // can't overflow, it's negative + return R { Int(a / int(b) - 1), Int(a % int(b) - 1 + int(b)) }; + } + } + return R { Int(a / int(b)), Int(a % int(b)) }; +} + +template <unsigned b, typename Int, + std::enable_if_t<(b > 1) && QCALMATH_ISPOW2(b) && (int(b) > 0), + bool> = true> +constexpr auto qDivMod(Int a) +{ + constexpr unsigned w = QtPrivate::qConstexprCountTrailingZeroBits(b); + struct R { Int quotient; Int remainder; }; + if constexpr (std::is_signed_v<Int>) { + if (a < 0) + return R { Int((a + 1) / int(b) - 1), Int(a & int(b - 1)) }; + } + return R { Int(a >> w), Int(a & int(b - 1)) }; +} -template<typename Int> constexpr Int qMod(Int a, unsigned b) -{ return a - qDiv(a, b) * b; } +#undef QCALMATH_ISPOW2 +// </kludge> + +template <unsigned b, typename Int> constexpr Int qDiv(Int a) { return qDivMod<b>(a).quotient; } +template <unsigned b, typename Int> constexpr Int qMod(Int a) { return qDivMod<b>(a).remainder; } } // QRoundingDown +namespace QRomanCalendrical { +// Julian Day number of Gregorian 1 BCE, February 29th: +constexpr qint64 LeapDayGregorian1Bce = 1721119; +// Aside from (maybe) some turns of centuries, one year in four is leap: +constexpr unsigned FourYears = 4 * 365 + 1; +constexpr unsigned FiveMonths = 31 + 30 + 31 + 30 + 31; // Mar-Jul or Aug-Dec. + +constexpr auto yearMonthToYearDays(int year, int month) +{ + // Pre-digests year and month to (possibly denormal) year count and day-within-year. + struct R { qint64 year; qint64 days; }; + if (year < 0) // Represent -N BCE as 1-N so year numbering is contiguous. + ++year; + month -= 3; // Adjust month numbering so March = 0, ... + if (month < 0) { // and Jan = 10, Feb = 11, in the previous year. + --year; + month += 12; + } + return R { year, QRoundingDown::qDiv<5>(FiveMonths * month + 2) }; +} + +constexpr auto dayInYearToYmd(int dayInYear) +{ + // The year is an adjustment to the year for which dayInYear may be denormal. + struct R { int year; int month; int day; }; + // Shared code for Julian and Milankovic (at least). + using namespace QRoundingDown; + const auto month5Day = qDivMod<FiveMonths>(5 * dayInYear + 2); + // Its remainder changes by 5 per day, except at roughly monthly quotient steps. + const auto yearMonth = qDivMod<12>(month5Day.quotient + 2); + return R { yearMonth.quotient, yearMonth.remainder + 1, qDiv<5>(month5Day.remainder) + 1 }; +} +} + QT_END_NAMESPACE #endif // QCALENDARMATH_P_H |