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
14// Need a reference time zone so exported outputs do not exhibit unpredictable local/UTC hours differences
15const Qt::TimeSpec REFERENCE_TIME_ZONE (Qt::UTC);
16
18{
19 loadFormatsFormat();
20 loadFormatsParseAcceptable();
21 loadFormatsParseIncomplete();
22}
23
24bool FormatDateTime::ambiguityBetweenDateAndTime (CoordUnitsDate coordUnitsDate,
25 CoordUnitsTime coordUnitsTime,
26 const QString &string) const
27{
28 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::ambiguityBetweenDateAndTime";
29
30 bool ambiguous = false;
31
32 // There is no ambiguity if user specified either date or time as empty
33 if (coordUnitsDate != COORD_UNITS_DATE_SKIP &&
34 coordUnitsTime != COORD_UNITS_TIME_SKIP) {
35
36 // See if there is just a single number
37 QStringList fields = string.trimmed().split(QRegExp ("[/- :]"));
38
39 if (fields.count() == 1) {
40
41 // There is a single number. Since there are no attached delimiters to differentiate a date versus
42 // a time, this means the number is ambiguous
43 ambiguous = true;
44 }
45 }
46
47 return ambiguous;
48}
49
50void FormatDateTime::dateTimeLookup (const FormatsDate &formatsDateAll,
52 CoordUnitsDate coordUnitsDate,
53 CoordUnitsTime coordUnitsTime,
54 const QString &string,
56 double &value,
57 bool &success) const
58{
59 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::dateTimeLookup";
60
61 success = false;
62
63 ENGAUGE_ASSERT (formatsDateAll.contains (coordUnitsDate));
64 ENGAUGE_ASSERT (formatsTimeAll.contains (coordUnitsTime));
65
66 QStringList formatsDate = formatsDateAll [coordUnitsDate];
67 QStringList formatsTime = formatsTimeAll [coordUnitsTime];
68
69 // Loop through the legal date/time combinations
70 QStringList::const_iterator itrDate, itrTime;
71 bool iterating = true;
72 for (itrDate = formatsDate.begin(); itrDate != formatsDate.end() && iterating; itrDate++) {
73
75
76 for (itrTime = formatsTime.begin(); itrTime != formatsTime.end() && iterating; itrTime++) {
77
79
80 // Insert space as separator only if needed. Do not use trim around here since formatDate may or may not end in a space
81 QString separator = (!formatDate.isEmpty() && !formatTime.isEmpty() ? " " : "");
82
84
85 if (!formatDateTime.isEmpty()) {
86
87 // Try parsing according to the current format
89
90 QDateTime dt = QDateTime::fromString (string,
92
93 if (dt.isValid() && !ambiguityBetweenDateAndTime (coordUnitsDate,
94 coordUnitsTime,
95 string)) {
96
97 success = true;
98 value = dt.toTimeSpec (REFERENCE_TIME_ZONE).toTime_t ();
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 // Range of unsigned versus signed is not a problem here. A signed value maxes out at 2.4 billion
141 // which is year 2038
142 QDateTime dt = QDateTime::fromTime_t (unsigned (qFloor (value)));
143
144 return dt.toTimeSpec(REFERENCE_TIME_ZONE).toString (format);
145}
146
147void FormatDateTime::loadFormatsFormat()
148{
149 m_formatsDateFormat [COORD_UNITS_DATE_SKIP] = "";
150 m_formatsDateFormat [COORD_UNITS_DATE_MONTH_DAY_YEAR] = "MM/dd/yyyy";
151 m_formatsDateFormat [COORD_UNITS_DATE_DAY_MONTH_YEAR] = "dd/MM/yyyy";
152 m_formatsDateFormat [COORD_UNITS_DATE_YEAR_MONTH_DAY] = "yyyy/MM/dd";
153
154 ENGAUGE_ASSERT (m_formatsDateFormat.count () == NUM_COORD_UNITS_DATE);
155
156 m_formatsTimeFormat [COORD_UNITS_TIME_SKIP] = "";
157 m_formatsTimeFormat [COORD_UNITS_TIME_HOUR_MINUTE] = "hh/mm";
158 m_formatsTimeFormat [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = "hh:mm:ss";
159
160 ENGAUGE_ASSERT (m_formatsTimeFormat.count () == NUM_COORD_UNITS_TIME);
161}
162
163void FormatDateTime::loadFormatsParseAcceptable()
164{
165 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::loadFormatsParseAcceptable";
166
168
169 // COORD_UNITS_DATE_SKIP and COORD_UNITS_TIME_SKIP allow date/time respectively even when skipped,
170 // although there can be ambiguity with between COORD_UNITS_DATE_MONTH_DAY_YEAR and COORD_UNITS_DATE_DAY_MONTH_YEAR
171 skip << "";
172
173 dayMonth << "d/M"
174 << "d-M"
175 << "d/MM"
176 << "d-MM"
177 << "d/MMM"
178 << "d-MMM"
179 << "d/MMMM"
180 << "d-MMMM"
181 << "dd/M"
182 << "dd-M"
183 << "dd/M"
184 << "dd-M"
185 << "dd/MM"
186 << "dd-MM"
187 << "dd/MMM"
188 << "dd-MMM"
189 << "dd/MMMM"
190 << "dd-MMMM";
191 dayMonthYear << "d/M/yyyy"
192 << "d-M-yyyy"
193
194 << "d/MM/yyyy"
195 << "d-MM-yyyy"
196 << "d/MMM/yyyy"
197 << "d-MMM-yyyy"
198 << "d MMM yyyy"
199 << "d/MMMM/yyyy"
200 << "d-MMMM-yyyy"
201 << "d MMMM yyyy"
202
203 << "dd/MM/yyyy"
204 << "dd-MM-yyyy"
205 << "dd/MMM/yyyy"
206 << "dd-MMM-yyyy"
207 << "dd MMM yyyy"
208 << "dd/MMMM/yyyy"
209 << "dd-MMMM-yyyy"
210 << "dd MMMM yyyy";
211 monthDay << "M/d"
212 << "M-d"
213 << "M d"
214 << "M/dd"
215 << "M-dd"
216 << "M dd"
217 << "MM/d"
218 << "MM-d"
219 << "MM d"
220 << "MM/dd"
221 << "MM-dd"
222 << "MM dd"
223 << "MMM/d"
224 << "MMM-d"
225 << "MMM d"
226 << "MMM/dd"
227 << "MMM-dd"
228 << "MMM dd"
229 << "MMMM/d"
230 << "MMMM-d"
231 << "MMMM d"
232 << "MMMM/dd"
233 << "MMMM-dd"
234 << "MMMM dd";
235 monthDayYear << "M/d/yyyy"
236 << "M-d-yyyy"
237 << "M d yyyy"
238 << "M/dd/yyyy"
239 << "M-dd-yyyy"
240 << "M dd yyyy"
241 << "MM/d/yyyy"
242 << "MM-d-yyyy"
243 << "MM d yyyy"
244 << "MM/dd/yyyy"
245 << "MM-dd-yyyy"
246 << "MM dd yyyy"
247 << "MMM/d/yyyy"
248 << "MMM-d-yyyy"
249 << "MMM d yyyy"
250 << "MMM/dd/yyyy"
251 << "MMM-dd-yyyy"
252 << "MMM dd yyyy"
253 << "MMMM/d/yyyy"
254 << "MMMM-d-yyyy"
255 << "MMMM d"
256 << "MMMM/dd"
257 << "MMMM-dd"
258 << "MMMM dd";
259 yearMonth << "yyyy/M"
260 << "yyyy-M"
261 << "yyyy M"
262 << "yyyy/MM"
263 << "yyyy-MM"
264 << "yyyy MM"
265 << "yyyy/MMM"
266 << "yyyy-MMM"
267 << "yyyy MMM"
268 << "yyyy/MMMM"
269 << "yyyy-MMMM"
270 << "yyyy MMMM";
271 yearMonthDay << "yyyy/M/d"
272 << "yyyy-M-d"
273 << "yyyy M d"
274 << "yyyy/M/dd"
275 << "yyyy-M-dd"
276 << "yyyy M dd"
277 << "yyyy/MM/dd"
278 << "yyyy-MM-dd"
279 << "yyyy MM dd"
280 << "yyyy/MMM/d"
281 << "yyyy-MMM-d"
282 << "yyyy MMM d"
283 << "yyyy/MMM/dd"
284 << "yyyy-MMM-dd"
285 << "yyyy MMM dd"
286 << "yyyy/MMMM/dd"
287 << "yyyy-MMMM-dd"
288 << "yyyy MMMM dd";
289
290 // Potential day-month ambiguity for COORD_UNITS_DATE_SKIP gets treated as month/day
291 m_formatsDateParseAcceptable [COORD_UNITS_DATE_SKIP] = skip + monthDay + monthDayYear + yearMonthDay;
292 m_formatsDateParseAcceptable [COORD_UNITS_DATE_MONTH_DAY_YEAR] = skip + monthDay + monthDayYear + yearMonthDay;
293 m_formatsDateParseAcceptable [COORD_UNITS_DATE_DAY_MONTH_YEAR] = skip + dayMonth + dayMonthYear + yearMonthDay;
294 m_formatsDateParseAcceptable [COORD_UNITS_DATE_YEAR_MONTH_DAY] = skip + yearMonth + yearMonthDay;
295
296 ENGAUGE_ASSERT (m_formatsDateParseAcceptable.count () == NUM_COORD_UNITS_DATE);
297
299
300 hour << "hh";
301 hourMinute << "hh:mm";
302 hourMinuteSecond << "hh:mm:ss";
303 hourMinutePm << "hh:mmA"
304 << "hh:mm A"
305 << "hh:mma"
306 << "hh:mm a";
307 hourMinuteSecondPm << "hh:mm:ssA"
308 << "hh:mm:ss A"
309 << "hh:mm:ssa"
310 << "hh:mm:ss a";
311
315
316 ENGAUGE_ASSERT (m_formatsTimeParseAcceptable.count () == NUM_COORD_UNITS_TIME);
317}
318
319void FormatDateTime::loadFormatsParseIncomplete()
320{
321 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::loadFormatsParseIncomplete";
322
324
325 // COORD_UNITS_DATE_SKIP and COORD_UNITS_TIME_SKIP allow date/time respectively even when skipped,
326 // although there can be ambiguity with between COORD_UNITS_DATE_MONTH_DAY_YEAR and COORD_UNITS_DATE_DAY_MONTH_YEAR
327 skip << "";
328
329 // IMPORTANT! Be sure to include complete date values since the date, which goes before the time, will be
330 // complete when the time is getting
331 day << "\\d{1,2}"
332 << "\\d{1,2}/"
333 << "\\d{1,2}-";
334 dayMonth << "\\d{1,2}/\\d{1,2}"
335 << "\\d{1,2}/\\d{1,2} "
336 << "\\d{1,2}/\\d{1,2}/"
337 << "\\d{1,2}-\\d{1,2}-"
338 << "\\d{1,2}/[a-zA-Z]{1,12}/"
339 << "\\d{1,2}-[a-zA-Z]{1,12}-"
340 << "\\d{1,2} [a-zA-Z]{1,12} ";
341 month << "\\d{1,2}"
342 << "\\d{1,2}/"
343 << "[a-zA-Z]{1,12}"
344 << "[a-zA-Z]{1,12} ";
345 monthDay << "\\d{1,2}/\\d{1,2}"
346 << "\\d{1,2}/\\d{1,2} "
347 << "\\d{1,2}/\\d{1,2}/"
348 << "\\d{1,2} \\d{1,2}"
349 << "\\d{1,2} \\d{1,2} "
350 << "\\d{1,2}-\\d{1,2}-"
351 << "[a-zA-Z]{1,12}"
352 << "[a-zA-Z]{1,12} "
353 << "[a-zA-Z]{1,12} \\d{1,2}"
354 << "[a-zA-Z]{1,12} \\d{1,2} ";
355 monthDayYear << "\\d{1,2}/\\d{1,2}/\\d{1,4}"
356 << "\\d{1,2}/\\d{1,2}/\\d{1,4} "
357 << "\\d{1,2}-\\d{1,2}-\\d{1,4}"
358 << "\\d{1,2}-\\d{1,2}-\\d{1,4} "
359 << "\\d{1,2} \\d{1,2} \\d{1,4}"
360 << "\\d{1,2} \\d{1,2} \\d{1,4} ";
361 year << "\\d{1,4}"
362 << "\\d{1,4} "
363 << "\\d{1,4}/"
364 << "\\d{1,4}-";
365 yearMonth << "\\d{4}/\\d{1,2}"
366 << "\\d{4}/\\d{1,2} "
367 << "\\d{4}/\\d{1,2}/"
368 << "\\d{4}-\\d{1,2}"
369 << "\\d{4}-\\d{1,2} "
370 << "\\d{4}-\\d{1,2}-"
371 << "\\d{4} \\d{1,2}"
372 << "\\d{4} \\d{1,2} "
373 << "\\d{4}/[a-zA-Z]{1,12}"
374 << "\\d{4}/[a-zA-Z]{1,12} "
375 << "\\d{4}/[a-zA-Z]{1,12}/"
376 << "\\d{4}-[a-zA-Z]{1,12}"
377 << "\\d{4}-[a-zA-Z]{1,12} "
378 << "\\d{4}-[a-zA-Z]{1,12}-"
379 << "\\d{4} [a-zA-Z]{1,12}"
380 << "\\d{4} [a-zA-Z]{1,12} ";
381 yearMonthDay << "\\d{4}/\\d{1,2}/\\d{1,2}"
382 << "\\d{4}/\\d{1,2}-\\d{1,2}"
383 << "\\d{4} \\d{1,2} \\d{1,2}"
384 << "\\d{4}/[a-zA-Z]{1,12}/\\d{1,2}"
385 << "\\d{4}-[a-zA-Z]{1,12}-\\d{1,2}";
386
387 // For every entry, the possible states leading up to the Acceptable states in m_formatsDateParseIncomplete are all included.
388 // Potential day-month ambiguity for COORD_UNITS_DATE_SKIP gets treated as month/day.
389 m_formatsDateParseIncomplete [COORD_UNITS_DATE_SKIP] = skip + month + monthDay + monthDayYear + year + yearMonth + yearMonthDay;
390 m_formatsDateParseIncomplete [COORD_UNITS_DATE_MONTH_DAY_YEAR] = skip + month + monthDay + monthDayYear + year + yearMonth + yearMonthDay;
391 m_formatsDateParseIncomplete [COORD_UNITS_DATE_DAY_MONTH_YEAR] = skip + day + dayMonth + year + yearMonth + yearMonthDay;
392 m_formatsDateParseIncomplete [COORD_UNITS_DATE_YEAR_MONTH_DAY] = skip + year + yearMonth + yearMonthDay;
393
394 ENGAUGE_ASSERT (m_formatsDateParseIncomplete.count () == NUM_COORD_UNITS_DATE);
395
397
398 hour << "\\d{1,2}"
399 << "\\d{1,2}:";
400 hourMinute << "\\d{1,2}:\\d{1,2}"
401 << "\\d{1,2}:\\d{1,2}:"
402 << "\\d{1,2}:\\d{1,2} ";
403 hourMinuteAmPm << "\\d{1,2}:\\d{1,2} [aApP]";
404 hourMinuteSecond << "\\d{1,2}:\\d{1,2}:\\d{1,2}"
405 << "\\d{1,2}:\\d{1,2}:\\d{1,2} ";
406 hourMinuteSecondAmPm << "\\d{1,2}:\\d{1,2}:\\d{1,2} [aApP]";
407
408 // For every entry, the possible states leading up to the Acceptable states in m_formatsTimeParseIncomplete are all included.
409 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_SKIP] = skip +
410 hour +
413 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_HOUR_MINUTE] = skip +
414 hour +
417 m_formatsTimeParseIncomplete [COORD_UNITS_TIME_HOUR_MINUTE_SECOND] = skip +
418 hour +
421
422 ENGAUGE_ASSERT (m_formatsTimeParseIncomplete.count () == NUM_COORD_UNITS_TIME);
423}
424
425QValidator::State FormatDateTime::parseInput (CoordUnitsDate coordUnitsDate,
426 CoordUnitsTime coordUnitsTime,
428 double &value) const
429{
430 LOG4CPP_INFO_S ((*mainCat)) << "FormatDateTime::parseInput"
431 << " date=" << coordUnitsDateToString (coordUnitsDate).toLatin1().data()
432 << " time=" << coordUnitsTimeToString (coordUnitsTime).toLatin1().data()
433 << " string=" << stringUntrimmed.toLatin1().data();
434
435 const bool USE_QREGEXP = true, DO_NOT_USE_QREGEXP = false;
436
437 const QString string = stringUntrimmed.trimmed();
438
439 QValidator::State state;
440 if (string.isEmpty()) {
441
442 state = QValidator::Intermediate;
443
444 } else {
445
446 state = QValidator::Invalid;
447
448 // First see if value is acceptable
449 bool success = false;
450 dateTimeLookup (m_formatsDateParseAcceptable,
451 m_formatsTimeParseAcceptable,
452 coordUnitsDate,
453 coordUnitsTime,
454 string,
456 value,
457 success);
458 if (success) {
459
460 state = QValidator::Acceptable;
461
462 } else {
463
464 // Not acceptable, but perhaps it is just incomplete
465 dateTimeLookup (m_formatsDateParseIncomplete,
466 m_formatsTimeParseIncomplete,
467 coordUnitsDate,
468 coordUnitsTime,
469 string,
471 value,
472 success);
473 if (success) {
474
475 state = QValidator::Intermediate;
476
477 }
478 }
479 }
480
481 return state;
482}
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 if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) define ENGAUGE...
const Qt::TimeSpec REFERENCE_TIME_ZONE(Qt::UTC)
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