File indexing completed on 2022-11-23 11:08:48

0001 /* This file is part of the KDE project
0002    Copyright (C) 2018 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016  * Boston, MA 02110-1301, USA.
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  */
0019 
0020 #include "KDbDateTime.h"
0021 
0022 #include <QRegularExpression>
0023 
0024 const int UNCACHED_YEAR = -1;
0025 const int INVALID_YEAR = -2;
0026 
0027 namespace {
0028 template <typename T>
0029 std::function<QString(const T&)> byteArrayToString()
0030 {
0031     return [](const T &v) { return QString::fromLatin1(v.toString()); };
0032 }
0033 
0034 struct KDbDateTimeMetatypeInitializer {
0035     KDbDateTimeMetatypeInitializer()
0036     {
0037         using namespace std::placeholders;
0038         QMetaType::registerConverter<KDbYear, QString>(byteArrayToString<KDbYear>());
0039         QMetaType::registerConverter<KDbYear, int>(std::bind(&KDbYear::toIsoValue, _1));
0040         QMetaType::registerConverter<KDbDate, QString>(byteArrayToString<KDbDate>());
0041         QMetaType::registerConverter<KDbDate, QDate>(std::bind(&KDbDate::toQDate, _1));
0042         QMetaType::registerConverter<KDbTime, QString>(byteArrayToString<KDbTime>());
0043         QMetaType::registerConverter<KDbTime, QTime>(std::bind(&KDbTime::toQTime, _1));
0044         QMetaType::registerConverter<KDbDateTime, QString>(byteArrayToString<KDbDateTime>());
0045         QMetaType::registerConverter<KDbDateTime, QDateTime>(std::bind(&KDbDateTime::toQDateTime, _1));
0046         QMetaType::registerComparators<KDbYear>();
0047         QMetaType::registerComparators<KDbDate>();
0048         QMetaType::registerComparators<KDbTime>();
0049         QMetaType::registerComparators<KDbDateTime>();
0050     }
0051 };
0052 
0053 KDbDateTimeMetatypeInitializer s_init;
0054 }
0055 
0056 bool KDbYear::operator==(const KDbYear &other) const
0057 {
0058     return m_sign == other.sign() && m_string == other.yearString();
0059 }
0060 
0061 bool KDbYear::operator<(const KDbYear &other) const
0062 {
0063     return toQDateValue() < other.toQDateValue();
0064 }
0065 
0066 bool KDbYear::isValid() const
0067 {
0068     return std::get<1>(intValue());
0069 }
0070 
0071 bool KDbYear::isNull() const
0072 {
0073     return m_sign == Sign::None && m_string.isEmpty();
0074 }
0075 
0076 QByteArray KDbYear::signString() const
0077 {
0078     QByteArray result;
0079     switch (m_sign) {
0080     case Sign::Plus:
0081         result = QByteArrayLiteral("+");
0082         break;
0083     case Sign::Minus:
0084         result = QByteArrayLiteral("-");
0085         break;
0086     default:
0087         break;
0088     }
0089     return result;
0090 }
0091 
0092 KDB_EXPORT QDebug operator<<(QDebug dbg, KDbYear::Sign sign)
0093 {
0094     QDebugStateSaver saver(dbg);
0095     switch (sign) {
0096     case KDbYear::Sign::None:
0097         break;
0098     case KDbYear::Sign::Plus:
0099         dbg.nospace() << '+';
0100         break;
0101     case KDbYear::Sign::Minus:
0102         dbg.nospace() << '-';
0103         break;
0104     }
0105     return dbg.maybeSpace();
0106 }
0107 
0108 KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbYear& year)
0109 {
0110     QDebugStateSaver saver(dbg);
0111     dbg.nospace().noquote() << "KDbYear(" << year.sign() << year.yearString();
0112     if (!year.isValid()) {
0113         dbg.nospace() << " INVALID";
0114     }
0115     dbg.nospace() << ")";
0116     return dbg.maybeSpace();
0117 }
0118 
0119 QByteArray KDbYear::toString() const
0120 {
0121     QByteArray result;
0122     if (isNull()) {
0123         result = QByteArrayLiteral("<NULL_YEAR>");
0124     } else { // can be invalid, that's OK
0125         result = signString() + m_string;
0126     }
0127     return result;
0128 }
0129 
0130 int KDbYear::toIsoValue() const
0131 {
0132     return std::get<0>(intValue());
0133 }
0134 
0135 int KDbYear::toQDateValue() const
0136 {
0137     int result;
0138     bool ok;
0139     std::tie(result, ok) = intValue();
0140     if (!ok) {
0141         return 0;
0142     }
0143     if (result > 0) {
0144         return result;
0145     }
0146     return result - 1;
0147 }
0148 
0149 namespace {
0150 int intValueInternal(KDbYear::Sign sign, const QByteArray &string)
0151 {
0152     const int length = string.length();
0153     if (length < 4) {
0154         // TODO message: at least 4 digits required
0155         return INVALID_YEAR;
0156     } else if (length > 4) {
0157         if (sign == KDbYear::Sign::None) {
0158             // TODO message: more than 4 digits, sign required
0159             return INVALID_YEAR;
0160         }
0161     }
0162 
0163     static QRegularExpression digitsRegExp(QStringLiteral("^\\d+$"));
0164     if (!digitsRegExp.match(QString::fromLatin1(string)).hasMatch()) {
0165         // TODO message: only digits are accepted for year
0166         return INVALID_YEAR;
0167     }
0168 
0169     bool ok;
0170     int result = string.toInt(&ok);
0171     if (!ok || result < 0) {
0172         // TODO message: failed to convert year to integer >= 0
0173         return INVALID_YEAR;
0174     }
0175     int qDateYear;
0176     if (result == 0) {
0177         if (sign != KDbYear::Sign::Plus) {
0178             // TODO message: + required for 0000
0179             return INVALID_YEAR;
0180         }
0181         qDateYear = -1;
0182     } else if (sign == KDbYear::Sign::Minus) {
0183         qDateYear = - result - 1;
0184     } else { // Plus or None
0185         qDateYear = result;
0186     }
0187     // verify if this year is within the limits of QDate (see QDate::minJd(), QDate::maxJd())
0188     if (!QDate(qDateYear, 1, 1).isValid()) {
0189         // TODO message: year is not within limits
0190         return INVALID_YEAR;
0191     }
0192     return result;
0193 }
0194 }
0195 
0196 std::tuple<int, bool> KDbYear::intValue() const
0197 {
0198     if (m_isoValue == UNCACHED_YEAR) {
0199         const_cast<int&>(m_isoValue) = intValueInternal(m_sign, m_string); // cache
0200     }
0201     if (m_isoValue == INVALID_YEAR) {
0202         return std::make_tuple(0, false);
0203     }
0204     return std::make_tuple(m_sign == Sign::Minus ? -m_isoValue : m_isoValue, true);
0205 }
0206 
0207 bool KDbDate::operator==(const KDbDate &other) const
0208 {
0209     return m_year == other.year() && m_monthString == other.monthString()
0210         && m_dayString == other.dayString();
0211 }
0212 
0213 bool KDbDate::operator<(const KDbDate &other) const
0214 {
0215     return toQDate() < other.toQDate();
0216 }
0217 
0218 bool KDbDate::isValid() const
0219 {
0220     return toQDate().isValid();
0221 }
0222 
0223 bool KDbDate::isNull() const
0224 {
0225     return m_year.isNull() && m_monthString.isEmpty() && m_dayString.isEmpty();
0226 }
0227 
0228 QDate KDbDate::toQDate() const
0229 {
0230     return { m_year.toQDateValue(), month(), day() };
0231 }
0232 
0233 namespace {
0234 int toInt(const QByteArray &string, int min, int max, int minLength, int maxLength)
0235 {
0236     if (string.length() < minLength || string.length() > maxLength) {
0237         // TODO message: invalid length
0238         return -1;
0239     }
0240     bool ok = true;
0241     const int result = string.isEmpty() ? 0 : string.toInt(&ok);
0242     if (!ok || result < min || result > max) {
0243         // TODO message: could not convert string to integer
0244         return -1;
0245     }
0246     return result;
0247 }
0248 }
0249 
0250 int KDbDate::month() const
0251 {
0252     return toInt(m_monthString, 1, 12, 1, 2);
0253 }
0254 
0255 int KDbDate::day() const
0256 {
0257     return toInt(m_dayString, 1, 31, 1, 2);
0258 }
0259 
0260 QByteArray KDbDate::toString() const
0261 {
0262     QByteArray result;
0263     if (isNull()) {
0264         result = QByteArrayLiteral("<NULL_DATE>");
0265     } else { // can be invalid, that's OK
0266         result = m_year.toString() + '-' + m_monthString + '-' + m_dayString;
0267     }
0268     return result;
0269 }
0270 
0271 KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDate &date)
0272 {
0273     QDebugStateSaver saver(dbg);
0274     dbg.nospace().noquote() << "KDbDate(" << date.toString();
0275     if (!date.isValid()) {
0276         dbg.nospace() << " INVALID";
0277     }
0278     dbg.nospace() << ")";
0279     return dbg.maybeSpace();
0280 }
0281 
0282 bool KDbTime::operator==(const KDbTime &other) const
0283 {
0284     return m_hourString == other.hourString() && m_minuteString == other.minuteString()
0285         && m_secondString == other.secondString() && m_msecString == other.msecString()
0286         && m_period == other.period();
0287 }
0288 
0289 bool KDbTime::operator<(const KDbTime &other) const
0290 {
0291     return toQTime() < other.toQTime();
0292 }
0293 
0294 QTime KDbTime::toQTime() const
0295 {
0296     // Rules for hours based on https://www.timeanddate.com/time/am-and-pm.html#converting
0297     int h = hour();
0298     if (h == -1) {
0299         return {};
0300     }
0301     const int m = minute();
0302     if (m == -1) {
0303         return {};
0304     }
0305     const int s = second();
0306     if (s == -1) {
0307         return {};
0308     }
0309     const int ms = msec();
0310     if (ms == -1) {
0311         return {};
0312     }
0313     if (m_period == Period::None) {
0314         return { h, m, s, ms };
0315     }
0316     return QTime::fromString(
0317         QStringLiteral("%1:%2:%3.%4 %5")
0318             .arg(h)
0319             .arg(m)
0320             .arg(s)
0321             .arg(ms)
0322             .arg(m_period == Period::Am ? QLatin1String("AM") : QLatin1String("PM")),
0323         QStringLiteral("h:m:s.z AP"));
0324 }
0325 
0326 int KDbTime::hour() const
0327 {
0328     switch (m_period) {
0329     case Period::None:
0330         return toInt(m_hourString, 0, 23, 1, 2);
0331     case Period::Am:
0332     case Period::Pm:
0333         return toInt(m_hourString, 1, 12, 1, 2);
0334     }
0335     return -1;
0336 }
0337 
0338 int KDbTime::minute() const
0339 {
0340     return toInt(m_minuteString, 0, 59, 1, 2);
0341 }
0342 
0343 int KDbTime::second() const
0344 {
0345     return toInt(m_secondString, 0, 59, 0, 2);
0346 }
0347 
0348 int KDbTime::msec() const
0349 {
0350     return toInt(m_msecString, 0, 999, 0, 3);
0351 }
0352 
0353 bool KDbTime::isValid() const
0354 {
0355     return toQTime().isValid();
0356 }
0357 
0358 bool KDbTime::isNull() const
0359 {
0360     return m_hourString.isEmpty() || m_minuteString.isEmpty();
0361 }
0362 
0363 QByteArray KDbTime::toString() const
0364 {
0365     QByteArray result;
0366     if (isNull()) {
0367         result = QByteArrayLiteral("<NULL_TIME>");
0368     } else if (m_msecString.isEmpty()) { // can be invalid, that's OK
0369         if (m_secondString.isEmpty()) {
0370             result = m_hourString + ':' + m_minuteString;
0371         } else {
0372             result = m_hourString + ':' + m_minuteString + ':' + m_secondString;
0373         }
0374     } else { // can be invalid, that's OK
0375         result = m_hourString + ':' + m_minuteString + ':' + m_secondString + '.' + m_msecString;
0376     }
0377     switch (m_period) {
0378     case KDbTime::Period::Am:
0379         result += " AM";
0380         break;
0381     case KDbTime::Period::Pm:
0382         result += " PM";
0383         break;
0384     default:
0385         break;
0386     }
0387     return result;
0388 }
0389 
0390 KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTime &time)
0391 {
0392     QDebugStateSaver saver(dbg);
0393     dbg.nospace().noquote() << "KDbTime(" << time.toString();
0394     if (!time.isValid()) {
0395         dbg.nospace() << " INVALID";
0396     }
0397     dbg.nospace() << ")";
0398     return dbg.maybeSpace();
0399 }
0400 
0401 bool KDbDateTime::operator==(const KDbDateTime &other) const
0402 {
0403     return date() == other.date() && time() == other.time();
0404 }
0405 
0406 bool KDbDateTime::operator<(const KDbDateTime &other) const
0407 {
0408     return toQDateTime() < other.toQDateTime();
0409 }
0410 
0411 bool KDbDateTime::isValid() const
0412 {
0413     return m_date.isValid() && m_time.isValid();
0414 }
0415 
0416 bool KDbDateTime::isNull() const
0417 {
0418     return m_date.isNull() || m_time.isNull();
0419 }
0420 
0421 QDateTime KDbDateTime::toQDateTime() const
0422 {
0423     return { m_date.toQDate(), m_time.toQTime() };
0424 }
0425 
0426 QByteArray KDbDateTime::toString() const
0427 {
0428     QByteArray result;
0429     if (isNull()) {
0430         result = QByteArrayLiteral("<NULL_DATETIME>");
0431     } else {
0432         result = m_date.toString() + ' ' + m_time.toString(); // can be invalid, that's OK
0433     }
0434     return result;
0435 }
0436 
0437 KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDateTime &dateTime)
0438 {
0439     QDebugStateSaver saver(dbg);
0440     dbg.nospace().noquote() << "KDbDateTime(" << dateTime.toString();
0441     if (!dateTime.isValid()) {
0442         dbg.nospace() << "INVALID";
0443     }
0444     dbg.nospace() << ")";
0445     return dbg.maybeSpace();
0446 }