Changeset View
Changeset View
Standalone View
Standalone View
src/KDbDateTime.cpp
- This file was added.
1 | /* This file is part of the KDE project | ||||
---|---|---|---|---|---|
2 | Copyright (C) 2018 Jarosław Staniek <staniek@kde.org> | ||||
3 | | ||||
4 | This library is free software; you can redistribute it and/or | ||||
5 | modify it under the terms of the GNU Library General Public | ||||
6 | License as published by the Free Software Foundation; either | ||||
7 | version 2 of the License, or (at your option) any later version. | ||||
8 | | ||||
9 | This library is distributed in the hope that it will be useful, | ||||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
12 | Library General Public License for more details. | ||||
13 | | ||||
14 | You should have received a copy of the GNU Library General Public License | ||||
15 | along with this library; see the file COPYING.LIB. If not, write to | ||||
16 | * Boston, MA 02110-1301, USA. | ||||
17 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
18 | */ | ||||
19 | | ||||
20 | #include "KDbDateTime.h" | ||||
21 | | ||||
22 | #include <QRegularExpression> | ||||
23 | | ||||
24 | const int UNCACHED_YEAR = -1; | ||||
25 | const int INVALID_YEAR = -2; | ||||
26 | | ||||
27 | namespace { | ||||
28 | template <typename T> | ||||
29 | std::function<QString(const T&)> byteArrayToString() | ||||
30 | { | ||||
31 | return [](const T &v) { return QString::fromLatin1(v.toString()); }; | ||||
32 | } | ||||
33 | | ||||
34 | struct KDbDateTimeMetatypeInitializer { | ||||
35 | KDbDateTimeMetatypeInitializer() | ||||
36 | { | ||||
37 | using namespace std::placeholders; | ||||
38 | QMetaType::registerConverter<KDbYear, QString>(byteArrayToString<KDbYear>()); | ||||
39 | QMetaType::registerConverter<KDbYear, int>(std::bind(&KDbYear::toIsoValue, _1)); | ||||
40 | QMetaType::registerConverter<KDbDate, QString>(byteArrayToString<KDbDate>()); | ||||
41 | QMetaType::registerConverter<KDbDate, QDate>(std::bind(&KDbDate::toQDate, _1)); | ||||
42 | QMetaType::registerConverter<KDbTime, QString>(byteArrayToString<KDbTime>()); | ||||
43 | QMetaType::registerConverter<KDbTime, QTime>(std::bind(&KDbTime::toQTime, _1)); | ||||
44 | QMetaType::registerConverter<KDbDateTime, QString>(byteArrayToString<KDbDateTime>()); | ||||
45 | QMetaType::registerConverter<KDbDateTime, QDateTime>(std::bind(&KDbDateTime::toQDateTime, _1)); | ||||
46 | QMetaType::registerComparators<KDbYear>(); | ||||
47 | QMetaType::registerComparators<KDbDate>(); | ||||
48 | QMetaType::registerComparators<KDbTime>(); | ||||
49 | QMetaType::registerComparators<KDbDateTime>(); | ||||
50 | } | ||||
51 | }; | ||||
52 | | ||||
53 | KDbDateTimeMetatypeInitializer s_init; | ||||
54 | } | ||||
55 | | ||||
56 | bool KDbYear::operator==(const KDbYear &other) const | ||||
57 | { | ||||
58 | return m_sign == other.sign() && m_string == other.yearString(); | ||||
59 | } | ||||
60 | | ||||
61 | bool KDbYear::operator<(const KDbYear &other) const | ||||
62 | { | ||||
63 | return toQDateValue() < other.toQDateValue(); | ||||
64 | } | ||||
65 | | ||||
66 | bool KDbYear::isValid() const | ||||
67 | { | ||||
68 | return std::get<1>(intValue()); | ||||
69 | } | ||||
70 | | ||||
71 | bool KDbYear::isNull() const | ||||
72 | { | ||||
73 | return m_sign == Sign::None && m_string.isEmpty(); | ||||
74 | } | ||||
75 | | ||||
76 | QByteArray KDbYear::signString() const | ||||
77 | { | ||||
78 | QByteArray result; | ||||
79 | switch (m_sign) { | ||||
80 | case Sign::Plus: | ||||
81 | result = QByteArrayLiteral("+"); | ||||
82 | break; | ||||
83 | case Sign::Minus: | ||||
84 | result = QByteArrayLiteral("-"); | ||||
85 | break; | ||||
86 | default: | ||||
87 | break; | ||||
88 | } | ||||
89 | return result; | ||||
90 | } | ||||
91 | | ||||
92 | KDB_EXPORT QDebug operator<<(QDebug dbg, KDbYear::Sign sign) | ||||
93 | { | ||||
94 | QDebugStateSaver saver(dbg); | ||||
95 | switch (sign) { | ||||
96 | case KDbYear::Sign::None: | ||||
97 | break; | ||||
98 | case KDbYear::Sign::Plus: | ||||
99 | dbg.nospace() << '+'; | ||||
100 | break; | ||||
101 | case KDbYear::Sign::Minus: | ||||
102 | dbg.nospace() << '-'; | ||||
103 | break; | ||||
104 | } | ||||
105 | return dbg.maybeSpace(); | ||||
106 | } | ||||
107 | | ||||
108 | KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbYear& year) | ||||
109 | { | ||||
110 | QDebugStateSaver saver(dbg); | ||||
111 | dbg.nospace().noquote() << "KDbYear(" << year.sign() << year.yearString(); | ||||
112 | if (!year.isValid()) { | ||||
113 | dbg.nospace() << " INVALID"; | ||||
114 | } | ||||
115 | dbg.nospace() << ")"; | ||||
116 | return dbg.maybeSpace(); | ||||
117 | } | ||||
118 | | ||||
119 | QByteArray KDbYear::toString() const | ||||
120 | { | ||||
121 | QByteArray result; | ||||
122 | if (isNull()) { | ||||
123 | result = QByteArrayLiteral("<NULL_YEAR>"); | ||||
124 | } else { // can be invalid, that's OK | ||||
125 | result = signString() + m_string; | ||||
126 | } | ||||
127 | return result; | ||||
128 | } | ||||
129 | | ||||
130 | int KDbYear::toIsoValue() const | ||||
131 | { | ||||
132 | return std::get<0>(intValue()); | ||||
133 | } | ||||
134 | | ||||
135 | int KDbYear::toQDateValue() const | ||||
136 | { | ||||
137 | int result; | ||||
138 | bool ok; | ||||
139 | std::tie(result, ok) = intValue(); | ||||
140 | if (!ok) { | ||||
141 | return 0; | ||||
142 | } | ||||
143 | if (result > 0) { | ||||
144 | return result; | ||||
145 | } | ||||
146 | return result - 1; | ||||
147 | } | ||||
148 | | ||||
149 | namespace { | ||||
150 | int intValueInternal(KDbYear::Sign sign, const QByteArray &string) | ||||
151 | { | ||||
152 | const int length = string.length(); | ||||
153 | if (length < 4) { | ||||
154 | // TODO message: at least 4 digits required | ||||
155 | return INVALID_YEAR; | ||||
156 | } else if (length > 4) { | ||||
157 | if (sign == KDbYear::Sign::None) { | ||||
158 | // TODO message: more than 4 digits, sign required | ||||
159 | return INVALID_YEAR; | ||||
160 | } | ||||
161 | } | ||||
162 | | ||||
163 | static QRegularExpression digitsRegExp(QStringLiteral("^\\d+$")); | ||||
164 | if (!digitsRegExp.match(QString::fromLatin1(string)).hasMatch()) { | ||||
165 | // TODO message: only digits are accepted for year | ||||
166 | return INVALID_YEAR; | ||||
167 | } | ||||
168 | | ||||
169 | bool ok; | ||||
170 | int result = string.toInt(&ok); | ||||
171 | if (!ok || result < 0) { | ||||
172 | // TODO message: failed to convert year to integer >= 0 | ||||
173 | return INVALID_YEAR; | ||||
174 | } | ||||
175 | int qDateYear; | ||||
176 | if (result == 0) { | ||||
177 | if (sign != KDbYear::Sign::Plus) { | ||||
178 | // TODO message: + required for 0000 | ||||
179 | return INVALID_YEAR; | ||||
180 | } | ||||
181 | qDateYear = -1; | ||||
182 | } else if (sign == KDbYear::Sign::Minus) { | ||||
183 | qDateYear = - result - 1; | ||||
184 | } else { // Plus or None | ||||
185 | qDateYear = result; | ||||
186 | } | ||||
187 | // verify if this year is within the limits of QDate (see QDate::minJd(), QDate::maxJd()) | ||||
188 | if (!QDate(qDateYear, 1, 1).isValid()) { | ||||
189 | // TODO message: year is not within limits | ||||
190 | return INVALID_YEAR; | ||||
191 | } | ||||
192 | return result; | ||||
193 | } | ||||
194 | } | ||||
195 | | ||||
196 | std::tuple<int, bool> KDbYear::intValue() const | ||||
197 | { | ||||
198 | if (m_isoValue == UNCACHED_YEAR) { | ||||
199 | const_cast<int&>(m_isoValue) = intValueInternal(m_sign, m_string); // cache | ||||
200 | } | ||||
201 | if (m_isoValue == INVALID_YEAR) { | ||||
202 | return std::make_tuple(0, false); | ||||
203 | } | ||||
204 | return std::make_tuple(m_sign == Sign::Minus ? -m_isoValue : m_isoValue, true); | ||||
205 | } | ||||
206 | | ||||
207 | bool KDbDate::operator==(const KDbDate &other) const | ||||
208 | { | ||||
209 | return m_year == other.year() && m_monthString == other.monthString() | ||||
210 | && m_dayString == other.dayString(); | ||||
211 | } | ||||
212 | | ||||
213 | bool KDbDate::operator<(const KDbDate &other) const | ||||
214 | { | ||||
215 | return toQDate() < other.toQDate(); | ||||
216 | } | ||||
217 | | ||||
218 | bool KDbDate::isValid() const | ||||
219 | { | ||||
220 | return toQDate().isValid(); | ||||
221 | } | ||||
222 | | ||||
223 | bool KDbDate::isNull() const | ||||
224 | { | ||||
225 | return m_year.isNull() && m_monthString.isEmpty() && m_dayString.isEmpty(); | ||||
226 | } | ||||
227 | | ||||
228 | QDate KDbDate::toQDate() const | ||||
229 | { | ||||
230 | return { m_year.toQDateValue(), month(), day() }; | ||||
231 | } | ||||
232 | | ||||
233 | namespace { | ||||
234 | int toInt(const QByteArray &string, int min, int max, int minLength, int maxLength) | ||||
235 | { | ||||
236 | if (string.length() < minLength || string.length() > maxLength) { | ||||
237 | // TODO message: invalid length | ||||
238 | return -1; | ||||
239 | } | ||||
240 | bool ok = true; | ||||
241 | const int result = string.isEmpty() ? 0 : string.toInt(&ok); | ||||
242 | if (!ok || result < min || result > max) { | ||||
243 | // TODO message: could not convert string to integer | ||||
244 | return -1; | ||||
245 | } | ||||
246 | return result; | ||||
247 | } | ||||
248 | } | ||||
249 | | ||||
250 | int KDbDate::month() const | ||||
251 | { | ||||
252 | return toInt(m_monthString, 1, 12, 1, 2); | ||||
253 | } | ||||
254 | | ||||
255 | int KDbDate::day() const | ||||
256 | { | ||||
257 | return toInt(m_dayString, 1, 31, 1, 2); | ||||
258 | } | ||||
259 | | ||||
260 | QByteArray KDbDate::toString() const | ||||
261 | { | ||||
262 | QByteArray result; | ||||
263 | if (isNull()) { | ||||
264 | result = QByteArrayLiteral("<NULL_DATE>"); | ||||
265 | } else { // can be invalid, that's OK | ||||
266 | result = m_year.toString() + '-' + m_monthString + '-' + m_dayString; | ||||
267 | } | ||||
268 | return result; | ||||
269 | } | ||||
270 | | ||||
271 | KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDate &date) | ||||
272 | { | ||||
273 | QDebugStateSaver saver(dbg); | ||||
274 | dbg.nospace().noquote() << "KDbDate(" << date.toString(); | ||||
275 | if (!date.isValid()) { | ||||
276 | dbg.nospace() << " INVALID"; | ||||
277 | } | ||||
278 | dbg.nospace() << ")"; | ||||
279 | return dbg.maybeSpace(); | ||||
280 | } | ||||
281 | | ||||
282 | bool KDbTime::operator==(const KDbTime &other) const | ||||
283 | { | ||||
284 | return m_hourString == other.hourString() && m_minuteString == other.minuteString() | ||||
285 | && m_secondString == other.secondString() && m_msecString == other.msecString() | ||||
286 | && m_period == other.period(); | ||||
287 | } | ||||
288 | | ||||
289 | bool KDbTime::operator<(const KDbTime &other) const | ||||
290 | { | ||||
291 | return toQTime() < other.toQTime(); | ||||
292 | } | ||||
293 | | ||||
294 | QTime KDbTime::toQTime() const | ||||
295 | { | ||||
296 | // Rules for hours based on https://www.timeanddate.com/time/am-and-pm.html#converting | ||||
297 | int h = hour(); | ||||
298 | if (h == -1) { | ||||
299 | return {}; | ||||
300 | } | ||||
301 | const int m = minute(); | ||||
302 | if (m == -1) { | ||||
303 | return {}; | ||||
304 | } | ||||
305 | const int s = second(); | ||||
306 | if (s == -1) { | ||||
307 | return {}; | ||||
308 | } | ||||
309 | const int ms = msec(); | ||||
310 | if (ms == -1) { | ||||
311 | return {}; | ||||
312 | } | ||||
313 | if (m_period == Period::None) { | ||||
314 | return { h, m, s, ms }; | ||||
315 | } | ||||
316 | return QTime::fromString( | ||||
317 | QStringLiteral("%1:%2:%3.%4 %5") | ||||
318 | .arg(h) | ||||
319 | .arg(m) | ||||
320 | .arg(s) | ||||
321 | .arg(ms) | ||||
322 | .arg(m_period == Period::Am ? QLatin1String("AM") : QLatin1String("PM")), | ||||
323 | QStringLiteral("h:m:s.z AP")); | ||||
324 | } | ||||
325 | | ||||
326 | int KDbTime::hour() const | ||||
327 | { | ||||
328 | switch (m_period) { | ||||
329 | case Period::None: | ||||
330 | return toInt(m_hourString, 0, 23, 1, 2); | ||||
331 | case Period::Am: | ||||
332 | case Period::Pm: | ||||
333 | return toInt(m_hourString, 1, 12, 1, 2); | ||||
334 | } | ||||
335 | return -1; | ||||
336 | } | ||||
337 | | ||||
338 | int KDbTime::minute() const | ||||
339 | { | ||||
340 | return toInt(m_minuteString, 0, 59, 1, 2); | ||||
341 | } | ||||
342 | | ||||
343 | int KDbTime::second() const | ||||
344 | { | ||||
345 | return toInt(m_secondString, 0, 59, 0, 2); | ||||
346 | } | ||||
347 | | ||||
348 | int KDbTime::msec() const | ||||
349 | { | ||||
350 | return toInt(m_msecString, 0, 999, 0, 3); | ||||
351 | } | ||||
352 | | ||||
353 | bool KDbTime::isValid() const | ||||
354 | { | ||||
355 | return toQTime().isValid(); | ||||
356 | } | ||||
357 | | ||||
358 | bool KDbTime::isNull() const | ||||
359 | { | ||||
360 | return m_hourString.isEmpty() || m_minuteString.isEmpty(); | ||||
361 | } | ||||
362 | | ||||
363 | QByteArray KDbTime::toString() const | ||||
364 | { | ||||
365 | QByteArray result; | ||||
366 | if (isNull()) { | ||||
367 | result = QByteArrayLiteral("<NULL_TIME>"); | ||||
368 | } else if (m_msecString.isEmpty()) { // can be invalid, that's OK | ||||
369 | if (m_secondString.isEmpty()) { | ||||
370 | result = m_hourString + ':' + m_minuteString; | ||||
371 | } else { | ||||
372 | result = m_hourString + ':' + m_minuteString + ':' + m_secondString; | ||||
373 | } | ||||
374 | } else { // can be invalid, that's OK | ||||
375 | result = m_hourString + ':' + m_minuteString + ':' + m_secondString + '.' + m_msecString; | ||||
376 | } | ||||
377 | switch (m_period) { | ||||
378 | case KDbTime::Period::Am: | ||||
379 | result += " AM"; | ||||
380 | break; | ||||
381 | case KDbTime::Period::Pm: | ||||
382 | result += " PM"; | ||||
383 | break; | ||||
384 | default: | ||||
385 | break; | ||||
386 | } | ||||
387 | return result; | ||||
388 | } | ||||
389 | | ||||
390 | KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTime &time) | ||||
391 | { | ||||
392 | QDebugStateSaver saver(dbg); | ||||
393 | dbg.nospace().noquote() << "KDbTime(" << time.toString(); | ||||
394 | if (!time.isValid()) { | ||||
395 | dbg.nospace() << " INVALID"; | ||||
396 | } | ||||
397 | dbg.nospace() << ")"; | ||||
398 | return dbg.maybeSpace(); | ||||
399 | } | ||||
400 | | ||||
401 | bool KDbDateTime::operator==(const KDbDateTime &other) const | ||||
402 | { | ||||
403 | return date() == other.date() && time() == other.time(); | ||||
404 | } | ||||
405 | | ||||
406 | bool KDbDateTime::operator<(const KDbDateTime &other) const | ||||
407 | { | ||||
408 | return toQDateTime() < other.toQDateTime(); | ||||
409 | } | ||||
410 | | ||||
411 | bool KDbDateTime::isValid() const | ||||
412 | { | ||||
413 | return m_date.isValid() && m_time.isValid(); | ||||
414 | } | ||||
415 | | ||||
416 | bool KDbDateTime::isNull() const | ||||
417 | { | ||||
418 | return m_date.isNull() || m_time.isNull(); | ||||
419 | } | ||||
420 | | ||||
421 | QDateTime KDbDateTime::toQDateTime() const | ||||
422 | { | ||||
423 | return { m_date.toQDate(), m_time.toQTime() }; | ||||
424 | } | ||||
425 | | ||||
426 | QByteArray KDbDateTime::toString() const | ||||
427 | { | ||||
428 | QByteArray result; | ||||
429 | if (isNull()) { | ||||
430 | result = QByteArrayLiteral("<NULL_DATETIME>"); | ||||
431 | } else { | ||||
432 | result = m_date.toString() + ' ' + m_time.toString(); // can be invalid, that's OK | ||||
433 | } | ||||
434 | return result; | ||||
435 | } | ||||
436 | | ||||
437 | KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbDateTime &dateTime) | ||||
438 | { | ||||
439 | QDebugStateSaver saver(dbg); | ||||
440 | dbg.nospace().noquote() << "KDbDateTime(" << dateTime.toString(); | ||||
441 | if (!dateTime.isValid()) { | ||||
442 | dbg.nospace() << "INVALID"; | ||||
443 | } | ||||
444 | dbg.nospace() << ")"; | ||||
445 | return dbg.maybeSpace(); | ||||
446 | } |