Coverage Report

Created: 2024-02-11 06:25

/src/poco/Foundation/src/DateTimeParser.cpp
Line
Count
Source (jump to first uncovered line)
1
//
2
// DateTimeParser.cpp
3
//
4
// Library: Foundation
5
// Package: DateTime
6
// Module:  DateTimeParser
7
//
8
// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
9
// and Contributors.
10
//
11
// SPDX-License-Identifier: BSL-1.0
12
//
13
14
15
#include "Poco/DateTimeParser.h"
16
#include "Poco/DateTimeFormat.h"
17
#include "Poco/DateTime.h"
18
#include "Poco/Exception.h"
19
#include "Poco/Ascii.h"
20
#include "Poco/String.h"
21
22
23
namespace Poco {
24
25
26
#define SKIP_JUNK() \
27
0
  while (it != end && !Ascii::isDigit(*it)) ++it
28
29
30
#define SKIP_DIGITS() \
31
0
  while (it != end && Ascii::isDigit(*it)) ++it
32
33
34
#define PARSE_NUMBER(var) \
35
0
  while (it != end && Ascii::isDigit(*it)) var = var*10 + ((*it++) - '0')
36
37
38
#define PARSE_NUMBER_N(var, n) \
39
0
  { int i = 0; while (i++ < n && it != end && Ascii::isDigit(*it)) var = var*10 + ((*it++) - '0'); }
40
41
42
#define PARSE_FRACTIONAL_N(var, n) \
43
0
  { int i = 0; while (i < n && it != end && Ascii::isDigit(*it)) { var = var*10 + ((*it++) - '0'); i++; } while (i++ < n) var *= 10; }
44
45
46
inline std::string cleanedInputString(const std::string& str)
47
0
{
48
0
  return Poco::trim(str);
49
0
}
50
51
void DateTimeParser::parse(const std::string& fmt, const std::string& dtStr, DateTime& dateTime, int& timeZoneDifferential)
52
0
{
53
0
  const auto str = cleanedInputString(dtStr);
54
55
0
  if (fmt.empty() || str.empty() || (DateTimeFormat::hasFormat(fmt) && !DateTimeFormat::isValid(str)))
56
0
    throw SyntaxException("Invalid DateTimeString:" + dtStr);
57
58
0
  int year   = 0;
59
0
  int month  = 0;
60
0
  int day    = 0;
61
0
  int hour   = 0;
62
0
  int minute = 0;
63
0
  int second = 0;
64
0
  int millis = 0;
65
0
  int micros = 0;
66
0
  int tzd    = 0;
67
68
0
  bool dayParsed = false;
69
0
  bool monthParsed = false;
70
71
0
  std::string::const_iterator it   = str.begin();
72
0
  std::string::const_iterator end  = str.end();
73
0
  std::string::const_iterator itf  = fmt.begin();
74
0
  std::string::const_iterator endf = fmt.end();
75
76
0
  while (itf != endf && it != end)
77
0
  {
78
0
    if (*itf == '%')
79
0
    {
80
0
      if (++itf != endf)
81
0
      {
82
0
        switch (*itf)
83
0
        {
84
0
        case 'w':
85
0
        case 'W':
86
0
          while (it != end && Ascii::isSpace(*it)) ++it;
87
0
          while (it != end && Ascii::isAlpha(*it)) ++it;
88
0
          break;
89
0
        case 'b':
90
0
        case 'B':
91
0
          month = parseMonth(it, end);
92
0
          monthParsed = true;
93
0
          break;
94
0
        case 'd':
95
0
        case 'e':
96
0
        case 'f':
97
0
          SKIP_JUNK();
98
0
          PARSE_NUMBER_N(day, 2);
99
0
          dayParsed = true;
100
0
          break;
101
0
        case 'm':
102
0
        case 'n':
103
0
        case 'o':
104
0
          SKIP_JUNK();
105
0
          PARSE_NUMBER_N(month, 2);
106
0
          monthParsed = true;
107
0
          break;
108
0
        case 'y':
109
0
          SKIP_JUNK();
110
0
          PARSE_NUMBER_N(year, 2);
111
0
          if (year >= 69)
112
0
            year += 1900;
113
0
          else
114
0
            year += 2000;
115
0
          break;
116
0
        case 'Y':
117
0
          SKIP_JUNK();
118
0
          PARSE_NUMBER_N(year, 4);
119
0
          break;
120
0
        case 'r':
121
0
          SKIP_JUNK();
122
0
          PARSE_NUMBER(year);
123
0
          if (year < 1000)
124
0
          {
125
0
            if (year >= 69)
126
0
              year += 1900;
127
0
            else
128
0
              year += 2000;
129
0
          }
130
0
          break;
131
0
        case 'H':
132
0
        case 'h':
133
0
          SKIP_JUNK();
134
0
          PARSE_NUMBER_N(hour, 2);
135
0
          break;
136
0
        case 'a':
137
0
        case 'A':
138
0
          hour = parseAMPM(it, end, hour);
139
0
          break;
140
0
        case 'M':
141
0
          SKIP_JUNK();
142
0
          PARSE_NUMBER_N(minute, 2);
143
0
          break;
144
0
        case 'S':
145
0
          SKIP_JUNK();
146
0
          PARSE_NUMBER_N(second, 2);
147
0
          break;
148
0
        case 's':
149
0
          SKIP_JUNK();
150
0
          PARSE_NUMBER_N(second, 2);
151
0
          if (it != end && (*it == '.' || *it == ','))
152
0
          {
153
0
            ++it;
154
0
            PARSE_FRACTIONAL_N(millis, 3);
155
0
            PARSE_FRACTIONAL_N(micros, 3);
156
0
            SKIP_DIGITS();
157
0
          }
158
0
          break;
159
0
        case 'i':
160
0
          SKIP_JUNK();
161
0
          PARSE_NUMBER_N(millis, 3);
162
0
          break;
163
0
        case 'c':
164
0
          SKIP_JUNK();
165
0
          PARSE_NUMBER_N(millis, 1);
166
0
          millis *= 100;
167
0
          break;
168
0
        case 'F':
169
0
          SKIP_JUNK();
170
0
          PARSE_FRACTIONAL_N(millis, 3);
171
0
          PARSE_FRACTIONAL_N(micros, 3);
172
0
          SKIP_DIGITS();
173
0
          break;
174
0
        case 'z':
175
0
        case 'Z':
176
0
          tzd = parseTZD(it, end);
177
0
          break;
178
0
        }
179
0
        ++itf;
180
0
      }
181
0
    }
182
0
    else ++itf;
183
0
  }
184
0
  if (!monthParsed) month = 1;
185
0
  if (!dayParsed) day = 1;
186
0
  if (DateTime::isValid(year, month, day, hour, minute, second, millis, micros))
187
0
    dateTime.assign(year, month, day, hour, minute, second, millis, micros);
188
0
  else
189
0
    throw SyntaxException("date/time component out of range");
190
191
0
  timeZoneDifferential = tzd;
192
0
}
193
194
195
DateTime DateTimeParser::parse(const std::string& fmt, const std::string& str, int& timeZoneDifferential)
196
0
{
197
0
  DateTime result;
198
0
  parse(fmt, str, result, timeZoneDifferential);
199
0
  return result;
200
0
}
201
202
203
bool DateTimeParser::tryParse(const std::string& fmt, const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
204
0
{
205
0
  try
206
0
  {
207
0
    parse(fmt, str, dateTime, timeZoneDifferential);
208
0
  }
209
0
  catch (const Exception&)
210
0
  {
211
0
    return false;
212
0
  }
213
0
  return true;
214
0
}
215
216
217
void DateTimeParser::parse(const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
218
0
{
219
0
  if (!tryParse(str, dateTime, timeZoneDifferential))
220
0
    throw SyntaxException("Unsupported or invalid date/time format");
221
0
}
222
223
224
DateTime DateTimeParser::parse(const std::string& str, int& timeZoneDifferential)
225
0
{
226
0
  DateTime result;
227
0
  if (tryParse(str, result, timeZoneDifferential))
228
0
    return result;
229
0
  else
230
0
    throw SyntaxException("Unsupported or invalid date/time format");
231
0
}
232
233
234
bool DateTimeParser::tryParse(const std::string& dtStr, DateTime& dateTime, int& timeZoneDifferential)
235
0
{
236
0
  const auto str = cleanedInputString(dtStr);
237
238
0
  if (str.length() < 4) return false;
239
240
0
  if (str[3] == ',')
241
0
    return tryParse("%w, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
242
0
  else if (str[3] == ' ')
243
0
    return tryParse(DateTimeFormat::ASCTIME_FORMAT, str, dateTime, timeZoneDifferential);
244
0
  else if (str.find(',') < 10)
245
0
    return tryParse("%W, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
246
0
  else if (Ascii::isDigit(str[0]))
247
0
  {
248
0
    if (str.find(' ') != std::string::npos || str.length() == 10)
249
0
      return tryParse(DateTimeFormat::SORTABLE_FORMAT, str, dateTime, timeZoneDifferential);
250
0
    else if (str.find('.') != std::string::npos || str.find(',') != std::string::npos)
251
0
      return tryParse(DateTimeFormat::ISO8601_FRAC_FORMAT, str, dateTime, timeZoneDifferential);
252
0
    else
253
0
      return tryParse(DateTimeFormat::ISO8601_FORMAT, str, dateTime, timeZoneDifferential);
254
0
  }
255
0
  else return false;
256
0
}
257
258
259
int DateTimeParser::parseTZD(std::string::const_iterator& it, const std::string::const_iterator& end)
260
0
{
261
0
  struct Zone
262
0
  {
263
0
    const char* designator;
264
0
    int         timeZoneDifferential;
265
0
    bool        allowsDifference;
266
0
  };
267
268
0
  static const Zone zones[] =
269
0
  {
270
0
    {"Z",           0, true},
271
0
    {"UT",          0, true},
272
0
    {"GMT",         0, true},
273
0
    {"BST",    1*3600, false},
274
0
    {"IST",    1*3600, false},
275
0
    {"WET",         0, false},
276
0
    {"WEST",   1*3600, false},
277
0
    {"CET",    1*3600, false},
278
0
    {"CEST",   2*3600, false},
279
0
    {"EET",    2*3600, false},
280
0
    {"EEST",   3*3600, false},
281
0
    {"MSK",    3*3600, false},
282
0
    {"MSD",    4*3600, false},
283
0
    {"NST",   -3*3600-1800, false},
284
0
    {"NDT",   -2*3600-1800, false},
285
0
    {"AST",   -4*3600, false},
286
0
    {"ADT",   -3*3600, false},
287
0
    {"EST",   -5*3600, false},
288
0
    {"EDT",   -4*3600, false},
289
0
    {"CST",   -6*3600, false},
290
0
    {"CDT",   -5*3600, false},
291
0
    {"MST",   -7*3600, false},
292
0
    {"MDT",   -6*3600, false},
293
0
    {"PST",   -8*3600, false},
294
0
    {"PDT",   -7*3600, false},
295
0
    {"AKST",  -9*3600, false},
296
0
    {"AKDT",  -8*3600, false},
297
0
    {"HST",  -10*3600, false},
298
0
    {"AEST",  10*3600, false},
299
0
    {"AEDT",  11*3600, false},
300
0
    {"ACST",   9*3600+1800, false},
301
0
    {"ACDT",  10*3600+1800, false},
302
0
    {"AWST",   8*3600, false},
303
0
    {"AWDT",   9*3600, false}
304
0
  };
305
306
0
  int tzd = 0;
307
0
  while (it != end && Ascii::isSpace(*it)) ++it;
308
0
  const Zone* zone = nullptr;
309
0
  std::string designator;
310
0
  if (it != end)
311
0
  {
312
0
    if (Ascii::isAlpha(*it))
313
0
    {
314
0
      designator += *it++;
315
0
      if (it != end && Ascii::isAlpha(*it)) designator += *it++;
316
0
      if (it != end && Ascii::isAlpha(*it)) designator += *it++;
317
0
      if (it != end && Ascii::isAlpha(*it)) designator += *it++;
318
0
      for (unsigned i = 0; i < sizeof(zones)/sizeof(Zone); ++i)
319
0
      {
320
0
        if (designator == zones[i].designator)
321
0
        {
322
0
          zone = &(zones[i]);
323
0
          tzd = zone->timeZoneDifferential;
324
0
          break;
325
0
        }
326
0
      }
327
0
    }
328
0
    if (!designator.empty() && !zone)
329
0
      throw SyntaxException("Unknown timezone designator "s + designator);
330
331
0
    if (it != end && (*it == '+' || *it == '-'))
332
0
    {
333
      // Time difference is allowed only for some timezone designators in general
334
      // Some formats prevent even that with regular expression
335
0
      if (zone && !zone->allowsDifference)
336
0
        throw SyntaxException("Timezone does not allow difference "s + zone->designator);
337
338
0
      int sign = *it == '+' ? 1 : -1;
339
0
      ++it;
340
0
      int hours = 0;
341
0
      PARSE_NUMBER_N(hours, 2);
342
0
      if (hours < 0 || hours > 23)
343
0
        throw SyntaxException("Timezone difference hours out of range");
344
0
      if (it != end && *it == ':') ++it;
345
0
      int minutes = 0;
346
0
      PARSE_NUMBER_N(minutes, 2);
347
0
      if (minutes < 0 || minutes > 59)
348
0
        throw SyntaxException("Timezone difference minutes out of range");
349
0
      tzd += sign*(hours*3600 + minutes*60);
350
0
    }
351
0
  }
352
0
  return tzd;
353
0
}
354
355
356
int DateTimeParser::parseMonth(std::string::const_iterator& it, const std::string::const_iterator& end)
357
0
{
358
0
  std::string month;
359
0
  while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
360
0
  bool isFirst = true;
361
0
  while (it != end && Ascii::isAlpha(*it))
362
0
  {
363
0
    char ch = (*it++);
364
0
    if (isFirst) { month += Ascii::toUpper(ch); isFirst = false; }
365
0
    else month += Ascii::toLower(ch);
366
0
  }
367
0
  if (month.length() < 3) throw SyntaxException("Month name must be at least three characters long", month);
368
0
  for (int i = 0; i < 12; ++i)
369
0
  {
370
0
    if (DateTimeFormat::MONTH_NAMES[i].find(month) == 0)
371
0
      return i + 1;
372
0
  }
373
0
  throw SyntaxException("Not a valid month name", month);
374
0
}
375
376
377
int DateTimeParser::parseDayOfWeek(std::string::const_iterator& it, const std::string::const_iterator& end)
378
0
{
379
0
  std::string dow;
380
0
  while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
381
0
  bool isFirst = true;
382
0
  while (it != end && Ascii::isAlpha(*it))
383
0
  {
384
0
    char ch = (*it++);
385
0
    if (isFirst) { dow += Ascii::toUpper(ch); isFirst = false; }
386
0
    else dow += Ascii::toLower(ch);
387
0
  }
388
0
  if (dow.length() < 3) throw SyntaxException("Weekday name must be at least three characters long", dow);
389
0
  for (int i = 0; i < 7; ++i)
390
0
  {
391
0
    if (DateTimeFormat::WEEKDAY_NAMES[i].find(dow) == 0)
392
0
      return i;
393
0
  }
394
0
  throw SyntaxException("Not a valid weekday name", dow);
395
0
}
396
397
398
int DateTimeParser::parseAMPM(std::string::const_iterator& it, const std::string::const_iterator& end, int hour)
399
0
{
400
0
  std::string ampm;
401
0
  while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
402
0
  while (it != end && Ascii::isAlpha(*it))
403
0
  {
404
0
    char ch = (*it++);
405
0
    ampm += Ascii::toUpper(ch);
406
0
  }
407
0
  if (ampm == "AM")
408
0
  {
409
0
    if (hour == 12)
410
0
      return 0;
411
0
    else
412
0
      return hour;
413
0
  }
414
0
  else if (ampm == "PM")
415
0
  {
416
0
    if (hour < 12)
417
0
      return hour + 12;
418
0
    else
419
0
      return hour;
420
0
  }
421
0
  else throw SyntaxException("Not a valid AM/PM designator", ampm);
422
0
}
423
424
425
} // namespace Poco