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(QRegExp ("[/- :]"));
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,
49 CoordUnitsDate coordUnitsDate,
50 CoordUnitsTime coordUnitsTime,
51 const QString &string,
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
72
73 for (itrTime = formatsTime.begin(); itrTime != formatsTime.end() && iterating; itrTime++) {
74
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
81
82 if (!formatDateTime.isEmpty()) {
83
84 // Try parsing according to the current format
86
87 QDateTime dt = QDateTime::fromString (string,
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
111 if (reg.exactMatch(string)) {
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
143 if (value > 0) {
144 dt = fromSecsSinceEpoch ((unsigned long int) (value));
145 } else {
146 dt = fromSecsSinceEpoch ((long int) (value));
147 }
148
149 return dt.toLocalTime ().toString (format); // Convert using local time to prevent addition of utc offset
150}
151
152QDateTime FormatDateTime::fromSecsSinceEpoch (qint64 secs) const
153{
154 return QDateTime::fromMSecsSinceEpoch (secs * 1000);
155}
156
157void FormatDateTime::loadFormatsFormat()
158{
159 m_formatsDateFormat [COORD_UNITS_DATE_SKIP] = "";
160 m_formatsDateFormat [COORD_UNITS_DATE_MONTH_DAY_YEAR] = "MM/dd/yyyy";
161 m_formatsDateFormat [COORD_UNITS_DATE_DAY_MONTH_YEAR] = "dd/MM/yyyy";
162 m_formatsDateFormat [COORD_UNITS_DATE_YEAR_MONTH_DAY] = "yyyy/MM/dd";
163
164 ENGAUGE_ASSERT (m_formatsDateFormat.count () == NUM_COORD_UNITS_DATE);
165
166 m_formatsTimeFormat [COORD_UNITS_TIME_SKIP] = "";
167 m_formatsTimeFormat [COORD_UNITS_TIME_HOUR_MINUTE] = "hh/mm";
168 m_formatsTimeFormat [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = "hh:mm:ss";
169
170 ENGAUGE_ASSERT (m_formatsTimeFormat.count () == NUM_COORD_UNITS_TIME);
171}
172
173void FormatDateTime::loadFormatsParseAcceptable()
174{
175 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::loadFormatsParseAcceptable";
176
178
179 // COORD_UNITS_DATE_SKIP and COORD_UNITS_TIME_SKIP allow date/time respectively even when skipped,
180 // although there can be ambiguity with between COORD_UNITS_DATE_MONTH_DAY_YEAR and COORD_UNITS_DATE_DAY_MONTH_YEAR
181 skip << "";
182
183 dayMonth << "d/M"
184 << "d-M"
185 << "d/MM"
186 << "d-MM"
187 << "d/MMM"
188 << "d-MMM"
189 << "d/MMMM"
190 << "d-MMMM"
191 << "dd/M"
192 << "dd-M"
193 << "dd/M"
194 << "dd-M"
195 << "dd/MM"
196 << "dd-MM"
197 << "dd/MMM"
198 << "dd-MMM"
199 << "dd/MMMM"
200 << "dd-MMMM";
201 dayMonthYear << "d/M/yyyy"
202 << "d-M-yyyy"
203
204 << "d/MM/yyyy"
205 << "d-MM-yyyy"
206 << "d/MMM/yyyy"
207 << "d-MMM-yyyy"
208 << "d MMM yyyy"
209 << "d/MMMM/yyyy"
210 << "d-MMMM-yyyy"
211 << "d MMMM yyyy"
212
213 << "dd/MM/yyyy"
214 << "dd-MM-yyyy"
215 << "dd/MMM/yyyy"
216 << "dd-MMM-yyyy"
217 << "dd MMM yyyy"
218 << "dd/MMMM/yyyy"
219 << "dd-MMMM-yyyy"
220 << "dd MMMM yyyy";
221 monthDay << "M/d"
222 << "M-d"
223 << "M d"
224 << "M/dd"
225 << "M-dd"
226 << "M dd"
227 << "MM/d"
228 << "MM-d"
229 << "MM d"
230 << "MM/dd"
231 << "MM-dd"
232 << "MM dd"
233 << "MMM/d"
234 << "MMM-d"
235 << "MMM d"
236 << "MMM/dd"
237 << "MMM-dd"
238 << "MMM dd"
239 << "MMMM/d"
240 << "MMMM-d"
241 << "MMMM d"
242 << "MMMM/dd"
243 << "MMMM-dd"
244 << "MMMM dd";
245 monthDayYear << "M/d/yyyy"
246 << "M-d-yyyy"
247 << "M d yyyy"
248 << "M/dd/yyyy"
249 << "M-dd-yyyy"
250 << "M dd yyyy"
251 << "MM/d/yyyy"
252 << "MM-d-yyyy"
253 << "MM d yyyy"
254 << "MM/dd/yyyy"
255 << "MM-dd-yyyy"
256 << "MM dd yyyy"
257 << "MMM/d/yyyy"
258 << "MMM-d-yyyy"
259 << "MMM d yyyy"
260 << "MMM/dd/yyyy"
261 << "MMM-dd-yyyy"
262 << "MMM dd yyyy"
263 << "MMMM/d/yyyy"
264 << "MMMM-d-yyyy"
265 << "MMMM d"
266 << "MMMM/dd"
267 << "MMMM-dd"
268 << "MMMM dd";
269 yearMonth << "yyyy/M"
270 << "yyyy-M"
271 << "yyyy M"
272 << "yyyy/MM"
273 << "yyyy-MM"
274 << "yyyy MM"
275 << "yyyy/MMM"
276 << "yyyy-MMM"
277 << "yyyy MMM"
278 << "yyyy/MMMM"
279 << "yyyy-MMMM"
280 << "yyyy MMMM";
281 yearMonthDay << "yyyy/M/d"
282 << "yyyy-M-d"
283 << "yyyy M d"
284 << "yyyy/M/dd"
285 << "yyyy-M-dd"
286 << "yyyy M dd"
287 << "yyyy/MM/dd"
288 << "yyyy-MM-dd"
289 << "yyyy MM dd"
290 << "yyyy/MMM/d"
291 << "yyyy-MMM-d"
292 << "yyyy MMM d"
293 << "yyyy/MMM/dd"
294 << "yyyy-MMM-dd"
295 << "yyyy MMM dd"
296 << "yyyy/MMMM/dd"
297 << "yyyy-MMMM-dd"
298 << "yyyy MMMM dd";
299
300 // Potential day-month ambiguity for COORD_UNITS_DATE_SKIP gets treated as month/day
301 m_formatsDateParseAcceptable [COORD_UNITS_DATE_SKIP] = skip + monthDay + monthDayYear + yearMonthDay;
302 m_formatsDateParseAcceptable [COORD_UNITS_DATE_MONTH_DAY_YEAR] = skip + monthDay + monthDayYear + yearMonthDay;
303 m_formatsDateParseAcceptable [COORD_UNITS_DATE_DAY_MONTH_YEAR] = skip + dayMonth + dayMonthYear + yearMonthDay;
304 m_formatsDateParseAcceptable [COORD_UNITS_DATE_YEAR_MONTH_DAY] = skip + yearMonth + yearMonthDay;
305
306 ENGAUGE_ASSERT (m_formatsDateParseAcceptable.count () == NUM_COORD_UNITS_DATE);
307
309
310 hour << "hh";
311 hourMinute << "hh:mm";
312 hourMinuteSecond << "hh:mm:ss";
313 hourMinutePm << "hh:mmA"
314 << "hh:mm A"
315 << "hh:mma"
316 << "hh:mm a";
317 hourMinuteSecondPm << "hh:mm:ssA"
318 << "hh:mm:ss A"
319 << "hh:mm:ssa"
320 << "hh:mm:ss a";
321
325
326 ENGAUGE_ASSERT (m_formatsTimeParseAcceptable.count () == NUM_COORD_UNITS_TIME);
327}
328
329void FormatDateTime::loadFormatsParseIncomplete()
330{
331 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::loadFormatsParseIncomplete";
332
334
335 // COORD_UNITS_DATE_SKIP and COORD_UNITS_TIME_SKIP allow date/time respectively even when skipped,
336 // although there can be ambiguity with between COORD_UNITS_DATE_MONTH_DAY_YEAR and COORD_UNITS_DATE_DAY_MONTH_YEAR
337 skip << "";
338
339 // IMPORTANT! Be sure to include complete date values since the date, which goes before the time, will be
340 // complete when the time is getting
341 day << "\\d{1,2}"
342 << "\\d{1,2}/"
343 << "\\d{1,2}-";
344 dayMonth << "\\d{1,2}/\\d{1,2}"
345 << "\\d{1,2}/\\d{1,2} "
346 << "\\d{1,2}/\\d{1,2}/"
347 << "\\d{1,2}-\\d{1,2}-"
348 << "\\d{1,2}/[a-zA-Z]{1,12}/"
349 << "\\d{1,2}-[a-zA-Z]{1,12}-"
350 << "\\d{1,2} [a-zA-Z]{1,12} ";
351 dayMonthYear << "\\d{1,2}/\\d{1,2}/\\d{1,4}"
352 << "\\d{1,2}/\\d{1,2}/\\d{1,4} "
353 << "\\d{1,2}-\\d{1,2}-\\d{1,4}"
354 << "\\d{1,2}-\\d{1,2}-\\d{1,4} ";
355 month << "\\d{1,2}"
356 << "\\d{1,2}/"
357 << "[a-zA-Z]{1,12}"
358 << "[a-zA-Z]{1,12} ";
359 monthDay << "\\d{1,2}/\\d{1,2}"
360 << "\\d{1,2}/\\d{1,2} "
361 << "\\d{1,2}/\\d{1,2}/"
362 << "\\d{1,2} \\d{1,2}"
363 << "\\d{1,2} \\d{1,2} "
364 << "\\d{1,2}-\\d{1,2}-"
365 << "[a-zA-Z]{1,12}"
366 << "[a-zA-Z]{1,12} "
367 << "[a-zA-Z]{1,12} \\d{1,2}"
368 << "[a-zA-Z]{1,12} \\d{1,2} ";
369 monthDayYear << "\\d{1,2}/\\d{1,2}/\\d{1,4}"
370 << "\\d{1,2}/\\d{1,2}/\\d{1,4} "
371 << "\\d{1,2}-\\d{1,2}-\\d{1,4}"
372 << "\\d{1,2}-\\d{1,2}-\\d{1,4} "
373 << "\\d{1,2} \\d{1,2} \\d{1,4}"
374 << "\\d{1,2} \\d{1,2} \\d{1,4} ";
375 year << "\\d{1,4}"
376 << "\\d{1,4} "
377 << "\\d{1,4}/"
378 << "\\d{1,4}-";
379 yearMonth << "\\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}-\\d{1,2} "
384 << "\\d{4}-\\d{1,2}-"
385 << "\\d{4} \\d{1,2}"
386 << "\\d{4} \\d{1,2} "
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 << "\\d{4}-[a-zA-Z]{1,12} "
392 << "\\d{4}-[a-zA-Z]{1,12}-"
393 << "\\d{4} [a-zA-Z]{1,12}"
394 << "\\d{4} [a-zA-Z]{1,12} ";
395 yearMonthDay << "\\d{4}/\\d{1,2}/\\d{1,2}"
396 << "\\d{4}/\\d{1,2}/\\d{1,2} "
397 << "\\d{4}/\\d{1,2}-\\d{1,2}"
398 << "\\d{4}/\\d{1,2}-\\d{1,2} "
399 << "\\d{4} \\d{1,2} \\d{1,2}"
400 << "\\d{4}/[a-zA-Z]{1,12}/\\d{1,2}"
401 << "\\d{4}/[a-zA-Z]{1,12}/\\d{1,2} "
402 << "\\d{4}-[a-zA-Z]{1,12}-\\d{1,2}"
403 << "\\d{4}-[a-zA-Z]{1,12}-\\d{1,2} ";
404
405 // For every entry, the possible states leading up to the Acceptable states in m_formatsDateParseIncomplete are all included.
406 // Potential day-month ambiguity for COORD_UNITS_DATE_SKIP gets treated as month/day.
407 m_formatsDateParseIncomplete [COORD_UNITS_DATE_SKIP] = skip + month + monthDay + monthDayYear + year + yearMonth + yearMonthDay;
408 m_formatsDateParseIncomplete [COORD_UNITS_DATE_MONTH_DAY_YEAR] = skip + month + monthDay + monthDayYear + year + yearMonth + yearMonthDay;
409 m_formatsDateParseIncomplete [COORD_UNITS_DATE_DAY_MONTH_YEAR] = skip + day + dayMonth + dayMonthYear + year + yearMonth + yearMonthDay;
410 m_formatsDateParseIncomplete [COORD_UNITS_DATE_YEAR_MONTH_DAY] = skip + year + yearMonth + yearMonthDay;
411
412 ENGAUGE_ASSERT (m_formatsDateParseIncomplete.count () == NUM_COORD_UNITS_DATE);
413
415
416 hour << "\\d{1,2}"
417 << "\\d{1,2}:";
418 hourMinute << "\\d{1,2}:\\d{1,2}"
419 << "\\d{1,2}:\\d{1,2}:"
420 << "\\d{1,2}:\\d{1,2} ";
421 hourMinuteAmPm << "\\d{1,2}:\\d{1,2} [aApP]";
422 hourMinuteSecond << "\\d{1,2}:\\d{1,2}:\\d{1,2}"
423 << "\\d{1,2}:\\d{1,2}:\\d{1,2} ";
424 hourMinuteSecondAmPm << "\\d{1,2}:\\d{1,2}:\\d{1,2} [aApP]";
425
426 // For every entry, the possible states leading up to the Acceptable states in m_formatsTimeParseIncomplete are all included.
427 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_SKIP] = skip +
428 hour +
431 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_HOUR_MINUTE] = skip +
432 hour +
435 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = skip +
436 hour +
439
440 ENGAUGE_ASSERT (m_formatsTimeParseIncomplete.count () == NUM_COORD_UNITS_TIME);
441}
442
443QValidator::State FormatDateTime::parseInput (CoordUnitsDate coordUnitsDate,
444 CoordUnitsTime coordUnitsTime,
446 double &value) const
447{
448 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::parseInput"
449 << " date=" << coordUnitsDateToString (coordUnitsDate).toLatin1().data()
450 << " time=" << coordUnitsTimeToString (coordUnitsTime).toLatin1().data()
451 << " string=" << stringUntrimmed.toLatin1().data();
452
453 const bool USE_QREGEXP = true, DO_NOT_USE_QREGEXP = false;
454
455 const QString string = stringUntrimmed.trimmed();
456
457 QValidator::State state;
458 if (string.isEmpty()) {
459
460 state = QValidator::Intermediate;
461
462 } else {
463
464 state = QValidator::Invalid;
465
466 // First see if value is acceptable
467 bool success = false;
468 dateTimeLookup (m_formatsDateParseAcceptable,
469 m_formatsTimeParseAcceptable,
470 coordUnitsDate,
471 coordUnitsTime,
472 string,
474 value,
475 success);
476 if (success) {
477
478 state = QValidator::Acceptable;
479
480 } else {
481
482 // Not acceptable, but perhaps it is just incomplete
483 dateTimeLookup (m_formatsDateParseIncomplete,
484 m_formatsTimeParseIncomplete,
485 coordUnitsDate,
486 coordUnitsTime,
487 string,
489 value,
490 success);
491 if (success) {
492
493 state = QValidator::Intermediate;
494
495 }
496 }
497 }
498
499 return state;
500}
501
502qint64 FormatDateTime::toSecsSinceEpoch (const QDateTime &dt) const
503{
504 return dt.toMSecsSinceEpoch () / 1000;
505}
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
const int INNER_RADIUS_MIN
#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