Engauge Digitizer  2
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 
18 bool 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 
44 void 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 
121 QString 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 
139 void 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 
155 void 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 
311 void 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 
417 QValidator::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.