| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtXmlPatterns module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include <QStringList> | 
| 41 | #if QT_CONFIG(timezone) | 
| 42 | #include <QTimeZone> | 
| 43 | #endif | 
| 44 |  | 
| 45 | #include "qbuiltintypes_p.h" | 
| 46 | #include "qitem_p.h" | 
| 47 | #include "qpatternistlocale_p.h" | 
| 48 | #include "qvalidationerror_p.h" | 
| 49 |  | 
| 50 | #include "qabstractdatetime_p.h" | 
| 51 |  | 
| 52 | QT_BEGIN_NAMESPACE | 
| 53 |  | 
| 54 | using namespace QPatternist; | 
| 55 |  | 
| 56 | AbstractDateTime::AbstractDateTime(const QDateTime &dateTime) : m_dateTime(dateTime) | 
| 57 | { | 
| 58 |     Q_ASSERT(dateTime.isValid()); | 
| 59 | } | 
| 60 |  | 
| 61 | #define badData(msg)        errorMessage = ValidationError::createError(msg); return QDateTime() | 
| 62 | #define getCapt(sym)        ((captTable.sym == -1) ? QString() : capts.at(captTable.sym)) | 
| 63 | #define getSafeCapt(sym)    ((captTable.sym == -1) ? QString() : capts.value(captTable.sym)) | 
| 64 |  | 
| 65 | QDateTime AbstractDateTime::create(AtomicValue::Ptr &errorMessage, | 
| 66 |                                    const QString &lexicalSource, | 
| 67 |                                    const CaptureTable &captTable) | 
| 68 | { | 
| 69 |     QRegExp myExp(captTable.regExp); | 
| 70 |  | 
| 71 |     if(!myExp.exactMatch(str: lexicalSource)) | 
| 72 |     { | 
| 73 |         badData(QString()); | 
| 74 |     } | 
| 75 |  | 
| 76 |     const QStringList capts(myExp.capturedTexts()); | 
| 77 |     const QString yearStr(getCapt(year)); | 
| 78 |  | 
| 79 |     if(yearStr.size() > 4 && yearStr.at(i: 0) == QLatin1Char('0')) | 
| 80 |     { | 
| 81 |         badData(QtXmlPatterns::tr("Year %1 is invalid because it begins with %2." ) | 
| 82 |                 .arg(formatData(yearStr)).arg(formatData("0" ))); | 
| 83 |     } | 
| 84 |  | 
| 85 |     /* If the strings are empty, load default values which are | 
| 86 |      * guranteed to pass the validness tests. */ | 
| 87 |     const QString monthStr(getCapt(month)); | 
| 88 |     const QString dayStr(getCapt(day)); | 
| 89 |     YearProperty year = yearStr.isEmpty() ? DefaultYear : yearStr.toInt(); | 
| 90 |     if(getCapt(yearSign) == QChar::fromLatin1(c: '-')) | 
| 91 |         year = -year; | 
| 92 |     const MonthProperty month = monthStr.isEmpty() ? DefaultMonth : monthStr.toInt(); | 
| 93 |     const MonthProperty day = dayStr.isEmpty() ? DefaultDay : dayStr.toInt(); | 
| 94 |  | 
| 95 |     if(!QDate::isValid(y: year, m: month, d: day)) | 
| 96 |     { | 
| 97 |         /* Try to give an intelligent message. */ | 
| 98 |         if(day > 31 || day < 1) | 
| 99 |         { | 
| 100 |             badData(QtXmlPatterns::tr("Day %1 is outside the range %2..%3." ) | 
| 101 |                     .arg(formatData(QString::number(day))) | 
| 102 |                     .arg(formatData("01" )) | 
| 103 |                     .arg(formatData("31" ))); | 
| 104 |         } | 
| 105 |         else if(month > 12 || month < -12 || month == 0) | 
| 106 |         { | 
| 107 |             badData(QtXmlPatterns::tr("Month %1 is outside the range %2..%3." ) | 
| 108 |                     .arg(month) | 
| 109 |                     .arg(formatData("01" )) | 
| 110 |                     .arg(formatData("12" ))); | 
| 111 |  | 
| 112 |         } | 
| 113 |         else if(QDate::isValid(y: DefaultYear, m: month, d: day)) | 
| 114 |         { | 
| 115 |             /* We can't use the badData() macro here because we need a different | 
| 116 |              * error code: FODT0001 instead of FORG0001. */ | 
| 117 |             errorMessage = ValidationError::createError(description: QtXmlPatterns::tr( | 
| 118 |                                sourceText: "Overflow: Can't represent date %1." ) | 
| 119 |                                .arg(a: formatData(data: QLatin1String("%1-%2-%3" )) | 
| 120 |                                     .arg(a: year).arg(a: month).arg(a: day)), | 
| 121 |                                ReportContext::FODT0001); | 
| 122 |             return QDateTime(); | 
| 123 |         } | 
| 124 |         else | 
| 125 |         { | 
| 126 |             badData(QtXmlPatterns::tr("Day %1 is invalid for month %2." ) | 
| 127 |                          .arg(formatData(QString::number(day))) | 
| 128 |                          .arg(formatData(QString::number(month)))); | 
| 129 |         } | 
| 130 |     } | 
| 131 |  | 
| 132 |     /* Parse the zone offset. */ | 
| 133 |     ZoneOffsetParseResult zoResult; | 
| 134 |     const ZOTotal offset = parseZoneOffset(result&: zoResult, capts, captTable); | 
| 135 |  | 
| 136 |     if(zoResult == Error) | 
| 137 |     { | 
| 138 |         errorMessage = ValidationError::createError(); | 
| 139 |         /* We encountered an error, so stop processing. */ | 
| 140 |         return QDateTime(); | 
| 141 |     } | 
| 142 |  | 
| 143 |     QDate date(year, month, day); | 
| 144 |  | 
| 145 |     /* Only deal with time if time is needed. */ | 
| 146 |     if(captTable.hour == -1) | 
| 147 |     { | 
| 148 |         QDateTime result = date.startOfDay(); | 
| 149 |         setUtcOffset(result, zoResult, zoOffset: offset); | 
| 150 |         return result; | 
| 151 |     } | 
| 152 |     else | 
| 153 |     { | 
| 154 |         /* Now, it's time for the time-part. | 
| 155 |          * | 
| 156 |          * If the strings are empty, toInt() will return 0, which | 
| 157 |          * in all cases is valid properties. */ | 
| 158 |         const QString hourStr(getCapt(hour)); | 
| 159 |         const QString minutesStr(getCapt(minutes)); | 
| 160 |         const QString secondsStr(getCapt(seconds)); | 
| 161 |         HourProperty hour = hourStr.toInt(); | 
| 162 |         const MinuteProperty mins = minutesStr.toInt(); | 
| 163 |         const SecondProperty secs = secondsStr.toInt(); | 
| 164 |  | 
| 165 |         QString msecondsStr(getSafeCapt(mseconds)); | 
| 166 |         if(!msecondsStr.isEmpty()) | 
| 167 |             msecondsStr = msecondsStr.leftJustified(width: 3, fill: QLatin1Char('0'), trunc: true); | 
| 168 |         const MSecondProperty msecs = msecondsStr.toInt(); | 
| 169 |  | 
| 170 |         if(hour == 24) | 
| 171 |         { | 
| 172 |             /* 24:00:00.00 is an invalid time for QTime, so handle it here. */ | 
| 173 |             if(mins != 0 || secs != 0 || msecs != 0) | 
| 174 |             { | 
| 175 |                 badData(QtXmlPatterns::tr("Time 24:%1:%2.%3 is invalid. "  | 
| 176 |                                           "Hour is 24, but minutes, seconds, "  | 
| 177 |                                           "and milliseconds are not all 0; " ) | 
| 178 |                         .arg(mins).arg(secs).arg(msecs)); | 
| 179 |             } | 
| 180 |             else | 
| 181 |             { | 
| 182 |                 hour = 0; | 
| 183 |                 date = date.addDays(days: 1); | 
| 184 |             } | 
| 185 |         } | 
| 186 |         else if(!QTime::isValid(h: hour, m: mins, s: secs, ms: msecs)) | 
| 187 |         { | 
| 188 |             badData(QtXmlPatterns::tr("Time %1:%2:%3.%4 is invalid." ) | 
| 189 |                          .arg(hour).arg(mins).arg(secs).arg(msecs)); | 
| 190 |         } | 
| 191 |  | 
| 192 |         const QTime time(hour, mins, secs, msecs); | 
| 193 |         Q_ASSERT(time.isValid()); | 
| 194 |  | 
| 195 |         QDateTime result(date, time); | 
| 196 |         setUtcOffset(result, zoResult, zoOffset: offset); | 
| 197 |         return result; | 
| 198 |     } | 
| 199 | } | 
| 200 |  | 
| 201 | ZOTotal AbstractDateTime::parseZoneOffset(ZoneOffsetParseResult &result, | 
| 202 |                                           const QStringList &capts, | 
| 203 |                                           const CaptureTable &captTable) | 
| 204 | { | 
| 205 |     const QString zoneOffsetSignStr(getCapt(zoneOffsetSign)); | 
| 206 |  | 
| 207 |     if(zoneOffsetSignStr.isEmpty()) | 
| 208 |     { | 
| 209 |         const QString zoneOffsetUTCStr(getCapt(zoneOffsetUTCSymbol)); | 
| 210 |         Q_ASSERT(zoneOffsetUTCStr.isEmpty() || zoneOffsetUTCStr == QLatin1String("Z" )); | 
| 211 |  | 
| 212 |         if(zoneOffsetUTCStr.isEmpty()) | 
| 213 |             result = LocalTime; | 
| 214 |         else | 
| 215 |             result = UTC; | 
| 216 |  | 
| 217 |         return 0; | 
| 218 |     } | 
| 219 |  | 
| 220 |     Q_ASSERT(zoneOffsetSignStr == QLatin1String("-" ) || zoneOffsetSignStr == QLatin1String("+" )); | 
| 221 |  | 
| 222 |     const QString zoneOffsetHourStr(getCapt(zoneOffsetHour)); | 
| 223 |     Q_ASSERT(!zoneOffsetHourStr.isEmpty()); | 
| 224 |     const ZOHourProperty zoHour = zoneOffsetHourStr.toInt(); | 
| 225 |  | 
| 226 |     if(zoHour > 14 || zoHour < -14) | 
| 227 |     { | 
| 228 |         result = Error; | 
| 229 |         return 0; | 
| 230 |         /* | 
| 231 |         badZOData(QtXmlPatterns::tr("%1 it is not a valid hour property in a zone offset. " | 
| 232 |                        "It must be less than or equal to 14.").arg(zoHour)); | 
| 233 |                        */ | 
| 234 |     } | 
| 235 |  | 
| 236 |     const QString zoneOffsetMinuteStr(getCapt(zoneOffsetMinute)); | 
| 237 |     Q_ASSERT(!zoneOffsetMinuteStr.isEmpty()); | 
| 238 |     const ZOHourProperty zoMins = zoneOffsetMinuteStr.toInt(); | 
| 239 |  | 
| 240 |     if(zoHour == 14 && zoMins != 0) | 
| 241 |     { | 
| 242 |         /* | 
| 243 |         badZOData(QtXmlPatterns::tr("When the hour property in a zone offset is 14, the minute property " | 
| 244 |                        "must be 0, not %1.").arg(zoMins)); | 
| 245 |                        */ | 
| 246 |         result = Error; | 
| 247 |         return 0; | 
| 248 |     } | 
| 249 |     else if(zoMins > 59 || zoMins < -59) | 
| 250 |     { | 
| 251 |         /* | 
| 252 |         badZOData(QtXmlPatterns::tr("The minute property in a zone offset cannot be larger than 59. " | 
| 253 |                        "%1 is therefore invalid.").arg(zoMins)); | 
| 254 |                        */ | 
| 255 |         result = Error; | 
| 256 |         return 0; | 
| 257 |     } | 
| 258 |  | 
| 259 |     if(zoHour == 0 && zoMins == 0) /* "-00:00" and "+00:00" is equal to 'Z'. */ | 
| 260 |     { | 
| 261 |         result = UTC; | 
| 262 |         return 0; | 
| 263 |     } | 
| 264 |     else | 
| 265 |     { | 
| 266 |         ZOTotal zoneOffset = (zoHour * 60 + zoMins) * 60; | 
| 267 |  | 
| 268 |         if(zoneOffsetSignStr == QChar::fromLatin1(c: '-')) | 
| 269 |             zoneOffset = -zoneOffset; | 
| 270 |  | 
| 271 |         result = Offset; | 
| 272 |         return zoneOffset; | 
| 273 |     } | 
| 274 | } | 
| 275 | //#undef badZOData | 
| 276 |  | 
| 277 | void AbstractDateTime::setUtcOffset(QDateTime &result, | 
| 278 |                                      const ZoneOffsetParseResult zoResult, | 
| 279 |                                      const int zoOffset) | 
| 280 | { | 
| 281 |     if(zoResult == UTC) | 
| 282 |         result.setTimeSpec(Qt::UTC); | 
| 283 |     else if(zoResult == LocalTime) | 
| 284 |         result.setTimeSpec(Qt::LocalTime); | 
| 285 |     else | 
| 286 |     { | 
| 287 |         Q_ASSERT(zoResult == Offset); | 
| 288 |         result.setOffsetFromUtc(zoOffset); | 
| 289 |     } | 
| 290 | } | 
| 291 |  | 
| 292 | #undef badData | 
| 293 | #undef getCapt | 
| 294 | #undef getSafeCapt | 
| 295 |  | 
| 296 | bool AbstractDateTime::isRangeValid(const QDate &date, | 
| 297 |                                     QString &message) | 
| 298 | { | 
| 299 |     if(date.isValid()) | 
| 300 |         return true; | 
| 301 |     else | 
| 302 |     { | 
| 303 |         message = QtXmlPatterns::tr(sourceText: "Overflow: Date can't be represented." ); | 
| 304 |         return false; | 
| 305 |     } | 
| 306 | } | 
| 307 |  | 
| 308 | QString AbstractDateTime::dateToString() const | 
| 309 | { | 
| 310 |     return m_dateTime.toString(format: QLatin1String("yyyy-MM-dd" )); | 
| 311 | } | 
| 312 |  | 
| 313 | QString AbstractDateTime::serializeMSeconds(const MSecondProperty mseconds) | 
| 314 | { | 
| 315 |     QString retval; | 
| 316 |     retval.append(c: QLatin1Char('.')); | 
| 317 |     int div = 100; | 
| 318 |     MSecondProperty msecs = mseconds; | 
| 319 |  | 
| 320 |     while(msecs > 0) | 
| 321 |     { | 
| 322 |         int d = msecs / div; | 
| 323 |         retval.append(c: QLatin1Char(d + '0')); | 
| 324 |         msecs = msecs % div; | 
| 325 |         div = div / 10; | 
| 326 |     } | 
| 327 |  | 
| 328 |     return retval; | 
| 329 | } | 
| 330 |  | 
| 331 | QString AbstractDateTime::timeToString() const | 
| 332 | { | 
| 333 |     QString base(m_dateTime.toString(format: QLatin1String("hh:mm:ss" ))); | 
| 334 |     const MSecondProperty msecs = m_dateTime.time().msec(); | 
| 335 |  | 
| 336 |     if(msecs) | 
| 337 |         base.append(s: serializeMSeconds(mseconds: msecs)); | 
| 338 |  | 
| 339 |     return base; | 
| 340 | } | 
| 341 |  | 
| 342 | QString AbstractDateTime::zoneOffsetToString() const | 
| 343 | { | 
| 344 |     switch(m_dateTime.timeSpec()) | 
| 345 |     { | 
| 346 |         case Qt::LocalTime: | 
| 347 |             return QString(); | 
| 348 |         case Qt::UTC: | 
| 349 |             return QLatin1String("Z" ); | 
| 350 |         default: | 
| 351 |         { | 
| 352 |             Q_ASSERT(m_dateTime.timeSpec() == Qt::OffsetFromUTC); | 
| 353 |  | 
| 354 |             const int zoneOffset = m_dateTime.offsetFromUtc(); | 
| 355 |             Q_ASSERT(zoneOffset != 0); | 
| 356 |             const int posZoneOffset = qAbs(t: zoneOffset); | 
| 357 |  | 
| 358 |             /* zoneOffset is in seconds. */ | 
| 359 |             const int hours = posZoneOffset/(60 * 60); | 
| 360 |             const int minutes = (posZoneOffset % (60 * 60)) / 60; | 
| 361 |  | 
| 362 |             QString result; | 
| 363 |             result.reserve(asize: 6); | 
| 364 |  | 
| 365 |             result.append(c: zoneOffset < 0 ? QLatin1Char('-') : QLatin1Char('+')); | 
| 366 |             result.append(s: QString::number(hours).rightJustified(width: 2, fill: QLatin1Char('0'))); | 
| 367 |             result.append(c: QLatin1Char(':')); | 
| 368 |             result.append(s: QString::number(minutes).rightJustified(width: 2, fill: QLatin1Char('0'))); | 
| 369 |             return result; | 
| 370 |         } | 
| 371 |     } | 
| 372 | } | 
| 373 |  | 
| 374 | void AbstractDateTime::copyTimeSpec(const QDateTime &from, | 
| 375 |                                     QDateTime &to) | 
| 376 | { | 
| 377 |     switch(from.timeSpec()) | 
| 378 |     { | 
| 379 |         case Qt::UTC: | 
| 380 |         /* Fallthrough. */ | 
| 381 |         case Qt::LocalTime: | 
| 382 |         { | 
| 383 |             to.setTimeSpec(from.timeSpec()); | 
| 384 |             return; | 
| 385 |         } | 
| 386 |         case Qt::OffsetFromUTC: | 
| 387 |         { | 
| 388 |             to.setOffsetFromUtc(from.offsetFromUtc()); | 
| 389 |             Q_ASSERT(to.timeSpec() == Qt::OffsetFromUTC); | 
| 390 |             return; | 
| 391 |         } | 
| 392 | #if QT_CONFIG(timezone) | 
| 393 |         case Qt::TimeZone: | 
| 394 |         { | 
| 395 |             to.setTimeZone(from.timeZone()); | 
| 396 |             return; | 
| 397 |         } | 
| 398 | #endif | 
| 399 |     } | 
| 400 | } | 
| 401 |  | 
| 402 | Item AbstractDateTime::fromValue(const QDateTime &) const | 
| 403 | { | 
| 404 |     Q_ASSERT_X(false, Q_FUNC_INFO, | 
| 405 |                "Calling AbstractDateTime::fromDateTime() makes no sense." ); | 
| 406 |     return Item(); | 
| 407 | } | 
| 408 | QT_END_NAMESPACE | 
| 409 |  |