Engauge Digitizer 2
Loading...
Searching...
No Matches
FormatDateTime.cpp
Go to the documentation of this file.
1/******************************************************************************************************
2 * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3 * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4 * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5 ******************************************************************************************************/
6
7#include "EngaugeAssert.h"
8#include "FormatDateTime.h"
9#include "Logger.h"
10#include <QDateTime>
11#include <qmath.h>
12#include <QTimeZone>
13
15{
16 loadFormatsFormat();
17 loadFormatsParseAcceptable();
18 loadFormatsParseIncomplete();
19}
20
21bool FormatDateTime::ambiguityBetweenDateAndTime (CoordUnitsDate coordUnitsDate,
22 CoordUnitsTime coordUnitsTime,
23 const QString &string) const
24{
25 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::ambiguityBetweenDateAndTime";
26
27 bool ambiguous = false;
28
29 // There is no ambiguity if user specified either date or time as empty
30 if (coordUnitsDate != COORD_UNITS_DATE_SKIP &&
31 coordUnitsTime != COORD_UNITS_TIME_SKIP) {
32
33 // See if there is just a single number
34 QStringList fields = string.trimmed().split(QRegularExpression ("[/- :]"));
35
36 if (fields.count() == 1) {
37
38 // There is a single number. Since there are no attached delimiters to differentiate a date versus
39 // a time, this means the number is ambiguous
40 ambiguous = true;
41 }
42 }
43
44 return ambiguous;
45}
46
47void FormatDateTime::dateTimeLookup (const FormatsDate &formatsDateAll,
48 const FormatsTime &formatsTimeAll,
49 CoordUnitsDate coordUnitsDate,
50 CoordUnitsTime coordUnitsTime,
51 const QString &string,
52 bool useQDateTimeElseQRegExp,
53 double &value,
54 bool &success) const
55{
56 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::dateTimeLookup";
57
58 success = false;
59
60 ENGAUGE_ASSERT (formatsDateAll.contains (coordUnitsDate));
61 ENGAUGE_ASSERT (formatsTimeAll.contains (coordUnitsTime));
62
63 QStringList formatsDate = formatsDateAll [coordUnitsDate];
64 QStringList formatsTime = formatsTimeAll [coordUnitsTime];
65
66 // Loop through the legal date/time combinations
67 QStringList::const_iterator itrDate, itrTime;
68 bool iterating = true;
69 for (itrDate = formatsDate.begin(); itrDate != formatsDate.end() && iterating; itrDate++) {
70
71 QString formatDate = *itrDate;
72
73 for (itrTime = formatsTime.begin(); itrTime != formatsTime.end() && iterating; itrTime++) {
74
75 QString formatTime = *itrTime;
76
77 // Insert space as separator only if needed. Do not use trim around here since formatDate may or may not end in a space
78 QString separator = (!formatDate.isEmpty() && !formatTime.isEmpty() ? " " : "");
79
80 QString formatDateTime = formatDate + separator + formatTime;
81
82 if (!formatDateTime.isEmpty()) {
83
84 // Try parsing according to the current format
85 if (useQDateTimeElseQRegExp) {
86
87 QDateTime dt = QDateTime::fromString (string,
88 formatDateTime);
89
90 if (dt.isValid() && !ambiguityBetweenDateAndTime (coordUnitsDate,
91 coordUnitsTime,
92 string)) {
93
94 success = true;
95 // Convert using local time to prevent addition of utc offset. Number of seconds since 1970 epoch
96 // is used with 64 bits resolution, versus time_t with only 32 bits resolution (and limites to 1970 to 2038).
97 // Time value is negative for pre-epoch. Grep for fromSecsSinceEpoch in this same file
98 value = toSecsSinceEpoch (dt.toLocalTime ());
99 iterating = false; // Stop iterating
100
101 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::dateTimeLookup"
102 << " string=" << string.toLatin1().data()
103 << " qDateTimeFormatMatched=" << formatDateTime.toLatin1().data()
104 << " value=" << value
105 << " stringQDateTime=" << dt.toString().toLatin1().data();
106
107 }
108 } else {
109
110 QRegularExpression reg (formatDateTime);
111 if (reg.match(string).hasMatch()) {
112
113 success = true; // Note that value does not get set in QRegExp case
114 iterating = false; // Stop iterating
115
116 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::dateTimeLookup"
117 << " string=" << string.toLatin1().data()
118 << " regExpMatched=" << formatDateTime.toLatin1().data();
119
120 }
121 }
122 }
123 }
124 }
125}
126
128 CoordUnitsTime coordUnitsTime,
129 double value) const
130{
131 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::formatOutput"
132 << " value=" << value;
133
134 ENGAUGE_ASSERT (m_formatsDateFormat.contains (coordUnitsDate));
135 ENGAUGE_ASSERT (m_formatsTimeFormat.contains (coordUnitsTime));
136
137 QString format = m_formatsDateFormat [coordUnitsDate] + " " + m_formatsTimeFormat [coordUnitsTime];
138 format = format.trimmed();
139
140 // We are using 64 bits resolution on seconds from epoch rather than time_t which has 32 bits resolution (and
141 // is therefore limited to 1970 to 2038). Time value is negative for pre-epoch. Grep for toSecsSinceEpoch in this same file
142 QDateTime dt;
143 dt = fromSecsSinceEpoch (static_cast<qint64> (value));
144
145 return dt.toLocalTime ().toString (format); // Convert using local time to prevent addition of utc offset
146}
147
148QDateTime FormatDateTime::fromSecsSinceEpoch (qint64 secs) const
149{
150 return QDateTime::fromMSecsSinceEpoch (secs * 1000);
151}
152
153void FormatDateTime::loadFormatsFormat()
154{
155 m_formatsDateFormat [COORD_UNITS_DATE_SKIP] = "";
156 m_formatsDateFormat [COORD_UNITS_DATE_MONTH_DAY_YEAR] = "MM/dd/yyyy";
157 m_formatsDateFormat [COORD_UNITS_DATE_DAY_MONTH_YEAR] = "dd/MM/yyyy";
158 m_formatsDateFormat [COORD_UNITS_DATE_YEAR_MONTH_DAY] = "yyyy/MM/dd";
159
160 ENGAUGE_ASSERT (m_formatsDateFormat.count () == NUM_COORD_UNITS_DATE);
161
162 m_formatsTimeFormat [COORD_UNITS_TIME_SKIP] = "";
163 m_formatsTimeFormat [COORD_UNITS_TIME_HOUR_MINUTE] = "hh/mm";
164 m_formatsTimeFormat [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = "hh:mm:ss";
165
166 ENGAUGE_ASSERT (m_formatsTimeFormat.count () == NUM_COORD_UNITS_TIME);
167}
168
169void FormatDateTime::loadFormatsParseAcceptable()
170{
171 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::loadFormatsParseAcceptable";
172
173 QStringList skip, dayMonth, dayMonthYear, monthDay, monthDayYear, yearMonth, yearMonthDay;
174
175 // COORD_UNITS_DATE_SKIP and COORD_UNITS_TIME_SKIP allow date/time respectively even when skipped,
176 // although there can be ambiguity with between COORD_UNITS_DATE_MONTH_DAY_YEAR and COORD_UNITS_DATE_DAY_MONTH_YEAR
177 skip << "";
178
179 dayMonth << "d/M"
180 << "d-M"
181 << "d/MM"
182 << "d-MM"
183 << "d/MMM"
184 << "d-MMM"
185 << "d/MMMM"
186 << "d-MMMM"
187 << "dd/M"
188 << "dd-M"
189 << "dd/M"
190 << "dd-M"
191 << "dd/MM"
192 << "dd-MM"
193 << "dd/MMM"
194 << "dd-MMM"
195 << "dd/MMMM"
196 << "dd-MMMM";
197 dayMonthYear << "d/M/yyyy"
198 << "d-M-yyyy"
199
200 << "d/MM/yyyy"
201 << "d-MM-yyyy"
202 << "d/MMM/yyyy"
203 << "d-MMM-yyyy"
204 << "d MMM yyyy"
205 << "d/MMMM/yyyy"
206 << "d-MMMM-yyyy"
207 << "d MMMM yyyy"
208
209 << "dd/MM/yyyy"
210 << "dd-MM-yyyy"
211 << "dd/MMM/yyyy"
212 << "dd-MMM-yyyy"
213 << "dd MMM yyyy"
214 << "dd/MMMM/yyyy"
215 << "dd-MMMM-yyyy"
216 << "dd MMMM yyyy";
217 monthDay << "M/d"
218 << "M-d"
219 << "M d"
220 << "M/dd"
221 << "M-dd"
222 << "M dd"
223 << "MM/d"
224 << "MM-d"
225 << "MM d"
226 << "MM/dd"
227 << "MM-dd"
228 << "MM dd"
229 << "MMM/d"
230 << "MMM-d"
231 << "MMM d"
232 << "MMM/dd"
233 << "MMM-dd"
234 << "MMM dd"
235 << "MMMM/d"
236 << "MMMM-d"
237 << "MMMM d"
238 << "MMMM/dd"
239 << "MMMM-dd"
240 << "MMMM dd";
241 monthDayYear << "M/d/yyyy"
242 << "M-d-yyyy"
243 << "M d yyyy"
244 << "M/dd/yyyy"
245 << "M-dd-yyyy"
246 << "M dd yyyy"
247 << "MM/d/yyyy"
248 << "MM-d-yyyy"
249 << "MM d yyyy"
250 << "MM/dd/yyyy"
251 << "MM-dd-yyyy"
252 << "MM dd yyyy"
253 << "MMM/d/yyyy"
254 << "MMM-d-yyyy"
255 << "MMM d yyyy"
256 << "MMM/dd/yyyy"
257 << "MMM-dd-yyyy"
258 << "MMM dd yyyy"
259 << "MMMM/d/yyyy"
260 << "MMMM-d-yyyy"
261 << "MMMM d"
262 << "MMMM/dd"
263 << "MMMM-dd"
264 << "MMMM dd";
265 yearMonth << "yyyy/M"
266 << "yyyy-M"
267 << "yyyy M"
268 << "yyyy/MM"
269 << "yyyy-MM"
270 << "yyyy MM"
271 << "yyyy/MMM"
272 << "yyyy-MMM"
273 << "yyyy MMM"
274 << "yyyy/MMMM"
275 << "yyyy-MMMM"
276 << "yyyy MMMM";
277 yearMonthDay << "yyyy/M/d"
278 << "yyyy-M-d"
279 << "yyyy M d"
280 << "yyyy/M/dd"
281 << "yyyy-M-dd"
282 << "yyyy M dd"
283 << "yyyy/MM/dd"
284 << "yyyy-MM-dd"
285 << "yyyy MM dd"
286 << "yyyy/MMM/d"
287 << "yyyy-MMM-d"
288 << "yyyy MMM d"
289 << "yyyy/MMM/dd"
290 << "yyyy-MMM-dd"
291 << "yyyy MMM dd"
292 << "yyyy/MMMM/dd"
293 << "yyyy-MMMM-dd"
294 << "yyyy MMMM dd";
295
296 // Potential day-month ambiguity for COORD_UNITS_DATE_SKIP gets treated as month/day
297 m_formatsDateParseAcceptable [COORD_UNITS_DATE_SKIP] = skip + monthDay + monthDayYear + yearMonthDay;
298 m_formatsDateParseAcceptable [COORD_UNITS_DATE_MONTH_DAY_YEAR] = skip + monthDay + monthDayYear + yearMonthDay;
299 m_formatsDateParseAcceptable [COORD_UNITS_DATE_DAY_MONTH_YEAR] = skip + dayMonth + dayMonthYear + yearMonthDay;
300 m_formatsDateParseAcceptable [COORD_UNITS_DATE_YEAR_MONTH_DAY] = skip + yearMonth + yearMonthDay;
301
302 ENGAUGE_ASSERT (m_formatsDateParseAcceptable.count () == NUM_COORD_UNITS_DATE);
303
304 QStringList hour, hourMinute, hourMinuteSecond, hourMinutePm, hourMinuteSecondPm;
305
306 hour << "hh";
307 hourMinute << "hh:mm";
308 hourMinuteSecond << "hh:mm:ss";
309 hourMinutePm << "hh:mmA"
310 << "hh:mm A"
311 << "hh:mma"
312 << "hh:mm a";
313 hourMinuteSecondPm << "hh:mm:ssA"
314 << "hh:mm:ss A"
315 << "hh:mm:ssa"
316 << "hh:mm:ss a";
317
318 m_formatsTimeParseAcceptable [COORD_UNITS_TIME_SKIP] = skip + hour + hourMinute + hourMinuteSecond + hourMinutePm + hourMinuteSecondPm;
319 m_formatsTimeParseAcceptable [COORD_UNITS_TIME_HOUR_MINUTE] = skip + hour + hourMinute + hourMinutePm + hourMinuteSecond + hourMinuteSecondPm;
320 m_formatsTimeParseAcceptable [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = skip + hour + hourMinute + hourMinutePm + hourMinuteSecond + hourMinuteSecondPm;
321
322 ENGAUGE_ASSERT (m_formatsTimeParseAcceptable.count () == NUM_COORD_UNITS_TIME);
323}
324
325void FormatDateTime::loadFormatsParseIncomplete()
326{
327 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::loadFormatsParseIncomplete";
328
329 QStringList skip, day, dayMonth, dayMonthYear, month, monthDay, monthDayYear, year, yearMonth, yearMonthDay;
330
331 // COORD_UNITS_DATE_SKIP and COORD_UNITS_TIME_SKIP allow date/time respectively even when skipped,
332 // although there can be ambiguity with between COORD_UNITS_DATE_MONTH_DAY_YEAR and COORD_UNITS_DATE_DAY_MONTH_YEAR
333 skip << "";
334
335 // IMPORTANT! Be sure to include complete date values since the date, which goes before the time, will be
336 // complete when the time is getting
337 day << "\\d{1,2}"
338 << "\\d{1,2}/"
339 << "\\d{1,2}-";
340 dayMonth << "\\d{1,2}/\\d{1,2}"
341 << "\\d{1,2}/\\d{1,2} "
342 << "\\d{1,2}/\\d{1,2}/"
343 << "\\d{1,2}-\\d{1,2}-"
344 << "\\d{1,2}/[a-zA-Z]{1,12}/"
345 << "\\d{1,2}-[a-zA-Z]{1,12}-"
346 << "\\d{1,2} [a-zA-Z]{1,12} ";
347 dayMonthYear << "\\d{1,2}/\\d{1,2}/\\d{1,4}"
348 << "\\d{1,2}/\\d{1,2}/\\d{1,4} "
349 << "\\d{1,2}-\\d{1,2}-\\d{1,4}"
350 << "\\d{1,2}-\\d{1,2}-\\d{1,4} ";
351 month << "\\d{1,2}"
352 << "\\d{1,2}/"
353 << "[a-zA-Z]{1,12}"
354 << "[a-zA-Z]{1,12} ";
355 monthDay << "\\d{1,2}/\\d{1,2}"
356 << "\\d{1,2}/\\d{1,2} "
357 << "\\d{1,2}/\\d{1,2}/"
358 << "\\d{1,2} \\d{1,2}"
359 << "\\d{1,2} \\d{1,2} "
360 << "\\d{1,2}-\\d{1,2}-"
361 << "[a-zA-Z]{1,12}"
362 << "[a-zA-Z]{1,12} "
363 << "[a-zA-Z]{1,12} \\d{1,2}"
364 << "[a-zA-Z]{1,12} \\d{1,2} ";
365 monthDayYear << "\\d{1,2}/\\d{1,2}/\\d{1,4}"
366 << "\\d{1,2}/\\d{1,2}/\\d{1,4} "
367 << "\\d{1,2}-\\d{1,2}-\\d{1,4}"
368 << "\\d{1,2}-\\d{1,2}-\\d{1,4} "
369 << "\\d{1,2} \\d{1,2} \\d{1,4}"
370 << "\\d{1,2} \\d{1,2} \\d{1,4} ";
371 year << "\\d{1,4}"
372 << "\\d{1,4} "
373 << "\\d{1,4}/"
374 << "\\d{1,4}-";
375 yearMonth << "\\d{4}/\\d{1,2}"
376 << "\\d{4}/\\d{1,2} "
377 << "\\d{4}/\\d{1,2}/"
378 << "\\d{4}-\\d{1,2}"
379 << "\\d{4}-\\d{1,2} "
380 << "\\d{4}-\\d{1,2}-"
381 << "\\d{4} \\d{1,2}"
382 << "\\d{4} \\d{1,2} "
383 << "\\d{4}/[a-zA-Z]{1,12}"
384 << "\\d{4}/[a-zA-Z]{1,12} "
385 << "\\d{4}/[a-zA-Z]{1,12}/"
386 << "\\d{4}-[a-zA-Z]{1,12}"
387 << "\\d{4}-[a-zA-Z]{1,12} "
388 << "\\d{4}-[a-zA-Z]{1,12}-"
389 << "\\d{4} [a-zA-Z]{1,12}"
390 << "\\d{4} [a-zA-Z]{1,12} ";
391 yearMonthDay << "\\d{4}/\\d{1,2}/\\d{1,2}"
392 << "\\d{4}/\\d{1,2}/\\d{1,2} "
393 << "\\d{4}/\\d{1,2}-\\d{1,2}"
394 << "\\d{4}/\\d{1,2}-\\d{1,2} "
395 << "\\d{4} \\d{1,2} \\d{1,2}"
396 << "\\d{4}/[a-zA-Z]{1,12}/\\d{1,2}"
397 << "\\d{4}/[a-zA-Z]{1,12}/\\d{1,2} "
398 << "\\d{4}-[a-zA-Z]{1,12}-\\d{1,2}"
399 << "\\d{4}-[a-zA-Z]{1,12}-\\d{1,2} ";
400
401 // For every entry, the possible states leading up to the Acceptable states in m_formatsDateParseIncomplete are all included.
402 // Potential day-month ambiguity for COORD_UNITS_DATE_SKIP gets treated as month/day.
403 m_formatsDateParseIncomplete [COORD_UNITS_DATE_SKIP] = skip + month + monthDay + monthDayYear + year + yearMonth + yearMonthDay;
404 m_formatsDateParseIncomplete [COORD_UNITS_DATE_MONTH_DAY_YEAR] = skip + month + monthDay + monthDayYear + year + yearMonth + yearMonthDay;
405 m_formatsDateParseIncomplete [COORD_UNITS_DATE_DAY_MONTH_YEAR] = skip + day + dayMonth + dayMonthYear + year + yearMonth + yearMonthDay;
406 m_formatsDateParseIncomplete [COORD_UNITS_DATE_YEAR_MONTH_DAY] = skip + year + yearMonth + yearMonthDay;
407
408 ENGAUGE_ASSERT (m_formatsDateParseIncomplete.count () == NUM_COORD_UNITS_DATE);
409
410 QStringList hour, hourMinute, hourMinuteAmPm, hourMinuteSecond, hourMinuteSecondAmPm;
411
412 hour << "\\d{1,2}"
413 << "\\d{1,2}:";
414 hourMinute << "\\d{1,2}:\\d{1,2}"
415 << "\\d{1,2}:\\d{1,2}:"
416 << "\\d{1,2}:\\d{1,2} ";
417 hourMinuteAmPm << "\\d{1,2}:\\d{1,2} [aApP]";
418 hourMinuteSecond << "\\d{1,2}:\\d{1,2}:\\d{1,2}"
419 << "\\d{1,2}:\\d{1,2}:\\d{1,2} ";
420 hourMinuteSecondAmPm << "\\d{1,2}:\\d{1,2}:\\d{1,2} [aApP]";
421
422 // For every entry, the possible states leading up to the Acceptable states in m_formatsTimeParseIncomplete are all included.
423 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_SKIP] = skip +
424 hour +
425 hourMinute + hourMinuteAmPm +
426 hourMinuteSecond + hourMinuteSecondAmPm;
427 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_HOUR_MINUTE] = skip +
428 hour +
429 hourMinute + hourMinuteAmPm +
430 hourMinuteSecond + hourMinuteSecondAmPm;
431 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = skip +
432 hour +
433 hourMinute + hourMinuteAmPm +
434 hourMinuteSecond + hourMinuteSecondAmPm;
435
436 ENGAUGE_ASSERT (m_formatsTimeParseIncomplete.count () == NUM_COORD_UNITS_TIME);
437}
438
439QValidator::State FormatDateTime::parseInput (CoordUnitsDate coordUnitsDate,
440 CoordUnitsTime coordUnitsTime,
441 const QString &stringUntrimmed,
442 double &value) const
443{
444 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::parseInput"
445 << " date=" << coordUnitsDateToString (coordUnitsDate).toLatin1().data()
446 << " time=" << coordUnitsTimeToString (coordUnitsTime).toLatin1().data()
447 << " string=" << stringUntrimmed.toLatin1().data();
448
449 const bool USE_QREGEXP = true, DO_NOT_USE_QREGEXP = false;
450
451 const QString string = stringUntrimmed.trimmed();
452
453 QValidator::State state;
454 if (string.isEmpty()) {
455
456 state = QValidator::Intermediate;
457
458 } else {
459
460 state = QValidator::Invalid;
461
462 // First see if value is acceptable
463 bool success = false;
464 dateTimeLookup (m_formatsDateParseAcceptable,
465 m_formatsTimeParseAcceptable,
466 coordUnitsDate,
467 coordUnitsTime,
468 string,
469 USE_QREGEXP,
470 value,
471 success);
472 if (success) {
473
474 state = QValidator::Acceptable;
475
476 } else {
477
478 // Not acceptable, but perhaps it is just incomplete
479 dateTimeLookup (m_formatsDateParseIncomplete,
480 m_formatsTimeParseIncomplete,
481 coordUnitsDate,
482 coordUnitsTime,
483 string,
484 DO_NOT_USE_QREGEXP,
485 value,
486 success);
487 if (success) {
488
489 state = QValidator::Intermediate;
490
491 }
492 }
493 }
494
495 return state;
496}
497
498qint64 FormatDateTime::toSecsSinceEpoch (const QDateTime &dt) const
499{
500 return dt.toMSecsSinceEpoch () / static_cast<qint64> (1000);
501}
QString coordUnitsDateToString(CoordUnitsDate coordUnits)
CoordUnitsDate
@ COORD_UNITS_DATE_SKIP
@ COORD_UNITS_DATE_DAY_MONTH_YEAR
@ NUM_COORD_UNITS_DATE
@ COORD_UNITS_DATE_YEAR_MONTH_DAY
@ COORD_UNITS_DATE_MONTH_DAY_YEAR
QString coordUnitsTimeToString(CoordUnitsTime coordUnits)
CoordUnitsTime
@ COORD_UNITS_TIME_HOUR_MINUTE_SECOND
@ COORD_UNITS_TIME_HOUR_MINUTE
@ NUM_COORD_UNITS_TIME
@ COORD_UNITS_TIME_SKIP
#define ENGAUGE_ASSERT(cond)
Drop in replacement for Q_ASSERT.
QHash< CoordUnitsDate, QStringList > FormatsDate
QHash< CoordUnitsTime, QStringList > FormatsTime
log4cpp::Category * mainCat
Definition Logger.cpp:14
FormatDateTime()
Single constructor.
QValidator::State parseInput(CoordUnitsDate coordUnitsDate, CoordUnitsTime coordUnitsTime, const QString &stringUntrimmed, double &value) const
Parse the input string into a time value.
QString formatOutput(CoordUnitsDate coordUnitsDate, CoordUnitsTime coordUnitsTime, double value) const
Format the date/time value according to date/time format settings.
#define LOG4CPP_INFO_S(logger)
Definition convenience.h:18