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
52QT_BEGIN_NAMESPACE
53
54using namespace QPatternist;
55
56AbstractDateTime::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
65QDateTime 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
201ZOTotal 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
277void 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
296bool 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
308QString AbstractDateTime::dateToString() const
309{
310 return m_dateTime.toString(format: QLatin1String("yyyy-MM-dd"));
311}
312
313QString 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
331QString 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
342QString 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
374void 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
402Item 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}
408QT_END_NAMESPACE
409

source code of qtxmlpatterns/src/xmlpatterns/data/qabstractdatetime.cpp