]> git.armaanb.net Git - gen-shell.git/blob - src/libshared/src/Duration.cpp
added install instructions
[gen-shell.git] / src / libshared / src / Duration.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2017, 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 ////////////////////////////////////////////////////////////////////////////////
111 Duration::Duration ()
112 {
113   clear ();
114 }
115
116 ////////////////////////////////////////////////////////////////////////////////
117 Duration::Duration (const std::string& input)
118 {
119   clear ();
120   std::string::size_type idx = 0;
121   parse (input, idx);
122 }
123
124 ////////////////////////////////////////////////////////////////////////////////
125 Duration::Duration (time_t input)
126 {
127   clear ();
128   _period = input;
129 }
130
131 ////////////////////////////////////////////////////////////////////////////////
132 bool Duration::operator< (const Duration& other)
133 {
134   return _period < other._period;
135 }
136
137 ////////////////////////////////////////////////////////////////////////////////
138 bool Duration::operator> (const Duration& other)
139 {
140   return _period > other._period;
141 }
142
143 ////////////////////////////////////////////////////////////////////////////////
144 bool Duration::operator<= (const Duration& other)
145 {
146   return _period <= other._period;
147 }
148
149 ////////////////////////////////////////////////////////////////////////////////
150 bool Duration::operator>= (const Duration& other)
151 {
152   return _period >= other._period;
153 }
154
155 ////////////////////////////////////////////////////////////////////////////////
156 std::string Duration::toString () const
157 {
158   std::stringstream s;
159   s << _period;
160   return s.str ();
161 }
162
163 ////////////////////////////////////////////////////////////////////////////////
164 time_t Duration::toTime_t () const
165 {
166   return _period;
167 }
168
169 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
197 bool Duration::parse_seconds (Pig& pig)
198 {
199   auto checkpoint = pig.cursor ();
200
201   int 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']]
217 bool Duration::parse_designated (Pig& pig)
218 {
219   auto checkpoint = pig.cursor ();
220
221   if (pig.skip ('P') &&
222       ! pig.eos ())
223   {
224     int 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']
278 bool Duration::parse_weeks (Pig& pig)
279 {
280   auto checkpoint = pig.cursor ();
281
282   if (pig.skip ('P') &&
283       ! pig.eos ())
284   {
285     int 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 ////////////////////////////////////////////////////////////////////////////////
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 <int> (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 <int> (number * static_cast <double> (seconds));
374             return true;
375           }
376         }
377       }
378     }
379   }
380
381   pig.restoreTo (checkpoint);
382   return false;
383 }
384
385 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
399 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 ////////////////////////////////////////////////////////////////////////////////
428 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 ////////////////////////////////////////////////////////////////////////////////
453 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 //
494 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 ////////////////////////////////////////////////////////////////////////////////
511 int Duration::days () const
512 {
513   return _period / 86400;
514 }
515
516 ////////////////////////////////////////////////////////////////////////////////
517 int Duration::hours () const
518 {
519   return _period / 3600;
520 }
521
522 ////////////////////////////////////////////////////////////////////////////////
523 int Duration::minutes () const
524 {
525   return _period / 60;
526 }
527
528 ////////////////////////////////////////////////////////////////////////////////
529 int Duration::seconds () const
530 {
531   return _period;
532 }
533
534 ////////////////////////////////////////////////////////////////////////////////
535 // Allow un-normalized values.
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 ////////////////////////////////////////////////////////////////////////////////