[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-core-devel
Subject: PATCH: time-only support for KDateTime (used in Nepomuk-KDE)
From: Sebastian =?iso-8859-1?q?Tr=FCg?= <strueg () mandriva ! com>
Date: 2007-03-15 12:02:01
Message-ID: 200703151302.02166.strueg () mandriva ! com
[Download RAW message or body]
Hi folks,
Aaron asked me if KDateTime already provided the date and time conversion
functionality from KMetaData::DateTime. Actually the ISO8601 support in
KDateTime is nearly perfect. The only thing missing is the support for
time-only instances and parsing of date- and time-only strings like
YYYY-MM-DD
the attached patch does that. However, it breaks one unit test: with my patch
KDateTime does create date-only ISO8601 strings like the one above which is
perfectly valid as far as I understood ISO8601. The current implementation
appends the SOD time (00:00:00.000) to a date-only string. This would not be
that big a problem for Nepomuk-KDE but just a little ugly IMHO.
Apart from the issue just described nothing really changes. So how about this?
Cheers,
Sebastian
["kdatetime-timeonly.diff" (text/x-diff)]
Index: kdatetime.cpp
===================================================================
--- kdatetime.cpp (revision 641810)
+++ kdatetime.cpp (working copy)
@@ -1,6 +1,7 @@
/*
This file is part of the KDE libraries
Copyright (c) 2005,2006 David Jarvie <software@astrojar.org.uk>
+ Copyright (c) 2007 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
@@ -320,7 +321,8 @@
status(stValid),
utcCached(true),
m2ndOccurrence(false),
- mDateOnly(false)
+ mDateOnly(false),
+ mTimeOnly(false)
{
converted.tz = 0; // flag time zone conversion as invalid
}
@@ -332,8 +334,11 @@
status(stValid),
utcCached(false),
m2ndOccurrence(false),
- mDateOnly(donly)
+ mDateOnly(donly),
+ mTimeOnly(false)
{
+ if (!mDt.date().isValid())
+ setTimeOnly(true);
switch (specType)
{
case KDateTime::TimeZone:
@@ -364,6 +369,7 @@
utcCached(rhs.utcCached),
m2ndOccurrence(rhs.m2ndOccurrence),
mDateOnly(rhs.mDateOnly),
+ mTimeOnly(rhs.mTimeOnly),
converted2ndOccur(rhs.converted2ndOccur)
{}
@@ -373,14 +379,16 @@
KDateTime::Spec spec() const;
QDateTime utc() const { return QDateTime(ut.date, ut.time, \
Qt::UTC); } bool dateOnly() const { return mDateOnly; }
+ bool timeOnly() const { return mTimeOnly; }
bool secondOccurrence() const { return m2ndOccurrence; }
void setDt(const QDateTime &dt) { mDt = dt; utcCached = m2ndOccurrence \
= false; } void setDtFromUtc(const QDateTime &utcdt);
- void setDate(const QDate &d) { mDt.setDate(d); utcCached = \
m2ndOccurrence = false; } + void setDate(const QDate &d) { \
mDt.setDate(d); utcCached = mTimeOnly = m2ndOccurrence = false; }
void setTime(const QTime &t) { mDt.setTime(t); utcCached = mDateOnly \
= m2ndOccurrence = false; }
void setDtTimeSpec(Qt::TimeSpec s) { mDt.setTimeSpec(s); utcCached = \
m2ndOccurrence = false; } void setSpec(const KDateTime::Spec&);
void setDateOnly(bool d);
+ void setTimeOnly(bool d);
int timeZoneOffset() const;
QDateTime toUtc(const KTimeZone *local = 0) const;
QDateTime toZone(const KTimeZone *zone, const KTimeZone *local = 0) const;
@@ -429,6 +437,7 @@
static QTime sod; // start of day (00:00:00)
+ static QDate sot; // start of time (01.01.0000)
/* Because some applications create thousands of instances of KDateTime, this
* data structure is designed to minimize memory usage. Ensure that all small
@@ -462,11 +471,13 @@
mutable bool m2ndOccurrence : 1; // this is the second occurrence of \
a time zone time private:
bool mDateOnly : 1; // true to ignore the time part
+ bool mTimeOnly : 1; // true to ignore the date parts
mutable bool converted2ndOccur : 1; // this is the second occurrence of \
'converted' time };
QTime KDateTimePrivate::sod(0,0,0);
+QDate KDateTimePrivate::sot(1,1,1);
KDateTime::Spec KDateTimePrivate::spec() const
{
@@ -551,6 +562,16 @@
}
}
+void KDateTimePrivate::setTimeOnly(bool timeOnly)
+{
+ if (timeOnly != mTimeOnly)
+ {
+ mTimeOnly = timeOnly;
+ if (timeOnly && mDt.date() != sot)
+ mDt.setDate(sot);
+ }
+}
+
/* Sets the date/time to a given UTC date/time. The time spec is not changed. */
void KDateTimePrivate::setDtFromUtc(const QDateTime &utcdt)
{
@@ -736,6 +757,7 @@
newd->specType = KDateTime::TimeZone;
newd->utcCached = utcCached;
newd->mDateOnly = mDateOnly;
+ newd->mTimeOnly = mTimeOnly;
newd->m2ndOccurrence = converted2ndOccur;
switch (specType)
{
@@ -771,6 +793,13 @@
d->setDtTimeSpec(Qt::UTC);
}
+KDateTime::KDateTime(const QTime &time, const Spec &spec)
+: d(new KDateTimePrivate(QDateTime(QDate(), time, Qt::LocalTime), spec, false))
+{
+ if (spec.type() == UTC)
+ d->setDtTimeSpec(Qt::UTC);
+}
+
KDateTime::KDateTime(const QDate &date, const QTime &time, const Spec &spec)
: d(new KDateTimePrivate(QDateTime(date, time, Qt::LocalTime), spec))
{
@@ -816,6 +845,7 @@
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::isTimeOnly() const { return \
d->timeOnly(); } bool KDateTime::isLocalZone() const { return \
d->specType == TimeZone && d->z.tz == KSystemTimeZones::local(); } bool \
KDateTime::isClockTime() const { return d->specType == ClockTime; } bool \
KDateTime::isUtc() const { return d->specType == UTC || (d->specType == \
OffsetFromUTC && d->z.utcOffset == 0); } @@ -1101,10 +1131,17 @@
{
QDateTime qdt = d->dt();
qdt.setTimeSpec(Qt::UTC); // set time as UTC to avoid daylight savings \
adjustments in addSecs()
- qdt = qdt.addDays(days).addSecs(seconds);
+ if (d->timeOnly())
+ qdt = qdt.addSecs(seconds);
+ else
+ qdt = qdt.addDays(days).addSecs(seconds);
qdt.setTimeSpec(Qt::LocalTime);
return KDateTime(qdt, Spec(ClockTime));
}
+ if (d->timeOnly())
+ {
+ return KDateTime(d->toUtc().addSecs(seconds), d->spec());
+ }
return KDateTime(d->toUtc().addDays(days).addSecs(seconds), d->spec());
}
@@ -1169,6 +1206,8 @@
{
if (!isValid() || !t2.isValid())
return 0;
+ if (d->timeOnly() || t2.d->timeOnly())
+ return 0;
if (d->dateOnly())
{
QDate dat = t2.d->dateOnly() ? t2.d->date() : \
t2.toTimeSpec(d->spec()).d->date(); @@ -1231,6 +1270,9 @@
KDateTime::Comparison KDateTime::compare(const KDateTime &other) const
{
+ if (d->timeOnly() != other.d->timeOnly())
+ return Impossible;
+
QDateTime start1, start2;
bool conv = (!d->equalSpec(*other.d) || d->secondOccurrence() != \
other.d->secondOccurrence()); if (conv)
@@ -1305,6 +1347,8 @@
return true; // the two instances share the same data
if (d->dateOnly() != other.d->dateOnly())
return false;
+ if (d->timeOnly() != other.d->timeOnly())
+ return false;
if (d->equalSpec(*other.d))
{
// Both instances are in the same time zone, so compare directly
@@ -1450,6 +1494,15 @@
num = d->dt().time().second();
numLength = 2;
break;
+ case 'f':
+ if (d->dt().time().msec())
+ {
+ // Comma is preferred by ISO8601 as the decimal point \
symbol, + // so use it unless '.' is the symbol used in this \
locale. + result += (KGlobal::locale()->decimalSymbol() == \
QLatin1String(".")) ? QLatin1Char('.') : QLatin1Char(','); + \
result += s.sprintf("%03d", d->dt().time().msec()); + }
+ break;
case 'P': // am/pm
{
bool am = (d->dt().time().hour() < 12);
@@ -1652,11 +1705,14 @@
year = -year;
}
QString s;
- result += s.sprintf("%04d-%02d-%02d",
- year, d->date().month(), d->date().day());
- if (!d->dateOnly() || d->specType != ClockTime)
+ if (!d->timeOnly())
+ result += s.sprintf("%04d-%02d-%02d",
+ year, d->date().month(), d->date().day());
+ if (!d->dateOnly())
{
- result += s.sprintf("T%02d:%02d:%02d",
+ if (!d->timeOnly())
+ result += 'T';
+ result += s.sprintf("%02d:%02d:%02d",
d->dt().time().hour(), d->dt().time().minute(), \
d->dt().time().second()); if (d->dt().time().msec())
{
@@ -1664,10 +1720,15 @@
// so use it unless '.' is the symbol used in this locale.
result += (KGlobal::locale()->decimalSymbol() == \
QLatin1String(".")) ? QLatin1Char('.') : QLatin1Char(','); result += \
s.sprintf("%03d", d->dt().time().msec()); + // The fraction cannot \
end with a 0 + while (result.endsWith("0"))
+ result.truncate(result.length()-1);
}
+ if (d->specType == UTC)
+ return result + QLatin1Char('Z');
}
- if (d->specType == UTC)
- return result + QLatin1Char('Z');
+ else
+ return result;
if (d->specType == ClockTime)
return result;
tzcolon = ":";
@@ -1888,6 +1949,7 @@
* to those interchanging the data to agree on a scheme.
*/
bool dateOnly = false;
+ bool timeOnly = false;
// Check first for the extended format of ISO 8601
QRegExp rx("^([+-])?(\\d{4,})-(\\d\\d\\d|\\d\\d-\\d\\d)[T \
](\\d\\d)(?::(\\d\\d)(?::(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(?::(\\d\\d))?)?$");
if (str.indexOf(rx))
@@ -1910,7 +1972,16 @@
{
rx = QRegExp("^([+-])?(\\d{4})(\\d{3})$");
if (str.indexOf(rx))
- break;
+ {
+ dateOnly = false;
+ timeOnly = true;
+ // time only
+ rx = \
QRegExp("^(\\d\\d)(?::(\\d\\d)(?::(\\d\\d)(?:(?:\\.|,)(\\d+))?)?)?(Z|([+-])(\\d\\d)(?::(\\d\\d))?)?$");
+ if (str.indexOf(rx))
+ {
+ break;
+ }
+ }
}
}
}
@@ -1924,65 +1995,72 @@
int second = 0;
int msecs = 0;
bool leapSecond = false;
- int year = parts[2].toInt(&ok);
- if (!ok)
- break;
+ int year = 0;
+ int i = 1;
+ if (!timeOnly) {
+ i += 3;
+ year = parts[2].toInt(&ok);
+ if (!ok)
+ break;
+ }
if (parts[1] == QLatin1String("-"))
year = -year;
if (!dateOnly)
{
- hour = parts[4].toInt(&ok);
+ hour = parts[i].toInt(&ok);
if (!ok)
break;
- if (!parts[5].isEmpty())
+ if (!parts[i+1].isEmpty())
{
- minute = parts[5].toInt(&ok);
+ minute = parts[i+1].toInt(&ok);
if (!ok)
break;
}
- if (!parts[6].isEmpty())
+ if (!parts[i+2].isEmpty())
{
- second = parts[6].toInt(&ok);
+ second = parts[i+2].toInt(&ok);
if (!ok)
break;
}
leapSecond = (second == 60);
if (leapSecond)
second = 59; // apparently a leap second - validate below, \
once time zone is known
- if (!parts[7].isEmpty())
+ if (!parts[i+3].isEmpty())
{
- QString ms = parts[7] + QLatin1String("00");
+ QString ms = parts[i+3] + QLatin1String("00");
ms.truncate(3);
msecs = ms.toInt(&ok);
if (!ok)
break;
}
}
- 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))
- break;
- day = d.day();
- month = d.month();
+ if (!timeOnly) {
+ int month, day;
+ 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)) + break;
+ day = d.day();
+ month = d.month();
+ }
+ else
+ {
+ // A month and day are specified
+ month = parts[3].left(2).toInt(&ok);
+ 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 + if (!d.isValid())
+ break;
+ }
}
- else
- {
- // A month and day are specified
- month = parts[3].left(2).toInt(&ok);
- 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
- if (!d.isValid())
- break;
- }
if (dateOnly)
{
if (invalid)
@@ -1993,7 +2071,7 @@
}
return KDateTime(d, Spec(ClockTime));
}
- if (hour == 24 && !minute && !second && !msecs)
+ if (!timeOnly && hour == 24 && !minute && !second && !msecs)
{
// A time of 24:00:00 is allowed by ISO 8601, and means midnight at \
the end of the day d = d.addDays(1);
@@ -2003,7 +2081,7 @@
QTime t(hour, minute, second, msecs);
if (!t.isValid())
break;
- if (parts[8].isEmpty())
+ if (parts[i+4].isEmpty())
{
// No UTC offset is specified. Don't try to validate leap seconds.
if (invalid)
@@ -2015,19 +2093,19 @@
return KDateTime(d, t, KDateTimePrivate::fromStringDefault());
}
int offset = 0;
- SpecType spec = (parts[8] == QLatin1String("Z")) ? UTC : OffsetFromUTC;
+ SpecType spec = (parts[i+4] == QLatin1String("Z")) ? UTC : \
OffsetFromUTC; if (spec == OffsetFromUTC)
{
- offset = parts[10].toInt(&ok) * 3600;
+ offset = parts[i+6].toInt(&ok) * 3600;
if (!ok)
break;
- if (!parts[11].isEmpty())
+ if (!parts[i+7].isEmpty())
{
- offset += parts[11].toInt(&ok) * 60;
+ offset += parts[i+7].toInt(&ok) * 60;
if (!ok)
break;
}
- if (parts[9] == QLatin1String("-"))
+ if (parts[i+5] == QLatin1String("-"))
{
offset = -offset;
if (!offset && negZero)
Index: kdatetime.h
===================================================================
--- kdatetime.h (revision 641810)
+++ kdatetime.h (working copy)
@@ -1,6 +1,7 @@
/*
This file is part of the KDE libraries
Copyright (c) 2005,2006 David Jarvie <software@astrojar.org.uk>
+ Copyright (c) 2007 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
@@ -48,7 +49,8 @@
* The class KDateTime combines a date and time with support for an
* associated time zone or UTC offset. When manipulating KDateTime objects,
* their time zones or UTC offsets are automatically taken into account. KDateTime
- * can also be set to represent a date-only value with no associated time.
+ * can also be set to represent a date-only value with no associated time or
+ * a time-only value with no associated date.
*
* The class uses QDateTime internally to represent date/time values, and
* therefore can only be used for dates in the Gregorian calendar, with a year
@@ -460,6 +462,9 @@
*/
enum Comparison
{
+ Impossible = 0x0, /**< A proper comparision is not possible. This happens \
if one + * of the instances is time-only but the other \
one is not. + */
Before = 0x01, /**< This KDateTime is strictly earlier than the other,
* i.e. e1 < s2.
*/
@@ -524,6 +529,25 @@
explicit KDateTime(const QDate &date, const Spec &spec = Spec(LocalZone));
/**
+ * Constructs a time-only value expressed in a given time specification. The
+ * date is set to 01.01.0001
+ *
+ * The instance is initialised according to the time specification type of
+ * @p spec as follows:
+ * - @c UTC : date is stored as UTC.
+ * - @c OffsetFromUTC : date is a local time at the specified offset
+ * from UTC.
+ * - @c TimeZone : date is a local time in the specified time zone.
+ * - @c LocalZone : date is a local date in the current system time
+ * zone.
+ * - @c ClockTime : time zones are ignored.
+ *
+ * @param time time in the time zone indicated by @p spec
+ * @param spec time specification
+ */
+ explicit KDateTime(const QTime &time, const Spec &spec = Spec(LocalZone));
+
+ /**
* Constructs a date/time expressed as specified by @p spec.
*
* @p date and @p time are interpreted and stored according to the value of
@@ -611,6 +635,13 @@
bool isDateOnly() const;
/**
+ * Returns if the instance represents a date/time or a time-only value.
+ *
+ * @return @c true if time-only, @c false if date and time
+ */
+ bool isTimeOnly() const;
+
+ /**
* Returns the date part of the date/time. The value returned should be
* interpreted in terms of the instance's time zone or UTC offset.
*
@@ -1167,6 +1198,8 @@
* - %S seconds (00 - 59)
* - %:S seconds preceded with ':', but omitted if seconds value is zero
* - %:s milliseconds, 3 digits (000 - 999)
+ * - %f fraction of seconds (as used by XML Schema Part 2: Datatypes Second \
Edition: + * http://www.w3.org/TR/xmlschema-2)
* - %P "am" or "pm" in the current locale, or if undefined there, in English
* - %p "AM" or "PM" in the current locale, or if undefined there, in English
* - %:P "am" or "pm"
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic