[PATCH] Remove KDateTime limitation of 4712 BCE [v3].
Jon Severinsson
jon at severinsson.net
Tue Oct 9 20:36:45 UTC 2012
QDate in Qt5 now supports dates before 4712 BCE (new limit is 12626108195557530 BCE, well outside the limit of a four digit year).
---
> Hmm, did you remove too much documentation?
>
> This, for instance, still applies, doesn't it?
>
> [...]
>
> Only the stuff about the date range restriction should be removed.
And the stuff about Julian vs Proleptic Gregorian, but the paragraph you quoted should stay.
kdecore/date/kdatetime.cpp | 233 +++++++++++++--------------------------
kdecore/date/kdatetime.h | 63 +++--------
kdecore/tests/kdatetimetest.cpp | 11 +-
3 filer ändrade, 94 tillägg(+), 213 borttagningar(-)
diff --git a/kdecore/date/kdatetime.cpp b/kdecore/date/kdatetime.cpp
index df1fc3d..9dd1b26 100644
--- a/kdecore/date/kdatetime.cpp
+++ b/kdecore/date/kdatetime.cpp
@@ -70,16 +70,8 @@ static const char longMonth[][10] = {
"October", "November", "December"
};
-
-// The reason for the KDateTime being invalid, returned from KDateTime::fromString()
-enum Status {
- stValid = 0, // either valid, or really invalid
- stTooEarly // invalid (valid date before QDate range)
-};
-
-
static QDateTime fromStr(const QString& string, const QString& format, int& utcOffset,
- QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly, Status&);
+ QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly);
static int matchDay(const QString &string, int &offset, KCalendarSystem*);
static int matchMonth(const QString &string, int &offset, KCalendarSystem*);
static bool getUTCOffset(const QString &string, int &offset, bool colon, int &result);
@@ -89,10 +81,8 @@ static int findString_internal(const QString &string, const char *ptr, int count
template<int disp> static inline
int findString(const QString &string, const char array[][disp], int count, int &offset)
{ return findString_internal(string, array[0], count, offset, disp); }
-static QDate checkDate(int year, int month, int day, Status&);
-static const int MIN_YEAR = -4712; // minimum year which QDate allows
-static const int NO_NUMBER = 0x8000000; // indicates that no number is present in string conversion functions
+static const int NO_NUMBER = std::numeric_limits<int>::min(); // indicates that no number is present in string conversion functions
#ifdef COMPILING_TESTS
KDECORE_EXPORT int KDateTime_utcCacheHit = 0;
@@ -317,7 +307,6 @@ class KDateTimePrivate : public QSharedData
KDateTimePrivate()
: QSharedData(),
specType(KDateTime::Invalid),
- status(stValid),
utcCached(true),
convertedCached(false),
m2ndOccurrence(false),
@@ -329,7 +318,6 @@ class KDateTimePrivate : public QSharedData
: QSharedData(),
mDt(d),
specType(s.type()),
- status(stValid),
utcCached(false),
convertedCached(false),
m2ndOccurrence(false),
@@ -360,7 +348,6 @@ class KDateTimePrivate : public QSharedData
ut(rhs.ut),
converted(rhs.converted),
specType(rhs.specType),
- status(rhs.status),
utcCached(rhs.utcCached),
convertedCached(rhs.convertedCached),
m2ndOccurrence(rhs.m2ndOccurrence),
@@ -456,7 +443,6 @@ private:
} converted;
public:
KDateTime::SpecType specType : 4; // time spec type (N.B. need 3 bits + sign bit, since enums are signed on some platforms)
- Status status : 2; // reason for invalid status
mutable bool utcCached : 1; // true if 'ut' is valid
mutable bool convertedCached : 1; // true if 'converted' is valid
mutable bool m2ndOccurrence : 1; // this is the second occurrence of a time zone time
@@ -827,7 +813,6 @@ KDateTime &KDateTime::operator=(const KDateTime &other)
void KDateTime::detach() { d.detach(); }
bool KDateTime::isNull() const { return d->dt().isNull(); }
bool KDateTime::isValid() const { return d->specType != Invalid && d->dt().isValid(); }
-bool KDateTime::outOfRange() const { return d->status == stTooEarly; }
bool KDateTime::isDateOnly() const { return d->dateOnly(); }
bool KDateTime::isLocalZone() const { return d->specType == TimeZone && d->specZone == KSystemTimeZones::local(); }
bool KDateTime::isClockTime() const { return d->specType == ClockTime; }
@@ -1928,8 +1913,7 @@ KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *
}
}
}
- Status invalid = stValid;
- QDate qdate = checkDate(year, month+1, day, invalid); // convert date, and check for out-of-range
+ QDate qdate(year, month+1, day);
if (!qdate.isValid())
break;
KDateTime result(qdate, QTime(hour, minute, second), Spec(OffsetFromUTC, offset));
@@ -1949,12 +1933,6 @@ KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
break; // the time isn't the last second of the day
}
- if (invalid)
- {
- KDateTime dt; // date out of range - return invalid KDateTime ...
- dt.d->status = invalid; // ... with reason for error
- return dt;
- }
return result;
}
case RFC3339Date: // format is YYYY-MM-DDThh:mm:ss[.s]TZ
@@ -2112,15 +2090,14 @@ KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *
}
}
int month, day;
- Status invalid = stValid;
if (parts[3].length() == 3)
{
// A day of the year is specified
day = parts[3].toInt(&ok);
if (!ok || day < 1 || day > 366)
break;
- d = checkDate(year, 1, 1, invalid).addDays(day - 1); // convert date, and check for out-of-range
- if (!d.isValid() || (!invalid && d.year() != year))
+ d = QDate(year, 1, 1).addDays(day - 1);
+ if (!d.isValid() || (d.year() != year))
break;
day = d.day();
month = d.month();
@@ -2132,18 +2109,12 @@ KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *
day = parts[3].right(2).toInt(&ok1);
if (!ok || !ok1)
break;
- d = checkDate(year, month, day, invalid); // convert date, and check for out-of-range
+ d = QDate(year, month, day);
if (!d.isValid())
break;
}
if (dateOnly)
{
- if (invalid)
- {
- KDateTime dt; // date out of range - return invalid KDateTime ...
- dt.d->status = invalid; // ... with reason for error
- return dt;
- }
return KDateTime(d, Spec(ClockTime));
}
if (hour == 24 && !minute && !second && !msecs)
@@ -2159,12 +2130,6 @@ KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *
if (parts[8].isEmpty())
{
// No UTC offset is specified. Don't try to validate leap seconds.
- if (invalid)
- {
- KDateTime dt; // date out of range - return invalid KDateTime ...
- dt.d->status = invalid; // ... with reason for error
- return dt;
- }
return KDateTime(d, t, KDateTimePrivate::fromStringDefault());
}
int offset = 0;
@@ -2194,12 +2159,6 @@ KDateTime KDateTime::fromString(const QString &string, TimeFormat format, bool *
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
break; // the time isn't the last second of the day
}
- if (invalid)
- {
- KDateTime dt; // date out of range - return invalid KDateTime ...
- dt.d->status = invalid; // ... with reason for error
- return dt;
- }
return KDateTime(d, t, Spec(spec, offset));
}
case QtTextDate: // format is Wdy Mth DD [hh:mm:ss] YYYY [±hhmm]
@@ -2274,10 +2233,9 @@ KDateTime KDateTime::fromString(const QString &string, const QString &format,
{
int utcOffset = 0; // UTC offset in seconds
bool dateOnly = false;
- Status invalid = stValid;
QString zoneName;
QByteArray zoneAbbrev;
- QDateTime qdt = fromStr(string, format, utcOffset, zoneName, zoneAbbrev, dateOnly, invalid);
+ QDateTime qdt = fromStr(string, format, utcOffset, zoneName, zoneAbbrev, dateOnly);
if (!qdt.isValid())
return KDateTime();
if (zones)
@@ -2292,97 +2250,88 @@ KDateTime KDateTime::fromString(const QString &string, const QString &format,
zone = zones->zone(zoneName);
zname = true;
}
- else if (!invalid)
+ else if (!zoneAbbrev.isEmpty())
{
- if (!zoneAbbrev.isEmpty())
+ // A time zone abbreviation has been found.
+ // Use the time zone which contains it, if any, provided that the
+ // abbreviation applies at the specified date/time.
+ bool useUtcOffset = false;
+ const KTimeZones::ZoneMap z = zones->zones();
+ for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it)
{
- // A time zone abbreviation has been found.
- // Use the time zone which contains it, if any, provided that the
- // abbreviation applies at the specified date/time.
- bool useUtcOffset = false;
- const KTimeZones::ZoneMap z = zones->zones();
- for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it)
+ if (it.value().abbreviations().contains(zoneAbbrev))
{
- if (it.value().abbreviations().contains(zoneAbbrev))
+ int offset2;
+ int offset = it.value().offsetAtZoneTime(qdt, &offset2);
+ QDateTime ut(qdt);
+ ut.setTimeSpec(Qt::UTC);
+ ut.addSecs(-offset);
+ if (it.value().abbreviation(ut) != zoneAbbrev)
{
- int offset2;
- int offset = it.value().offsetAtZoneTime(qdt, &offset2);
- QDateTime ut(qdt);
- ut.setTimeSpec(Qt::UTC);
- ut.addSecs(-offset);
+ if (offset == offset2)
+ continue; // abbreviation doesn't apply at specified time
+ ut.addSecs(offset - offset2);
if (it.value().abbreviation(ut) != zoneAbbrev)
- {
- if (offset == offset2)
- continue; // abbreviation doesn't apply at specified time
- ut.addSecs(offset - offset2);
- if (it.value().abbreviation(ut) != zoneAbbrev)
- continue; // abbreviation doesn't apply at specified time
- offset = offset2;
- }
- // Found a time zone which uses this abbreviation at the specified date/time
- if (zone.isValid())
- {
- // Abbreviation is used by more than one time zone
- if (!offsetIfAmbiguous || offset != utcOffset)
- return KDateTime();
- useUtcOffset = true;
- }
- else
- {
- zone = it.value();
- utcOffset = offset;
- }
+ continue; // abbreviation doesn't apply at specified time
+ offset = offset2;
+ }
+ // Found a time zone which uses this abbreviation at the specified date/time
+ if (zone.isValid())
+ {
+ // Abbreviation is used by more than one time zone
+ if (!offsetIfAmbiguous || offset != utcOffset)
+ return KDateTime();
+ useUtcOffset = true;
+ }
+ else
+ {
+ zone = it.value();
+ utcOffset = offset;
}
}
- if (useUtcOffset)
- {
- zone = KTimeZone();
- if (!utcOffset)
- qdt.setTimeSpec(Qt::UTC);
- }
- else
- zname = true;
}
- else if (utcOffset || qdt.timeSpec() == Qt::UTC)
+ if (useUtcOffset)
+ {
+ zone = KTimeZone();
+ if (!utcOffset)
+ qdt.setTimeSpec(Qt::UTC);
+ }
+ else
+ zname = true;
+ }
+ else if (utcOffset || qdt.timeSpec() == Qt::UTC)
+ {
+ // A UTC offset has been found.
+ // Use the time zone which contains it, if any.
+ // For a date-only value, use the start of the day.
+ QDateTime dtUTC = qdt;
+ dtUTC.setTimeSpec(Qt::UTC);
+ dtUTC.addSecs(-utcOffset);
+ const KTimeZones::ZoneMap z = zones->zones();
+ for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it)
{
- // A UTC offset has been found.
- // Use the time zone which contains it, if any.
- // For a date-only value, use the start of the day.
- QDateTime dtUTC = qdt;
- dtUTC.setTimeSpec(Qt::UTC);
- dtUTC.addSecs(-utcOffset);
- const KTimeZones::ZoneMap z = zones->zones();
- for (KTimeZones::ZoneMap::ConstIterator it = z.constBegin(); it != z.constEnd(); ++it)
+ QList<int> offsets = it.value().utcOffsets();
+ if ((offsets.isEmpty() || offsets.contains(utcOffset))
+ && it.value().offsetAtUtc(dtUTC) == utcOffset)
{
- QList<int> offsets = it.value().utcOffsets();
- if ((offsets.isEmpty() || offsets.contains(utcOffset))
- && it.value().offsetAtUtc(dtUTC) == utcOffset)
+ // Found a time zone which uses this offset at the specified time
+ if (zone.isValid() || !utcOffset)
{
- // Found a time zone which uses this offset at the specified time
- if (zone.isValid() || !utcOffset)
- {
- // UTC offset is used by more than one time zone
- if (!offsetIfAmbiguous)
- return KDateTime();
- if (invalid)
- {
- KDateTime dt; // date out of range - return invalid KDateTime ...
- dt.d->status = invalid; // ... with reason for error
- return dt;
- }
- if (dateOnly)
- return KDateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset));
- qdt.setTimeSpec(Qt::LocalTime);
- return KDateTime(qdt, Spec(OffsetFromUTC, utcOffset));
- }
- zone = it.value();
+ // UTC offset is used by more than one time zone
+ if (!offsetIfAmbiguous)
+ return KDateTime();
+ if (dateOnly)
+ return KDateTime(qdt.date(), Spec(OffsetFromUTC, utcOffset));
+ qdt.setTimeSpec(Qt::LocalTime);
+ return KDateTime(qdt, Spec(OffsetFromUTC, utcOffset));
}
+ zone = it.value();
}
}
}
if (!zone.isValid() && zname)
return KDateTime(); // an unknown zone name or abbreviation was found
- if (zone.isValid() && !invalid)
+ if (zone.isValid())
{
if (dateOnly)
return KDateTime(qdt.date(), Spec(zone));
@@ -2391,12 +2340,6 @@ KDateTime KDateTime::fromString(const QString &string, const QString &format,
}
// No time zone match was found
- if (invalid)
- {
- KDateTime dt; // date out of range - return invalid KDateTime ...
- dt.d->status = invalid; // ... with reason for error
- return dt;
- }
KDateTime result;
if (utcOffset)
{
@@ -2474,9 +2417,8 @@ QDataStream & operator>>(QDataStream &s, KDateTime &kdt)
* utcOffset == 0, that indicates that no UTC offset was found.
*/
QDateTime fromStr(const QString& string, const QString& format, int& utcOffset,
- QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly, Status &status)
+ QString& zoneName, QByteArray& zoneAbbrev, bool& dateOnly)
{
- status = stValid;
QString str = string.simplified();
int year = NO_NUMBER;
int month = NO_NUMBER;
@@ -2796,10 +2738,10 @@ QDateTime fromStr(const QString& string, const QString& format, int& utcOffset,
year = KDateTime::currentLocalDate().year();
if (month == NO_NUMBER)
month = 1;
- QDate d = checkDate(year, month, (day > 0 ? day : 1), status); // convert date, and check for out-of-range
+ QDate d = QDate(year, month, (day > 0 ? day : 1));
if (!d.isValid())
return QDateTime();
- if (dayOfWeek != NO_NUMBER && !status)
+ if (dayOfWeek != NO_NUMBER)
{
if (day == NO_NUMBER)
{
@@ -3049,28 +2991,3 @@ int findString_internal(const QString &string, const char *array, int count, int
}
return -1;
}
-
-/*
- * Return the QDate for a given year, month and day.
- * If in error, check whether the reason is that the year is out of range.
- * If so, return a valid (but wrong) date but with 'status' set to the
- * appropriate error code. If no error, 'status' is set to stValid.
- */
-QDate checkDate(int year, int month, int day, Status &status)
-{
- status = stValid;
- QDate qdate(year, month, day);
- if (qdate.isValid())
- return qdate;
-
- // Invalid date - check whether it's simply out of range
- if (year < MIN_YEAR)
- {
- bool leap = (year % 4 == 0) && (year % 100 || year % 400 == 0);
- qdate.setDate((leap ? 2000 : 2001), month, day);
- if (qdate.isValid())
- status = stTooEarly;
- }
- return qdate;
-}
-
diff --git a/kdecore/date/kdatetime.h b/kdecore/date/kdatetime.h
index d0eb07d..6b6b893 100644
--- a/kdecore/date/kdatetime.h
+++ b/kdecore/date/kdatetime.h
@@ -52,12 +52,9 @@ class KDateTimeSpecPrivate;
* can also be set to represent a date-only value with no associated time.
*
* The class uses QDateTime internally to represent date/time values, and
- * therefore uses the Gregorian calendar for dates starting from 15 October 1582,
- * and the Julian calendar for dates up to 4 October 1582. The minimum year
- * number is -4712 (4713 BC), while the upper limit is more than 11,000,000. The
- * actual adoption of the Gregorian calendar after 1582 was slow; the last European
- * country to adopt it, Greece, did so only in 1923. See QDateTime Considerations
- * section below for further discussion of the date range limitations.
+ * therefore uses the Gregorian calendar retroactively. If you need the Julian
+ * calendar for historical dates (as commonly used prior to some date between 1582
+ * and 1923 depending on nation), please use @c KCalendarSystem and related classes.
*
* The time specification types which KDateTime supports are:
* - the UTC time zone
@@ -133,27 +130,6 @@ class KDateTimeSpecPrivate;
* QDateTime lacks virtual methods, KDateTime is not inherited from QDateTime,
* but instead is implemented using a private QDateTime object.
*
- * The date range restriction due to the use of QDateTime internally may at
- * first sight seem a design limitation. However, two factors should be
- * considered:
- *
- * - there are significant problems in the representation of dates before the
- * Gregorian calendar was adopted. The date of adoption of the Gregorian
- * calendar varied from place to place, and in the Julian calendar the
- * date of the new year varied so that in different places the year number
- * could differ by one. So any date/time system which attempted to represent
- * dates as actually used in history would be too specialized to belong to
- * the core KDE libraries. Date/time systems for scientific applications can
- * be much simpler, but may differ from historical records.
- *
- * - time zones were not invented until the middle of the 19th century. Before
- * that, solar time was used.
- *
- * Because of these issues, together with the fact that KDateTime's aim is to
- * provide automatic time zone handling for date/time values, QDateTime was
- * chosen as the basis for KDateTime. For those who need an extended date
- * range, other classes exist.
- *
* @section simulation Simulation Facility
*
* This class provides a facility to simulate the local system time, which
@@ -1291,19 +1267,13 @@ class KDECORE_EXPORT KDateTime //krazy:exclude=dpointer (implicitly shared)
* to RFCDate. Only set it to RFCDateDay if you want to return an error
* when the day of the week is omitted.
*
- * For @p format = ISODate or RFCDate[Day], if an invalid KDateTime is
- * returned, you can check why @p format was considered invalid by use of
- * outOfRange(). If that method returns true, it indicates that @p format
- * was in fact valid, but the date lies outside the range which can be
- * represented by QDate.
- *
* @param string string to convert
* @param format format code. LocalDate cannot be used here.
* @param negZero if non-null, the value is set to true if a UTC offset of
* '-0000' is found or, for RFC 2822 format, an unrecognised
* or invalid time zone abbreviation is found, else false.
* @return KDateTime value, or an invalid KDateTime if either parameter is invalid
- * @see setFromStringDefault(), toString(), outOfRange(), QString::fromString()
+ * @see setFromStringDefault(), toString(), QString::fromString()
*/
static KDateTime fromString(const QString &string, TimeFormat format = ISODate, bool *negZero = 0);
@@ -1427,11 +1397,6 @@ class KDECORE_EXPORT KDateTime //krazy:exclude=dpointer (implicitly shared)
* appears more than once but with different values, the weekday name does
* not tally with the date, an invalid KDateTime is returned.
*
- * If an invalid KDateTime is returned, you can check why @p format was
- * considered invalid by use of outOfRange(). If that method returns true,
- * it indicates that @p format was in fact valid, but the date lies outside
- * the range which can be represented by QDate.
- *
* @param string string to convert
* @param format format string
* @param zones time zone collection, or null for none
@@ -1442,7 +1407,7 @@ class KDECORE_EXPORT KDateTime //krazy:exclude=dpointer (implicitly shared)
* time zone information doesn't match any in @p zones, or if the
* time zone information is ambiguous and @p offsetIfAmbiguous is
* false
- * @see setFromStringDefault(), toString(), outOfRange()
+ * @see setFromStringDefault(), toString()
*/
static KDateTime fromString(const QString &string, const QString &format,
const KTimeZones *zones = 0, bool offsetIfAmbiguous = true);
@@ -1463,17 +1428,17 @@ class KDECORE_EXPORT KDateTime //krazy:exclude=dpointer (implicitly shared)
/**
- * Checks whether the date/time returned by the last call to fromString()
- * was invalid because an otherwise valid date was outside the range which
- * can be represented by QDate. This status occurs when fromString() read
- * a valid string containing a year earlier than -4712 (4713 BC). On exit
- * from fromString(), if outOfRange() returns @c true, isValid() will
- * return @c false.
+ * Always returns false, as dates earlier than -4712 are now supported by
+ * @c KDateTime.
*
- * @return @c true if date was earlier than -4712, else @c false
- * @see isValid(), fromString()
+ * @return @c false
+ * @see isValid()
+ * @deprecated since 5.0, we now supports all valid dates.
*/
- bool outOfRange() const;
+ inline bool outOfRange() const
+ {
+ return false;
+ }
/**
* Compare this instance with another to determine whether they are
diff --git a/kdecore/tests/kdatetimetest.cpp b/kdecore/tests/kdatetimetest.cpp
index 812abc5..5d6577b 100644
--- a/kdecore/tests/kdatetimetest.cpp
+++ b/kdecore/tests/kdatetimetest.cpp
@@ -3787,25 +3787,24 @@ void KDateTimeTest::strings_format()
QCOMPARE(dt.dateTime(), QDateTime(QDate(-4712,9,5), QTime(14,30,1,300), Qt::LocalTime));
QCOMPARE(dt.utcOffset(), 5*3600);
QVERIFY(dt.isValid());
- QVERIFY(!dt.outOfRange());
dt = KDateTime::fromString(QLatin1String("999909051430:01.3+0500"), QLatin1String("%Y%m%d%H%M%:S%:s%z"));
QCOMPARE(dt.dateTime(), QDateTime(QDate(9999,9,5), QTime(14,30,1,300), Qt::LocalTime));
QCOMPARE(dt.utcOffset(), 5*3600);
QVERIFY(dt.isValid());
- QVERIFY(!dt.outOfRange());
dt = KDateTime::fromString(QLatin1String("123456.09051430:01.3+0500"), QLatin1String("%:Y.%m%d%H%M%:S%:s%z"));
QCOMPARE(dt.dateTime(), QDateTime(QDate(123456,9,5), QTime(14,30,1,300), Qt::LocalTime));
QCOMPARE(dt.utcOffset(), 5*3600);
QVERIFY(dt.isValid());
- QVERIFY(!dt.outOfRange());
s = dt.toString(QLatin1String("%Y"));
QCOMPARE(s, QString::fromLatin1("123456"));
- dt = KDateTime::fromString(QLatin1String("-471412311430:01.3+0500"), QLatin1String("%Y%m%d%H%M%:S%:s%z"));
- QVERIFY(!dt.isValid()); // too early
- QVERIFY(dt.outOfRange());
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+ dt = KDateTime::fromString(QLatin1String("-471411231430:01.3+0500"), QLatin1String("%Y%m%d%H%M%:S%:s%z"));
+ QVERIFY(dt.isValid());
+ QVERIFY(dt.date().toJulianDay() == -1);
+#endif
dtutc = KDateTime::fromString(QLatin1String("2000-01-01T00:00:00.000+0000"), QLatin1String("%Y-%m-%dT%H:%M%:S%:s%z"));
QVERIFY(dtutc.isValid());
--
1.7.10.4
More information about the Kde-frameworks-devel
mailing list