File indexing completed on 2024-04-28 15:39:56

0001 // SPDX-FileCopyrightText: 2003-2010 Jesper K. Pedersen <blackie@kde.org>
0002 // SPDX-FileCopyrightText: 2022-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "ImageDate.h"
0007 
0008 #include <KLocalizedString>
0009 #include <QDebug>
0010 #include <QLocale>
0011 #include <qregexp.h>
0012 
0013 using namespace DB;
0014 
0015 static const QTime _startOfDay_(0, 0, 0);
0016 static const QTime _endOfDay_(23, 59, 59);
0017 
0018 namespace
0019 {
0020 
0021 QStringList monthNames()
0022 {
0023     static QStringList res;
0024     if (res.isEmpty()) {
0025         for (int i = 1; i <= 12; ++i) {
0026             res << QLocale().standaloneMonthName(i, QLocale::ShortFormat);
0027         }
0028         for (int i = 1; i <= 12; ++i) {
0029             res << QLocale().standaloneMonthName(i, QLocale::LongFormat);
0030         }
0031 
0032         res << i18nc("Abbreviated month name", "jan") << i18nc("Abbreviated month name", "feb")
0033             << i18nc("Abbreviated month name", "mar") << i18nc("Abbreviated month name", "apr")
0034             << i18nc("Abbreviated month name", "may") << i18nc("Abbreviated month name", "jun")
0035             << i18nc("Abbreviated month name", "jul") << i18nc("Abbreviated month name", "aug")
0036             << i18nc("Abbreviated month name", "sep") << i18nc("Abbreviated month name", "oct")
0037             << i18nc("Abbreviated month name", "nov") << i18nc("Abbreviated month name", "dec");
0038         res << QString::fromLatin1("jan") << QString::fromLatin1("feb") << QString::fromLatin1("mar") << QString::fromLatin1("apr")
0039             << QString::fromLatin1("may") << QString::fromLatin1("jun") << QString::fromLatin1("jul") << QString::fromLatin1("aug")
0040             << QString::fromLatin1("sep") << QString::fromLatin1("oct") << QString::fromLatin1("nov") << QString::fromLatin1("dec");
0041 
0042         for (int i = 1; i <= 12; ++i) {
0043             res << QLocale().monthName(i, QLocale::ShortFormat);
0044         }
0045         for (int i = 1; i <= 12; ++i) {
0046             res << QLocale().monthName(i, QLocale::LongFormat);
0047         }
0048 
0049         for (QStringList::iterator it = res.begin(); it != res.end(); ++it)
0050             *it = it->toLower();
0051     }
0052     return res;
0053 }
0054 
0055 QString formatRegexp()
0056 {
0057     static QString str;
0058     if (str.isEmpty()) {
0059         str = QString::fromLatin1("^((\\d\\d?)([-. /]+|$))?((");
0060         QStringList months = monthNames();
0061         for (QStringList::ConstIterator monthIt = months.constBegin(); monthIt != months.constEnd(); ++monthIt)
0062             str += QString::fromLatin1("%1|").arg(*monthIt);
0063 
0064         str += QString::fromLatin1("\\d?\\d)([-. /]+|$))?(\\d\\d(\\d\\d)?)?$");
0065     }
0066     return str;
0067 }
0068 
0069 } // namespace
0070 
0071 ImageDate::ImageDate(const QDate &date)
0072     : m_start(date, _startOfDay_)
0073     , m_end(m_start)
0074 {
0075 }
0076 
0077 ImageDate::ImageDate(const Utilities::FastDateTime &date)
0078     : m_start(date)
0079     , m_end(date)
0080 {
0081 }
0082 
0083 bool ImageDate::operator<=(const ImageDate &other) const
0084 {
0085     // This operator is used by QMap when checking for equal elements, thus we need the second part too.
0086     return m_start < other.m_start || (m_start == other.m_start && m_end <= other.m_end);
0087 }
0088 
0089 ImageDate::ImageDate()
0090     : m_start()
0091     , m_end()
0092 {
0093 }
0094 
0095 bool ImageDate::isNull() const
0096 {
0097     return m_start.isNull();
0098 }
0099 
0100 bool ImageDate::isFuzzy() const
0101 {
0102     return m_start != m_end;
0103 }
0104 
0105 static bool isFirstSecOfMonth(const Utilities::FastDateTime &date)
0106 {
0107     return date.date().day() == 1 && date.time().hour() == 0 && date.time().minute() == 0;
0108 }
0109 
0110 static bool isLastSecOfMonth(Utilities::FastDateTime date)
0111 {
0112     return isFirstSecOfMonth(date.addSecs(1));
0113 }
0114 
0115 static bool isFirstSecOfDay(const Utilities::FastDateTime &time)
0116 {
0117     return time.time().hour() == 0 && time.time().minute() == 0 && time.time().second() == 0;
0118 }
0119 
0120 static bool isLastSecOfDay(const Utilities::FastDateTime &time)
0121 {
0122     return time.time().hour() == 23 && time.time().minute() == 59 && time.time().second() == 59;
0123 }
0124 
0125 QString ImageDate::toString(bool withTime) const
0126 {
0127     if (m_start.isNull())
0128         return QString();
0129 
0130     if (m_start == m_end) {
0131         if (withTime && !isFirstSecOfDay(m_start))
0132             return m_start.toString(QString::fromLatin1("d. MMM yyyy hh:mm:ss"));
0133         else
0134             return m_start.toString(QString::fromLatin1("d. MMM yyyy"));
0135     }
0136 
0137     // start is different from end.
0138     if (isFirstSecOfMonth(m_start) && isLastSecOfMonth(m_end)) {
0139         if (m_start.date().month() == 1 && m_end.date().month() == 12) {
0140             if (m_start.date().year() == m_end.date().year()) {
0141                 // 2005
0142                 return QString::number(m_start.date().year());
0143             } else {
0144                 // 2005-2006
0145                 return QString::fromLatin1("%1 - %2").arg(m_start.date().year()).arg(m_end.date().year());
0146             }
0147         } else {
0148             // a whole month, but not a whole year.
0149             if (m_start.date().year() == m_end.date().year() && m_start.date().month() == m_end.date().month()) {
0150                 // jan 2005
0151                 return QString::fromLatin1("%1 %2")
0152                     .arg(QLocale().standaloneMonthName(m_start.date().month(), QLocale::ShortFormat))
0153                     .arg(m_start.date().year());
0154             } else {
0155                 // jan 2005 - feb 2006
0156                 return QString::fromLatin1("%1 %2 - %3 %4")
0157                     .arg(QLocale().standaloneMonthName(m_start.date().month(), QLocale::ShortFormat))
0158                     .arg(m_start.date().year())
0159                     .arg(QLocale().standaloneMonthName(m_end.date().month(), QLocale::ShortFormat))
0160                     .arg(m_end.date().year());
0161             }
0162         }
0163     }
0164 
0165     if (!withTime || (isFirstSecOfDay(m_start) && isLastSecOfDay(m_end))) {
0166         if (m_start.date() == m_end.date()) {
0167             // A whole day
0168             return m_start.toString(QString::fromLatin1("d. MMM yyyy"));
0169         } else {
0170             // A day range
0171             return QString::fromLatin1("%1 - %2")
0172                 .arg(m_start.toString(QString::fromLatin1("d. MMM yyyy")), m_end.toString(QString::fromLatin1("d. MMM yyyy")));
0173         }
0174     }
0175 
0176     // Range smaller than one day.
0177     if (withTime && (!isFirstSecOfDay(m_start) || !isLastSecOfDay(m_end)))
0178         return QString::fromLatin1("%1 - %2")
0179             .arg(m_start.toString(QString::fromLatin1("d. MMM yyyy hh:mm")), m_end.toString(QString::fromLatin1("d. MMM yyyy hh:mm")));
0180     else
0181         return QString::fromLatin1("%1 - %2")
0182             .arg(m_start.toString(QString::fromLatin1("d. MMM yyyy")), m_end.toString(QString::fromLatin1("d. MMM yyyy")));
0183 }
0184 
0185 bool ImageDate::operator==(const ImageDate &other) const
0186 {
0187     return m_start == other.m_start && m_end == other.m_end;
0188 }
0189 
0190 bool ImageDate::operator!=(const ImageDate &other) const
0191 {
0192     return !(*this == other);
0193 }
0194 
0195 bool ImageDate::operator<(const ImageDate &other) const
0196 {
0197     return start() < other.start() || (start() == other.start() && end() < other.end());
0198 }
0199 
0200 ImageDate::ImageDate(const Utilities::FastDateTime &start, const Utilities::FastDateTime &end)
0201 {
0202     if (!start.isValid() || !end.isValid() || start <= end) {
0203         m_start = start;
0204         m_end = end;
0205     } else {
0206         m_start = end;
0207         m_end = start;
0208     }
0209 }
0210 
0211 ImageDate::ImageDate(const QDate &start, const QDate &end)
0212 {
0213     if (!start.isValid() || !end.isValid() || start <= end) {
0214         m_start = Utilities::FastDateTime(start, _startOfDay_);
0215         m_end = Utilities::FastDateTime(end, _endOfDay_);
0216     } else {
0217         m_start = Utilities::FastDateTime(end, _startOfDay_);
0218         m_end = Utilities::FastDateTime(start, _endOfDay_);
0219     }
0220 }
0221 
0222 static QDate addMonth(int year, int month)
0223 {
0224     if (month == 12) {
0225         year++;
0226         month = 1;
0227     } else
0228         month++;
0229     return QDate(year, month, 1);
0230 }
0231 
0232 ImageDate::ImageDate(int yearFrom, int monthFrom, int dayFrom, int yearTo, int monthTo, int dayTo, int hourFrom, int minuteFrom, int secondFrom)
0233 {
0234     if (yearFrom <= 0) {
0235         m_start = Utilities::FastDateTime();
0236         m_end = Utilities::FastDateTime();
0237         return;
0238     }
0239 
0240     if (monthFrom <= 0) {
0241         m_start = QDate(yearFrom, 1, 1).startOfDay();
0242         m_end = QDate(yearFrom + 1, 1, 1).startOfDay().addSecs(-1);
0243     } else if (dayFrom <= 0) {
0244         m_start = QDate(yearFrom, monthFrom, 1).startOfDay();
0245         m_end = addMonth(yearFrom, monthFrom).startOfDay().addSecs(-1);
0246     } else if (hourFrom < 0) {
0247         m_start = QDate(yearFrom, monthFrom, dayFrom).startOfDay();
0248         m_end = QDate(yearFrom, monthFrom, dayFrom).addDays(1).startOfDay().addSecs(-1);
0249     } else if (minuteFrom < 0) {
0250         m_start = Utilities::FastDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, 0, 0));
0251         m_end = Utilities::FastDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, 23, 59));
0252     } else if (secondFrom < 0) {
0253         m_start = Utilities::FastDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, minuteFrom, 0));
0254         m_end = Utilities::FastDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, minuteFrom, 59));
0255     } else {
0256         m_start = Utilities::FastDateTime(QDate(yearFrom, monthFrom, dayFrom), QTime(hourFrom, minuteFrom, secondFrom));
0257         m_end = m_start;
0258     }
0259 
0260     if (yearTo > 0) {
0261         m_end = QDate(yearTo + 1, 1, 1).startOfDay().addSecs(-1);
0262 
0263         if (monthTo > 0) {
0264             m_end = addMonth(yearTo, monthTo).startOfDay().addSecs(-1);
0265 
0266             if (dayTo > 0) {
0267                 if (dayFrom == dayTo && monthFrom == monthTo && yearFrom == yearTo)
0268                     m_end = m_start;
0269                 else
0270                     m_end = QDate(yearTo, monthTo, dayTo).addDays(1).startOfDay().addSecs(-1);
0271             }
0272         }
0273         // It should not be possible here for m_end < m_start.
0274         Q_ASSERT(m_start <= m_end);
0275     }
0276 }
0277 
0278 bool ImageDate::hasValidTime() const
0279 {
0280     return m_start == m_end;
0281 }
0282 
0283 ImageDate::ImageDate(const QDate &start, const QDate &end, const QTime &time)
0284 {
0285     const QDate validatedEnd = (end.isValid()) ? end : start;
0286 
0287     if (start == validatedEnd && time.isValid()) {
0288         m_start = Utilities::FastDateTime(start, time);
0289         m_end = m_start;
0290     } else {
0291         if (start > validatedEnd) {
0292             m_end = Utilities::FastDateTime(start, _startOfDay_);
0293             m_start = Utilities::FastDateTime(validatedEnd, _endOfDay_);
0294         } else {
0295             m_start = Utilities::FastDateTime(start, _startOfDay_);
0296             m_end = Utilities::FastDateTime(validatedEnd, _endOfDay_);
0297         }
0298     }
0299 }
0300 
0301 ImageDate::MatchType ImageDate::isIncludedIn(const ImageDate &searchRange) const
0302 {
0303     if (searchRange.start() <= start() && searchRange.end() >= end())
0304         return MatchType::IsContained;
0305 
0306     if (searchRange.start() <= end() && searchRange.end() >= start()) {
0307         return MatchType::Overlap;
0308     }
0309     return MatchType::NoMatch;
0310 }
0311 
0312 bool ImageDate::includes(const Utilities::FastDateTime &date) const
0313 {
0314     return ImageDate(date).isIncludedIn(*this) == MatchType::IsContained;
0315 }
0316 
0317 void ImageDate::extendTo(const ImageDate &other)
0318 {
0319     if (other.isNull())
0320         return;
0321 
0322     if (isNull()) {
0323         m_start = other.m_start;
0324         m_end = other.m_end;
0325     } else {
0326         if (other.m_start < m_start)
0327             m_start = other.m_start;
0328         if (other.m_end > m_end)
0329             m_end = other.m_end;
0330     }
0331 }
0332 
0333 QDate DB::parseDateString(const QString &dateString, bool assumeStartDate)
0334 {
0335     QRegExp regexp(formatRegexp(), Qt::CaseInsensitive);
0336 
0337     if (regexp.exactMatch(dateString)) {
0338         int year = 0;
0339         int month = 0;
0340         int day = 0;
0341 
0342         QString dayStr = regexp.cap(2);
0343         QString monthStr = regexp.cap(5).toLower();
0344         QString yearStr = regexp.cap(7);
0345 
0346         if (dayStr.length() != 0)
0347             day = dayStr.toInt();
0348 
0349         if (yearStr.length() != 0) {
0350             year = yearStr.toInt();
0351             if (year < 50)
0352                 year += 2000;
0353             if (year < 100)
0354                 year += 1900;
0355         }
0356         if (monthStr.length() != 0) {
0357             int index = monthNames().indexOf(monthStr);
0358             if (index != -1)
0359                 month = (index % 12) + 1;
0360             else
0361                 month = monthStr.toInt();
0362         }
0363         if (year == 0)
0364             year = QDate::currentDate().year();
0365         if (month == 0) {
0366             if (assumeStartDate) {
0367                 month = 1;
0368                 day = 1;
0369             } else {
0370                 month = 12;
0371                 day = 31;
0372             }
0373         } else if (day == 0) {
0374             if (assumeStartDate)
0375                 day = 1;
0376             else
0377                 day = QDate(year, month, 1).daysInMonth();
0378         }
0379         return QDate(year, month, day);
0380     } else
0381         return QDate();
0382 }
0383 
0384 QDebug operator<<(QDebug debug, const DB::ImageDate &d)
0385 {
0386     QDebugStateSaver saveState(debug);
0387 
0388     if (d.isNull()) {
0389         debug << "DB::ImageDate()";
0390     } else if (d.isFuzzy()) {
0391         debug.nospace() << "DB::ImageDate(" << d.start().date().toString(Qt::ISODate) << ", " << d.end().date().toString(Qt::ISODate) << ")";
0392     } else {
0393         debug.nospace() << "DB::ImageDate(" << d.start().date().toString(Qt::ISODate) << ")";
0394     }
0395     return debug;
0396 }
0397 
0398 // vi:expandtab:tabstop=4 shiftwidth=4: