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