1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2021, Paul Beckingham, Federico Hernandez.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 //
23 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26
27 #include <cmake.h>
28 #include <Duration.h>
29 #include <unicode.h>
30 #include <sstream>
31 #include <iomanip>
32 #include <vector>
33
34 bool Duration::standaloneSecondsEnabled = true;
35
36 #define DAY 86400
37 #define HOUR 3600
38 #define MINUTE 60
39 #define SECOND 1
40
41 static struct
42 {
43 std::string unit;
44 int seconds;
45 bool standalone;
46 } durations[] =
47 {
48 // These are sorted by first character, then length, so that Pig::getOneOf
49 // returns a maximal match.
50 {"annual", 365 * DAY, true },
51 {"biannual", 730 * DAY, true },
52 {"bimonthly", 61 * DAY, true },
53 {"biweekly", 14 * DAY, true },
54 {"biyearly", 730 * DAY, true },
55 {"daily", 1 * DAY, true },
56 {"days", 1 * DAY, false},
57 {"day", 1 * DAY, true },
58 {"d", 1 * DAY, false},
59 {"fortnight", 14 * DAY, true },
60 {"hours", 1 * HOUR, false},
61 {"hour", 1 * HOUR, true },
62 {"hrs", 1 * HOUR, false},
63 {"hr", 1 * HOUR, true },
64 {"h", 1 * HOUR, false},
65 {"minutes", 1 * MINUTE, false},
66 {"minute", 1 * MINUTE, true },
67 {"mins", 1 * MINUTE, false},
68 {"min", 1 * MINUTE, true },
69 {"monthly", 30 * DAY, true },
70 {"months", 30 * DAY, false},
71 {"month", 30 * DAY, true },
72 {"mnths", 30 * DAY, false},
73 {"mths", 30 * DAY, false},
74 {"mth", 30 * DAY, true },
75 {"mos", 30 * DAY, false},
76 {"mo", 30 * DAY, true },
77 {"m", 30 * DAY, false},
78 {"quarterly", 91 * DAY, true },
79 {"quarters", 91 * DAY, false},
80 {"quarter", 91 * DAY, true },
81 {"qrtrs", 91 * DAY, false},
82 {"qrtr", 91 * DAY, true },
83 {"qtrs", 91 * DAY, false},
84 {"qtr", 91 * DAY, true },
85 {"q", 91 * DAY, false},
86 {"semiannual", 183 * DAY, true },
87 {"sennight", 14 * DAY, false},
88 {"seconds", 1 * SECOND, false},
89 {"second", 1 * SECOND, true },
90 {"secs", 1 * SECOND, false},
91 {"sec", 1 * SECOND, true },
92 {"s", 1 * SECOND, false},
93 {"weekdays", 1 * DAY, true },
94 {"weekly", 7 * DAY, true },
95 {"weeks", 7 * DAY, false},
96 {"week", 7 * DAY, true },
97 {"wks", 7 * DAY, false},
98 {"wk", 7 * DAY, true },
99 {"w", 7 * DAY, false},
100 {"yearly", 365 * DAY, true },
101 {"years", 365 * DAY, false},
102 {"year", 365 * DAY, true },
103 {"yrs", 365 * DAY, false},
104 {"yr", 365 * DAY, true },
105 {"y", 365 * DAY, false},
106 };
107
108 #define NUM_DURATIONS (sizeof (durations) / sizeof (durations[0]))
109
110 ////////////////////////////////////////////////////////////////////////////////
Duration()111 Duration::Duration ()
112 {
113 clear ();
114 }
115
116 ////////////////////////////////////////////////////////////////////////////////
Duration(const std::string & input)117 Duration::Duration (const std::string& input)
118 {
119 clear ();
120 std::string::size_type idx = 0;
121 parse (input, idx);
122 }
123
124 ////////////////////////////////////////////////////////////////////////////////
Duration(time_t input)125 Duration::Duration (time_t input)
126 {
127 clear ();
128 _period = input;
129 }
130
131 ////////////////////////////////////////////////////////////////////////////////
operator <(const Duration & other)132 bool Duration::operator< (const Duration& other)
133 {
134 return _period < other._period;
135 }
136
137 ////////////////////////////////////////////////////////////////////////////////
operator >(const Duration & other)138 bool Duration::operator> (const Duration& other)
139 {
140 return _period > other._period;
141 }
142
143 ////////////////////////////////////////////////////////////////////////////////
operator <=(const Duration & other)144 bool Duration::operator<= (const Duration& other)
145 {
146 return _period <= other._period;
147 }
148
149 ////////////////////////////////////////////////////////////////////////////////
operator >=(const Duration & other)150 bool Duration::operator>= (const Duration& other)
151 {
152 return _period >= other._period;
153 }
154
155 ////////////////////////////////////////////////////////////////////////////////
toString() const156 std::string Duration::toString () const
157 {
158 std::stringstream s;
159 s << _period;
160 return s.str ();
161 }
162
163 ////////////////////////////////////////////////////////////////////////////////
toTime_t() const164 time_t Duration::toTime_t () const
165 {
166 return _period;
167 }
168
169 ////////////////////////////////////////////////////////////////////////////////
parse(const std::string & input,std::string::size_type & start)170 bool Duration::parse (const std::string& input, std::string::size_type& start)
171 {
172 auto i = start;
173 Pig pig (input);
174 if (i)
175 pig.skipN (static_cast <int> (i));
176
177 if (Duration::standaloneSecondsEnabled && parse_seconds (pig))
178 {
179 // ::resolve is not needed in this case.
180 start = pig.cursor ();
181 return true;
182 }
183
184 else if (parse_designated (pig) ||
185 parse_weeks (pig) ||
186 parse_units (pig))
187 {
188 start = pig.cursor ();
189 resolve ();
190 return true;
191 }
192
193 return false;
194 }
195
196 ////////////////////////////////////////////////////////////////////////////////
parse_seconds(Pig & pig)197 bool Duration::parse_seconds (Pig& pig)
198 {
199 auto checkpoint = pig.cursor ();
200
201 long long epoch {};
202 if (pig.getDigits (epoch) &&
203 ! unicodeLatinAlpha (pig.peek ()) &&
204 (epoch == 0 ||
205 epoch > 60))
206 {
207 _period = static_cast <time_t> (epoch);
208 return true;
209 }
210
211 pig.restoreTo (checkpoint);
212 return false;
213 }
214
215 ////////////////////////////////////////////////////////////////////////////////
216 // 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']]
parse_designated(Pig & pig)217 bool Duration::parse_designated (Pig& pig)
218 {
219 auto checkpoint = pig.cursor ();
220
221 if (pig.skip ('P') &&
222 ! pig.eos ())
223 {
224 long long value;
225 pig.save ();
226 if (pig.getDigits (value) && pig.skip ('Y'))
227 _year = value;
228 else
229 pig.restore ();
230
231 pig.save ();
232 if (pig.getDigits (value) && pig.skip ('M'))
233 _month = value;
234 else
235 pig.restore ();
236
237 pig.save ();
238 if (pig.getDigits (value) && pig.skip ('D'))
239 _day = value;
240 else
241 pig.restore ();
242
243 if (pig.skip ('T') &&
244 ! pig.eos ())
245 {
246 pig.save ();
247 if (pig.getDigits (value) && pig.skip ('H'))
248 _hours = value;
249 else
250 pig.restore ();
251
252 pig.save ();
253 if (pig.getDigits (value) && pig.skip ('M'))
254 _minutes = value;
255 else
256 pig.restore ();
257
258 pig.save ();
259 if (pig.getDigits (value) && pig.skip ('S'))
260 _seconds = value;
261 else
262 pig.restore ();
263 }
264
265 auto following = pig.peek ();
266 if (pig.cursor () - checkpoint >= 3 &&
267 ! unicodeLatinAlpha (following) &&
268 ! unicodeLatinDigit (following))
269 return true;
270 }
271
272 pig.restoreTo (checkpoint);
273 return false;
274 }
275
276 ////////////////////////////////////////////////////////////////////////////////
277 // 'P' [nn 'W']
parse_weeks(Pig & pig)278 bool Duration::parse_weeks (Pig& pig)
279 {
280 auto checkpoint = pig.cursor ();
281
282 if (pig.skip ('P') &&
283 ! pig.eos ())
284 {
285 long long value;
286 pig.save ();
287 if (pig.getDigits (value) && pig.skip ('W'))
288 _weeks = value;
289 else
290 pig.restore ();
291
292 auto following = pig.peek ();
293 if (pig.cursor () - checkpoint >= 3 &&
294 ! unicodeLatinAlpha (following) &&
295 ! unicodeLatinDigit (following))
296 return true;
297 }
298
299 pig.restoreTo (checkpoint);
300 return false;
301 }
302
303 ////////////////////////////////////////////////////////////////////////////////
parse_units(Pig & pig)304 bool Duration::parse_units (Pig& pig)
305 {
306 auto checkpoint = pig.cursor ();
307
308 // Static and so preserved between calls.
309 static std::vector <std::string> units;
310 if (units.size () == 0)
311 for (unsigned int i = 0; i < NUM_DURATIONS; i++)
312 units.push_back (durations[i].unit);
313
314 double number;
315 std::string unit;
316 if (pig.getOneOf (units, unit))
317 {
318 auto following = pig.peek ();
319 if (! unicodeLatinAlpha (following) &&
320 ! unicodeLatinDigit (following))
321 {
322 for (unsigned int i = 0; i < NUM_DURATIONS; i++)
323 {
324 if (durations[i].unit == unit &&
325 durations[i].standalone)
326 {
327 _period = static_cast <time_t> (durations[i].seconds);
328 return true;
329 }
330 }
331 }
332 else
333 pig.restoreTo (checkpoint);
334 }
335
336 else if (pig.getDecimal (number))
337 {
338 pig.skipWS ();
339 if (pig.getOneOf (units, unit))
340 {
341 // The "d" unit is a special case, because it is the only one that can
342 // legitimately occur at the beginning of a UUID, and be followed by an
343 // operator:
344 //
345 // 1111111d-0000-0000-0000-000000000000
346 //
347 // Because Lexer::isDuration is higher precedence than Lexer::isUUID,
348 // the above UUID looks like:
349 //
350 // <1111111d> <-> ...
351 // duration op ...
352 //
353 // So as a special case, durations, with units of "d" are rejected if the
354 // quantity exceeds 10000.
355 //
356 if (unit == "d" && number > 10000.0)
357 {
358 pig.restoreTo (checkpoint);
359 return false;
360 }
361
362 auto following = pig.peek ();
363 if (! unicodeLatinAlpha (following) &&
364 ! unicodeLatinDigit (following))
365 {
366 // Linear lookup - should instead be logarithmic.
367 double seconds = 1;
368 for (unsigned int i = 0; i < NUM_DURATIONS; i++)
369 {
370 if (durations[i].unit == unit)
371 {
372 seconds = durations[i].seconds;
373 _period = static_cast <time_t> (number * static_cast <double> (seconds));
374 return true;
375 }
376 }
377 }
378 }
379 }
380
381 pig.restoreTo (checkpoint);
382 return false;
383 }
384
385 ////////////////////////////////////////////////////////////////////////////////
clear()386 void Duration::clear ()
387 {
388 _year = 0;
389 _month = 0;
390 _weeks = 0;
391 _day = 0;
392 _hours = 0;
393 _minutes = 0;
394 _seconds = 0;
395 _period = 0;
396 }
397
398 ////////////////////////////////////////////////////////////////////////////////
format() const399 const std::string Duration::format () const
400 {
401 if (_period)
402 {
403 time_t t = _period;
404 int seconds = t % 60; t /= 60;
405 int minutes = t % 60; t /= 60;
406 int hours = t % 24; t /= 24;
407 int days = t;
408
409 std::stringstream s;
410 if (days)
411 s << days << "d ";
412
413 s << hours
414 << ':'
415 << std::setw (2) << std::setfill ('0') << minutes
416 << ':'
417 << std::setw (2) << std::setfill ('0') << seconds;
418
419 return s.str ();
420 }
421 else
422 {
423 return "0:00:00";
424 }
425 }
426
427 ////////////////////////////////////////////////////////////////////////////////
formatHours() const428 const std::string Duration::formatHours () const
429 {
430 if (_period)
431 {
432 time_t t = _period;
433 int seconds = t % 60; t /= 60;
434 int minutes = t % 60; t /= 60;
435 int hours = t;
436
437 std::stringstream s;
438 s << hours
439 << ':'
440 << std::setw (2) << std::setfill ('0') << minutes
441 << ':'
442 << std::setw (2) << std::setfill ('0') << seconds;
443
444 return s.str ();
445 }
446 else
447 {
448 return "0:00:00";
449 }
450 }
451
452 ////////////////////////////////////////////////////////////////////////////////
formatISO() const453 const std::string Duration::formatISO () const
454 {
455 if (_period)
456 {
457 time_t t = _period;
458 int seconds = t % 60; t /= 60;
459 int minutes = t % 60; t /= 60;
460 int hours = t % 24; t /= 24;
461 int days = t;
462
463 std::stringstream s;
464 s << 'P';
465 if (days) s << days << 'D';
466
467 if (hours || minutes || seconds)
468 {
469 s << 'T';
470 if (hours) s << hours << 'H';
471 if (minutes) s << minutes << 'M';
472 if (seconds) s << seconds << 'S';
473 }
474
475 return s.str ();
476 }
477 else
478 {
479 return "PT0S";
480 }
481 }
482
483 ////////////////////////////////////////////////////////////////////////////////
484 // Range Representation
485 // --------- ---------------------
486 // >= 365d {n.n}y
487 // >= 90d {n}mo
488 // >= 14d {n}w
489 // >= 1d {n}d
490 // >= 1h {n}h
491 // >= 1min {n}min
492 // {n}s
493 //
formatVague(bool padding) const494 const std::string Duration::formatVague (bool padding) const
495 {
496 float days = (float) _period / 86400.0;
497
498 std::stringstream formatted;
499 if (_period >= 86400 * 365) formatted << std::fixed << std::setprecision (1) << (days / 365) << (padding ? "y " : "y");
500 else if (_period >= 86400 * 90) formatted << static_cast <int> (days / 30) << (padding ? "mo " : "mo");
501 else if (_period >= 86400 * 14) formatted << static_cast <int> (days / 7) << (padding ? "w " : "w");
502 else if (_period >= 86400) formatted << static_cast <int> (days) << (padding ? "d " : "d");
503 else if (_period >= 3600) formatted << static_cast <int> (_period / 3600) << (padding ? "h " : "h");
504 else if (_period >= 60) formatted << static_cast <int> (_period / 60) << "min"; // Longest suffix - no padding
505 else if (_period >= 1) formatted << static_cast <int> (_period) << (padding ? "s " : "s");
506
507 return formatted.str ();
508 }
509
510 ////////////////////////////////////////////////////////////////////////////////
days() const511 int Duration::days () const
512 {
513 return _period / 86400;
514 }
515
516 ////////////////////////////////////////////////////////////////////////////////
hours() const517 int Duration::hours () const
518 {
519 return _period / 3600;
520 }
521
522 ////////////////////////////////////////////////////////////////////////////////
minutes() const523 int Duration::minutes () const
524 {
525 return _period / 60;
526 }
527
528 ////////////////////////////////////////////////////////////////////////////////
seconds() const529 time_t Duration::seconds () const
530 {
531 return _period;
532 }
533
534 ////////////////////////////////////////////////////////////////////////////////
535 // Allow un-normalized values.
resolve()536 void Duration::resolve ()
537 {
538 if (! _period)
539 {
540 if (_weeks)
541 _period = (_weeks * 7 * 86400);
542 else
543 _period = (_year * 365 * 86400) +
544 (_month * 30 * 86400) +
545 (_day * 86400) +
546 (_hours * 3600) +
547 (_minutes * 60) +
548 _seconds;
549 }
550 }
551
552 ////////////////////////////////////////////////////////////////////////////////
553