Coverage Report

Created: 2023-12-02 06:12

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