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