File indexing completed on 2024-11-10 04:15:51
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 const 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 }