[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