File indexing completed on 2024-04-21 03:52:54

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 2009 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
0005   SPDX-FileContributor: Alvaro Manera <alvaro.manera@nokia.com>
0006 
0007   SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 #include "sorting.h"
0010 
0011 // PENDING(kdab) Review
0012 // The QString::compare() need to be replace by a DUI string comparisons.
0013 // See http://qt.gitorious.org/maemo-6-ui-framework/libdui
0014 // If not compiled in "meego-mode" should we be using locale compares?
0015 
0016 using namespace KCalendarCore;
0017 
0018 /**
0019  * How one QDateTime compares with another.
0020  *
0021  * If any all-day events are involved, comparison of QDateTime values
0022  * requires them to be considered as representing time periods. An all-day
0023  * instance represents a time period from 00:00:00 to 23:59:59.999 on a given
0024  * date, while a date/time instance can be considered to represent a time
0025  * period whose start and end times are the same. They may therefore be
0026  * earlier or later, or may overlap or be contained one within the other.
0027  *
0028  * Values may be OR'ed with each other in any combination of 'consecutive'
0029  * intervals to represent different types of relationship.
0030  *
0031  * In the descriptions of the values below,
0032  * - s1 = start time of first instance
0033  * - e1 = end time of first instance
0034  * - s2 = start time of second instance
0035  * - e2 = end time of second instance.
0036  */
0037 enum DateTimeComparison {
0038     Before = 0x01, /**< The first QDateTime is strictly earlier than the second,
0039                     *   i.e. e1 < s2.
0040                     */
0041     AtStart = 0x02, /**< The first QDateTime starts at the same time as the second,
0042                      *   and ends before the end of the second,
0043                      *   i.e. s1 = s2, e1 < e2.
0044                      */
0045     Inside = 0x04, /**< The first QDateTime starts after the start of the second,
0046                     *   and ends before the end of the second,
0047                     *   i.e. s1 > s2, e1 < e2.
0048                     */
0049     AtEnd = 0x08, /**< The first QDateTime starts after the start of the second,
0050                    *   and ends at the same time as the second,
0051                    *   i.e. s1 > s2, e1 = e2.
0052                    */
0053     After = 0x10, /**< The first QDateTime is strictly later than the second,
0054                    *   i.e. s1 > e2.
0055                    */
0056     Equal = AtStart | Inside | AtEnd,
0057     /**< Simultaneous, i.e. s1 = s2 && e1 = e2.
0058      */
0059     Outside = Before | AtStart | Inside | AtEnd | After,
0060     /**< The first QDateTime starts before the start of the other,
0061      *   and ends after the end of the other,
0062      *   i.e. s1 < s2, e1 > e2.
0063      */
0064     StartsAt = AtStart | Inside | AtEnd | After,
0065     /**< The first QDateTime starts at the same time as the other,
0066      *   and ends after the end of the other,
0067      *   i.e. s1 = s2, e1 > e2.
0068      */
0069     EndsAt = Before | AtStart | Inside | AtEnd,
0070     /**< The first QDateTime starts before the start of the other,
0071      *   and ends at the same time as the other,
0072      *   i.e. s1 < s2, e1 = e2.
0073      */
0074 };
0075 
0076 /**
0077     * Compare two QDateTime instances to determine whether they are
0078     * simultaneous, earlier or later.
0079 
0080     * The comparison takes time zones into account: if the two instances have
0081     * different time zones, they are first converted to UTC before comparing.
0082     *
0083     * If both instances are not all-day values, the first instance is considered to
0084     * be either simultaneous, earlier or later, and does not overlap.
0085     *
0086     * If one instance is all-day and the other is a not all-day, the first instance
0087     * is either strictly earlier, strictly later, or overlaps.
0088     *
0089     * If both instance are all-day, they are considered simultaneous if both
0090     * their start of day and end of day times are simultaneous with each
0091     * other. (Both start and end of day times need to be considered in case a
0092     * daylight savings change occurs during that day.) Otherwise, the first instance
0093     * can be strictly earlier, earlier but overlapping, later but overlapping,
0094     * or strictly later.
0095     *
0096     * Note that if either instance is a local  time (Qt::TimeSpec of QTimeZone::LocalTime),
0097     * the result cannot be guaranteed to be correct, since by definition they
0098     * contain no information about time zones or daylight savings changes.
0099     *
0100     * @return DateTimeComparison indicating the relationship of dt1 to dt2
0101     * @see operator==(), operator!=(), operator<(), operator<=(), operator>=(), operator>()
0102     */
0103 
0104 DateTimeComparison compare(const QDateTime &dt1, bool isAllDay1, const QDateTime &dt2, bool isAllDay2)
0105 {
0106     QDateTime start1;
0107     QDateTime start2;
0108     // FIXME When secondOccurrence is available in QDateTime
0109     // const bool conv = (!d->equalSpec(*other.d) || d->secondOccurrence() != other.d->secondOccurrence());
0110     const bool conv = dt1.timeSpec() != dt2.timeSpec() || (dt1.timeSpec() == Qt::OffsetFromUTC && dt1.offsetFromUtc() != dt2.offsetFromUtc())
0111         || (dt1.timeSpec() == Qt::TimeZone && dt1.timeZone() != dt2.timeZone());
0112     if (conv) {
0113         // Different time specs or one is a time which occurs twice,
0114         // so convert to UTC before comparing
0115         start1 = dt1.toUTC();
0116         start2 = dt2.toUTC();
0117     } else {
0118         // Same time specs, so no need to convert to UTC
0119         start1 = dt1;
0120         start2 = dt2;
0121     }
0122     if (isAllDay1 || isAllDay2) {
0123         // At least one of the instances is date-only, so we need to compare
0124         // time periods rather than just times.
0125         QDateTime end1;
0126         QDateTime end2;
0127         if (conv) {
0128             if (isAllDay1) {
0129                 QDateTime dt(dt1);
0130                 dt.setTime(QTime(23, 59, 59, 999));
0131                 end1 = dt.toUTC();
0132             } else {
0133                 end1 = start1;
0134             }
0135             if (isAllDay2) {
0136                 QDateTime dt(dt2);
0137                 dt.setTime(QTime(23, 59, 59, 999));
0138                 end2 = dt.toUTC();
0139             } else {
0140                 end2 = start2;
0141             }
0142         } else {
0143             if (isAllDay1) {
0144                 end1 = QDateTime(dt1.date(), QTime(23, 59, 59, 999), QTimeZone::LocalTime);
0145             } else {
0146                 end1 = dt1;
0147             }
0148             if (isAllDay2) {
0149                 end2 = QDateTime(dt2.date(), QTime(23, 59, 59, 999), QTimeZone::LocalTime);
0150             } else {
0151                 end2 = dt2;
0152             }
0153         }
0154 
0155         if (start1 == start2) {
0156             return !isAllDay1    ? AtStart
0157                 : (end1 == end2) ? Equal
0158                 : (end1 < end2)  ? static_cast<DateTimeComparison>(AtStart | Inside)
0159                                  : static_cast<DateTimeComparison>(AtStart | Inside | AtEnd | After);
0160         }
0161 
0162         if (start1 < start2) {
0163             return (end1 < start2) ? Before
0164                 : (end1 == end2)   ? static_cast<DateTimeComparison>(Before | AtStart | Inside | AtEnd)
0165                 : (end1 == start2) ? static_cast<DateTimeComparison>(Before | AtStart)
0166                 : (end1 < end2)    ? static_cast<DateTimeComparison>(Before | AtStart | Inside)
0167                                    : Outside;
0168         } else {
0169             return (start1 > end2) ? After
0170                 : (start1 == end2) ? (end1 == end2 ? AtEnd : static_cast<DateTimeComparison>(AtEnd | After))
0171                 : (end1 == end2)   ? static_cast<DateTimeComparison>(Inside | AtEnd)
0172                 : (end1 < end2)    ? Inside
0173                                    : static_cast<DateTimeComparison>(Inside | AtEnd | After);
0174         }
0175     }
0176     return (start1 == start2) ? Equal : (start1 < start2) ? Before : After;
0177 }
0178 
0179 bool KCalendarCore::Events::startDateLessThan(const Event::Ptr &e1, const Event::Ptr &e2)
0180 {
0181     DateTimeComparison res = compare(e1->dtStart(), e1->allDay(), e2->dtStart(), e2->allDay());
0182     if (res == Equal) {
0183         return Events::summaryLessThan(e1, e2);
0184     } else {
0185         return (res & Before || res & AtStart);
0186     }
0187 }
0188 
0189 bool KCalendarCore::Events::startDateMoreThan(const Event::Ptr &e1, const Event::Ptr &e2)
0190 {
0191     DateTimeComparison res = compare(e1->dtStart(), e1->allDay(), e2->dtStart(), e2->allDay());
0192     if (res == Equal) {
0193         return Events::summaryMoreThan(e1, e2);
0194     } else {
0195         return (res & After || res & AtEnd);
0196     }
0197 }
0198 
0199 bool KCalendarCore::Events::summaryLessThan(const Event::Ptr &e1, const Event::Ptr &e2)
0200 {
0201     return QString::compare(e1->summary(), e2->summary(), Qt::CaseInsensitive) < 0;
0202 }
0203 
0204 bool KCalendarCore::Events::summaryMoreThan(const Event::Ptr &e1, const Event::Ptr &e2)
0205 {
0206     return QString::compare(e1->summary(), e2->summary(), Qt::CaseInsensitive) > 0;
0207 }
0208 
0209 bool KCalendarCore::Events::endDateLessThan(const Event::Ptr &e1, const Event::Ptr &e2)
0210 {
0211     DateTimeComparison res = compare(e1->dtEnd(), e1->allDay(), e2->dtEnd(), e2->allDay());
0212     if (res == Equal) {
0213         return Events::summaryLessThan(e1, e2);
0214     } else {
0215         return (res & Before || res & AtStart);
0216     }
0217 }
0218 
0219 bool KCalendarCore::Events::endDateMoreThan(const Event::Ptr &e1, const Event::Ptr &e2)
0220 {
0221     DateTimeComparison res = compare(e1->dtEnd(), e1->allDay(), e2->dtEnd(), e2->allDay());
0222     if (res == Equal) {
0223         return Events::summaryMoreThan(e1, e2);
0224     } else {
0225         return (res & After || res & AtEnd);
0226     }
0227 }
0228 
0229 bool KCalendarCore::Journals::dateLessThan(const Journal::Ptr &j1, const Journal::Ptr &j2)
0230 {
0231     DateTimeComparison res = compare(j1->dtStart(), j1->allDay(), j2->dtStart(), j2->allDay());
0232     return (res & Before || res & AtStart);
0233 }
0234 
0235 bool KCalendarCore::Journals::dateMoreThan(const Journal::Ptr &j1, const Journal::Ptr &j2)
0236 {
0237     DateTimeComparison res = compare(j1->dtStart(), j1->allDay(), j2->dtStart(), j2->allDay());
0238     return (res & After || res & AtEnd);
0239 }
0240 
0241 bool KCalendarCore::Journals::summaryLessThan(const Journal::Ptr &j1, const Journal::Ptr &j2)
0242 {
0243     return QString::compare(j1->summary(), j2->summary(), Qt::CaseInsensitive) < 0;
0244 }
0245 
0246 bool KCalendarCore::Journals::summaryMoreThan(const Journal::Ptr &j1, const Journal::Ptr &j2)
0247 {
0248     return QString::compare(j1->summary(), j2->summary(), Qt::CaseInsensitive) > 0;
0249 }
0250 
0251 bool KCalendarCore::Todos::startDateLessThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0252 {
0253     DateTimeComparison res = compare(t1->dtStart(), t1->allDay(), t2->dtStart(), t2->allDay());
0254     if (res == Equal) {
0255         return Todos::summaryLessThan(t1, t2);
0256     } else {
0257         return (res & Before || res & AtStart);
0258     }
0259 }
0260 
0261 bool KCalendarCore::Todos::startDateMoreThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0262 {
0263     DateTimeComparison res = compare(t1->dtStart(), t1->allDay(), t2->dtStart(), t2->allDay());
0264     if (res == Equal) {
0265         return Todos::summaryMoreThan(t1, t2);
0266     } else {
0267         return (res & After || res & AtEnd);
0268     }
0269 }
0270 
0271 bool KCalendarCore::Todos::dueDateLessThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0272 {
0273     if (!t1->hasDueDate() ) {
0274         return false;
0275     }
0276     if (!t2->hasDueDate()) {
0277         return true;
0278     }
0279     DateTimeComparison res = compare(t1->dtDue(), t1->allDay(), t2->dtDue(), t2->allDay());
0280     if (res == Equal) {
0281         return Todos::summaryLessThan(t1, t2);
0282     } else {
0283         return (res & Before || res & AtStart);
0284     }
0285 }
0286 
0287 bool KCalendarCore::Todos::dueDateMoreThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0288 {
0289     if (!t2->hasDueDate()) {
0290         return false;
0291     }
0292     if (!t1->hasDueDate()) {
0293         return true;
0294     }
0295     DateTimeComparison res = compare(t1->dtDue(), t1->allDay(), t2->dtDue(), t2->allDay());
0296     if (res == Equal) {
0297         return Todos::summaryMoreThan(t1, t2);
0298     } else {
0299         return (res & After || res & AtEnd);
0300     }
0301 }
0302 
0303 bool KCalendarCore::Todos::priorityLessThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0304 {
0305     if (t1->priority() < t2->priority()) {
0306         return true;
0307     } else if (t1->priority() == t2->priority()) {
0308         return Todos::summaryLessThan(t1, t2);
0309     } else {
0310         return false;
0311     }
0312 }
0313 
0314 bool KCalendarCore::Todos::priorityMoreThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0315 {
0316     if (t1->priority() > t2->priority()) {
0317         return true;
0318     } else if (t1->priority() == t2->priority()) {
0319         return Todos::summaryMoreThan(t1, t2);
0320     } else {
0321         return false;
0322     }
0323 }
0324 
0325 bool KCalendarCore::Todos::percentLessThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0326 {
0327     if (t1->percentComplete() < t2->percentComplete()) {
0328         return true;
0329     } else if (t1->percentComplete() == t2->percentComplete()) {
0330         return Todos::summaryLessThan(t1, t2);
0331     } else {
0332         return false;
0333     }
0334 }
0335 
0336 bool KCalendarCore::Todos::percentMoreThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0337 {
0338     if (t1->percentComplete() > t2->percentComplete()) {
0339         return true;
0340     } else if (t1->percentComplete() == t2->percentComplete()) {
0341         return Todos::summaryMoreThan(t1, t2);
0342     } else {
0343         return false;
0344     }
0345 }
0346 
0347 bool KCalendarCore::Todos::summaryLessThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0348 {
0349     return QString::compare(t1->summary(), t2->summary(), Qt::CaseInsensitive) < 0;
0350 }
0351 
0352 bool KCalendarCore::Todos::summaryMoreThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0353 {
0354     return QString::compare(t1->summary(), t2->summary(), Qt::CaseInsensitive) > 0;
0355 }
0356 
0357 bool KCalendarCore::Todos::createdLessThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0358 {
0359     DateTimeComparison res = compare(t1->created(), t1->allDay(), t2->created(), t2->allDay());
0360     if (res == Equal) {
0361         return Todos::summaryLessThan(t1, t2);
0362     } else {
0363         return (res & Before || res & AtStart);
0364     }
0365 }
0366 
0367 bool KCalendarCore::Todos::createdMoreThan(const Todo::Ptr &t1, const Todo::Ptr &t2)
0368 {
0369     DateTimeComparison res = compare(t1->created(), t1->allDay(), t2->created(), t2->allDay());
0370     if (res == Equal) {
0371         return Todos::summaryMoreThan(t1, t2);
0372     } else {
0373         return (res & After || res & AtEnd);
0374     }
0375 }
0376 
0377 bool KCalendarCore::Incidences::dateLessThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0378 {
0379     DateTimeComparison res = compare(i1->dateTime(Incidence::RoleSort), i1->allDay(), i2->dateTime(Incidence::RoleSort), i2->allDay());
0380     if (res == Equal) {
0381         return Incidences::summaryLessThan(i1, i2);
0382     } else {
0383         return (res & Before || res & AtStart);
0384     }
0385 }
0386 
0387 bool KCalendarCore::Incidences::dateMoreThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0388 {
0389     DateTimeComparison res = compare(i1->dateTime(Incidence::RoleSort), i1->allDay(), i2->dateTime(Incidence::RoleSort), i2->allDay());
0390     if (res == Equal) {
0391         return Incidences::summaryMoreThan(i1, i2);
0392     } else {
0393         return (res & After || res & AtEnd);
0394     }
0395 }
0396 
0397 bool KCalendarCore::Incidences::createdLessThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0398 {
0399     DateTimeComparison res = compare(i1->created(), i1->allDay(), i2->created(), i2->allDay());
0400     if (res == Equal) {
0401         return Incidences::summaryLessThan(i1, i2);
0402     } else {
0403         return (res & Before || res & AtStart);
0404     }
0405 }
0406 
0407 bool KCalendarCore::Incidences::createdMoreThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0408 {
0409     DateTimeComparison res = compare(i1->created(), i1->allDay(), i2->created(), i2->allDay());
0410     if (res == Equal) {
0411         return Incidences::summaryMoreThan(i1, i2);
0412     } else {
0413         return (res & After || res & AtEnd);
0414     }
0415 }
0416 
0417 bool KCalendarCore::Incidences::summaryLessThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0418 {
0419     return QString::compare(i1->summary(), i2->summary(), Qt::CaseInsensitive) < 0;
0420 }
0421 
0422 bool KCalendarCore::Incidences::summaryMoreThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0423 {
0424     return QString::compare(i1->summary(), i2->summary(), Qt::CaseInsensitive) > 0;
0425 }
0426 
0427 bool KCalendarCore::Incidences::categoriesLessThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0428 {
0429     const auto res = QString::compare(i1->categoriesStr(), i2->categoriesStr(), Qt::CaseSensitive);
0430     if (res == 0) {
0431         return Incidences::summaryLessThan(i1, i2);
0432     } else {
0433         return res < 0;
0434     }
0435 }
0436 
0437 bool KCalendarCore::Incidences::categoriesMoreThan(const Incidence::Ptr &i1, const Incidence::Ptr &i2)
0438 {
0439     const auto res = QString::compare(i1->categoriesStr(), i2->categoriesStr(), Qt::CaseSensitive);
0440     if (res == 0) {
0441         return Incidences::summaryMoreThan(i1, i2);
0442     } else {
0443         return res > 0;
0444     }
0445 }