1 ////////////////////////////////////////////////////////////////////////////////
3 // Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
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:
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
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
23 // http://www.opensource.org/licenses/mit-license.php
25 ////////////////////////////////////////////////////////////////////////////////
34 bool Duration::standaloneSecondsEnabled = true;
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},
108 #define NUM_DURATIONS (sizeof (durations) / sizeof (durations[0]))
110 ////////////////////////////////////////////////////////////////////////////////
111 Duration::Duration ()
116 ////////////////////////////////////////////////////////////////////////////////
117 Duration::Duration (const std::string& input)
120 std::string::size_type idx = 0;
124 ////////////////////////////////////////////////////////////////////////////////
125 Duration::Duration (time_t input)
131 ////////////////////////////////////////////////////////////////////////////////
132 bool Duration::operator< (const Duration& other)
134 return _period < other._period;
137 ////////////////////////////////////////////////////////////////////////////////
138 bool Duration::operator> (const Duration& other)
140 return _period > other._period;
143 ////////////////////////////////////////////////////////////////////////////////
144 bool Duration::operator<= (const Duration& other)
146 return _period <= other._period;
149 ////////////////////////////////////////////////////////////////////////////////
150 bool Duration::operator>= (const Duration& other)
152 return _period >= other._period;
155 ////////////////////////////////////////////////////////////////////////////////
156 std::string Duration::toString () const
163 ////////////////////////////////////////////////////////////////////////////////
164 time_t Duration::toTime_t () const
169 ////////////////////////////////////////////////////////////////////////////////
170 bool Duration::parse (const std::string& input, std::string::size_type& start)
175 pig.skipN (static_cast <int> (i));
177 if (Duration::standaloneSecondsEnabled && parse_seconds (pig))
179 // ::resolve is not needed in this case.
180 start = pig.cursor ();
184 else if (parse_designated (pig) ||
188 start = pig.cursor ();
196 ////////////////////////////////////////////////////////////////////////////////
197 bool Duration::parse_seconds (Pig& pig)
199 auto checkpoint = pig.cursor ();
202 if (pig.getDigits (epoch) &&
203 ! unicodeLatinAlpha (pig.peek ()) &&
207 _period = static_cast <time_t> (epoch);
211 pig.restoreTo (checkpoint);
215 ////////////////////////////////////////////////////////////////////////////////
216 // 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']]
217 bool Duration::parse_designated (Pig& pig)
219 auto checkpoint = pig.cursor ();
221 if (pig.skip ('P') &&
226 if (pig.getDigits (value) && pig.skip ('Y'))
232 if (pig.getDigits (value) && pig.skip ('M'))
238 if (pig.getDigits (value) && pig.skip ('D'))
243 if (pig.skip ('T') &&
247 if (pig.getDigits (value) && pig.skip ('H'))
253 if (pig.getDigits (value) && pig.skip ('M'))
259 if (pig.getDigits (value) && pig.skip ('S'))
265 auto following = pig.peek ();
266 if (pig.cursor () - checkpoint >= 3 &&
267 ! unicodeLatinAlpha (following) &&
268 ! unicodeLatinDigit (following))
272 pig.restoreTo (checkpoint);
276 ////////////////////////////////////////////////////////////////////////////////
278 bool Duration::parse_weeks (Pig& pig)
280 auto checkpoint = pig.cursor ();
282 if (pig.skip ('P') &&
287 if (pig.getDigits (value) && pig.skip ('W'))
292 auto following = pig.peek ();
293 if (pig.cursor () - checkpoint >= 3 &&
294 ! unicodeLatinAlpha (following) &&
295 ! unicodeLatinDigit (following))
299 pig.restoreTo (checkpoint);
303 ////////////////////////////////////////////////////////////////////////////////
304 bool Duration::parse_units (Pig& pig)
306 auto checkpoint = pig.cursor ();
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);
316 if (pig.getOneOf (units, unit))
318 auto following = pig.peek ();
319 if (! unicodeLatinAlpha (following) &&
320 ! unicodeLatinDigit (following))
322 for (unsigned int i = 0; i < NUM_DURATIONS; i++)
324 if (durations[i].unit == unit &&
325 durations[i].standalone)
327 _period = static_cast <int> (durations[i].seconds);
333 pig.restoreTo (checkpoint);
336 else if (pig.getDecimal (number))
339 if (pig.getOneOf (units, unit))
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
345 // 1111111d-0000-0000-0000-000000000000
347 // Because Lexer::isDuration is higher precedence than Lexer::isUUID,
348 // the above UUID looks like:
350 // <1111111d> <-> ...
353 // So as a special case, durations, with units of "d" are rejected if the
354 // quantity exceeds 10000.
356 if (unit == "d" && number > 10000.0)
358 pig.restoreTo (checkpoint);
362 auto following = pig.peek ();
363 if (! unicodeLatinAlpha (following) &&
364 ! unicodeLatinDigit (following))
366 // Linear lookup - should instead be logarithmic.
368 for (unsigned int i = 0; i < NUM_DURATIONS; i++)
370 if (durations[i].unit == unit)
372 seconds = durations[i].seconds;
373 _period = static_cast <int> (number * static_cast <double> (seconds));
381 pig.restoreTo (checkpoint);
385 ////////////////////////////////////////////////////////////////////////////////
386 void Duration::clear ()
398 ////////////////////////////////////////////////////////////////////////////////
399 const std::string Duration::format () const
404 int seconds = t % 60; t /= 60;
405 int minutes = t % 60; t /= 60;
406 int hours = t % 24; t /= 24;
415 << std::setw (2) << std::setfill ('0') << minutes
417 << std::setw (2) << std::setfill ('0') << seconds;
427 ////////////////////////////////////////////////////////////////////////////////
428 const std::string Duration::formatHours () const
433 int seconds = t % 60; t /= 60;
434 int minutes = t % 60; t /= 60;
440 << std::setw (2) << std::setfill ('0') << minutes
442 << std::setw (2) << std::setfill ('0') << seconds;
452 ////////////////////////////////////////////////////////////////////////////////
453 const std::string Duration::formatISO () const
458 int seconds = t % 60; t /= 60;
459 int minutes = t % 60; t /= 60;
460 int hours = t % 24; t /= 24;
465 if (days) s << days << 'D';
467 if (hours || minutes || seconds)
470 if (hours) s << hours << 'H';
471 if (minutes) s << minutes << 'M';
472 if (seconds) s << seconds << 'S';
483 ////////////////////////////////////////////////////////////////////////////////
484 // Range Representation
485 // --------- ---------------------
494 const std::string Duration::formatVague (bool padding) const
496 float days = (float) _period / 86400.0;
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");
507 return formatted.str ();
510 ////////////////////////////////////////////////////////////////////////////////
511 int Duration::days () const
513 return _period / 86400;
516 ////////////////////////////////////////////////////////////////////////////////
517 int Duration::hours () const
519 return _period / 3600;
522 ////////////////////////////////////////////////////////////////////////////////
523 int Duration::minutes () const
528 ////////////////////////////////////////////////////////////////////////////////
529 int Duration::seconds () const
534 ////////////////////////////////////////////////////////////////////////////////
535 // Allow un-normalized values.
536 void Duration::resolve ()
541 _period = (_weeks * 7 * 86400);
543 _period = (_year * 365 * 86400) +
544 (_month * 30 * 86400) +
552 ////////////////////////////////////////////////////////////////////////////////