1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include <math.h>
8 #include <cstdio>
9 
10 #include "Wt/WException.h"
11 #include "Wt/WLocalDateTime.h"
12 #include "Wt/WTime.h"
13 #include "Wt/WLogger.h"
14 #include "Wt/Date/date.h"
15 
16 #include "WebUtils.h"
17 
18 namespace Wt {
19 
20 LOGGER("WTime");
21 
WTime()22 WTime::WTime()
23   : valid_(false),
24     null_(true),
25     time_(0)
26 { }
27 
WTime(int h,int m,int s,int ms)28 WTime::WTime(int h, int m, int s, int ms)
29   : valid_(false),
30     null_(false),
31     time_(0)
32 {
33   setHMS(h, m, s, ms);
34 }
35 
setHMS(int h,int m,int s,int ms)36 bool WTime::setHMS(int h, int m, int s, int ms)
37 {
38   null_ = false;
39   if (   m >= 0 && m <= 59
40          && s >= 0 && s <= 59
41          && ms >= 0 && ms <= 999) {
42     valid_ = true;
43     bool negative = h < 0;
44 
45     if (negative)
46       h = -h;
47 
48     time_ = ((h * 60 + m) * 60 + s) * 1000 + ms;
49 
50     if (negative)
51       time_ = -time_;
52   } else {
53     LOG_WARN("Invalid time: " << h << ":" << m << ":" << s << "." << ms);
54   }
55 
56   return valid_;
57 }
58 
addMSecs(int ms)59 WTime WTime::addMSecs(int ms) const
60 {
61   if (valid_)
62     return WTime(time_ + ms);
63   else
64     return *this;
65 }
66 
addSecs(int s)67 WTime WTime::addSecs(int s) const
68 {
69   return addMSecs(1000 * s);
70 }
71 
hour()72 int WTime::hour() const
73 {
74   return time_ / (1000 * 60 * 60);
75 }
76 
minute()77 int WTime::minute() const
78 {
79   return (std::abs((int)time_) / (1000 * 60)) % 60;
80 }
81 
second()82 int WTime::second() const
83 {
84   return (std::abs((int)time_) / 1000) % 60;
85 }
86 
msec()87 int WTime::msec() const
88 {
89   return std::abs((int)time_) % 1000;
90 }
91 
secsTo(const WTime & t)92 long WTime::secsTo(const WTime& t) const
93 {
94   return msecsTo(t) / 1000;
95 }
96 
msecsTo(const WTime & t)97 long WTime::msecsTo(const WTime& t) const
98 {
99   if (isValid() && t.isValid())
100     return t.time_ - time_;
101   else
102     return 0;
103 }
104 
105 bool WTime::operator> (const WTime& other) const
106 {
107   return other < *this;
108 }
109 
110 bool WTime::operator< (const WTime& other) const
111 {
112   if (isValid() && other.isValid())
113     return time_ < other.time_;
114   else
115     return false;
116 }
117 
118 bool WTime::operator!= (const WTime& other) const
119 {
120   return !(*this == other);
121 }
122 
123 bool WTime::operator== (const WTime& other) const
124 {
125   return valid_ == other.valid_ && null_ == other.null_ && time_ == other.time_;
126 }
127 
128 bool WTime::operator<= (const WTime& other) const
129 {
130   return (*this == other) || (*this < other);
131 }
132 
133 bool WTime::operator>= (const WTime& other) const
134 {
135   return other <= *this;
136 }
137 
currentTime()138 WTime WTime::currentTime()
139 {
140   return WLocalDateTime::currentDateTime().time();
141 }
142 
currentServerTime()143 WTime WTime::currentServerTime()
144 {
145   return WLocalDateTime::currentServerDateTime().time();
146 }
147 
defaultFormat()148 WString WTime::defaultFormat()
149 {
150   return WString::fromUTF8("HH:mm:ss");
151 }
152 
fromString(const WString & s)153 WTime WTime::fromString(const WString& s)
154 {
155   return fromString(s, defaultFormat());
156 }
157 
ParseState()158 WTime::ParseState::ParseState()
159 {
160   h = m = s = z = a = 0;
161   hour = minute = sec = msec = 0;
162   pm = parseAMPM = haveAMPM = false;
163 }
164 
fromString(const WString & s,const WString & format)165 WTime WTime::fromString(const WString& s, const WString& format)
166 {
167   WTime result;
168   WDateTime::fromString(nullptr, &result, s, format);
169   return result;
170 }
171 
handleSpecial(char c,const std::string & v,unsigned & vi,ParseState & parse,const WString & format)172 WDateTime::CharState WTime::handleSpecial(char c, const std::string& v,
173     unsigned& vi, ParseState& parse,
174     const WString& format)
175 {
176   switch (c) {
177     case 'H':
178     case 'h':
179       parse.parseAMPM = c == 'h';
180 
181       if (parse.h == 0)
182         if (!parseLast(v, vi, parse, format))
183           return WDateTime::CharState::CharInvalid;
184 
185       ++parse.h;
186       return WDateTime::CharState::CharHandled;
187 
188     case 'm':
189       if (parse.m == 0)
190         if (!parseLast(v, vi, parse, format))
191           return WDateTime::CharState::CharInvalid;
192 
193       ++parse.m;
194       return WDateTime::CharState::CharHandled;
195 
196     case 's':
197       if (parse.s == 0)
198         if (!parseLast(v, vi, parse, format))
199           return WDateTime::CharState::CharInvalid;
200 
201       ++parse.s;
202       return WDateTime::CharState::CharHandled;
203 
204     case 'z':
205       if (parse.z == 0)
206         if (!parseLast(v, vi, parse, format))
207           return WDateTime::CharState::CharInvalid;
208 
209       ++parse.z;
210       return WDateTime::CharState::CharHandled;
211 
212     case 'A':
213     case 'a':
214       if (!parseLast(v, vi, parse, format))
215         return WDateTime::CharState::CharInvalid;
216 
217       parse.a = 1;
218       return WDateTime::CharState::CharHandled;
219 
220     case 'P':
221     case 'p':
222       if (parse.a == 1) {
223         if (!parseLast(v, vi, parse, format))
224           return WDateTime::CharState::CharInvalid;
225 
226         return WDateTime::CharState::CharHandled;
227       }
228 
229     /* fall through */
230 
231     default:
232       if (!parseLast(v, vi, parse, format))
233         return WDateTime::CharState::CharInvalid;
234 
235       return WDateTime::CharState::CharUnhandled;
236   }
237 }
238 
fatalFormatError(const WString & format,int c,const char * cs)239 static void fatalFormatError(const WString& format, int c, const char *cs)
240 {
241   std::stringstream s;
242   s << "WTime format syntax error (for \"" << format.toUTF8()
243     << "\"): Cannot handle " << c << " consecutive " << cs;
244   throw WException(s.str());
245 }
246 
parseLast(const std::string & v,unsigned & vi,ParseState & parse,const WString & format)247 bool WTime::parseLast(const std::string& v, unsigned& vi,
248                       ParseState& parse,
249                       const WString& format)
250 {
251   const char *letter[] = { "h's", "m's", "s'es", "z's" };
252 
253   for (int i = 0; i < 4; ++i) {
254     int *count;
255     int *value;
256     int maxCount = 2;
257 
258     switch (i) {
259       case 0:
260         count = &parse.h;
261         value = &parse.hour;
262         break;
263 
264       case 1:
265         count = &parse.m;
266         value = &parse.minute;
267         break;
268 
269       case 2:
270         count = &parse.s;
271         value = &parse.sec;
272         break;
273 
274       case 3:
275         count = &parse.z;
276         value = &parse.msec;
277         maxCount = 3;
278     }
279 
280     if (*count != 0) {
281       if (*count == 1) {
282         std::string str;
283 
284         if (vi >= v.length())
285           return false;
286 
287         if ((i == 0) && (v[vi] == '-' || v[vi] == '+')) {
288           str += v[vi++];
289 
290           if (vi >= v.length())
291             return false;
292         }
293 
294         str += v[vi++];
295 
296         for (int j = 0; j < maxCount - 1; ++j)
297           if (vi < v.length())
298             if ('0' <= v[vi] && v[vi] <= '9')
299               str += v[vi++];
300 
301         try {
302           *value = Utils::stoi(str);
303         } catch (std::exception&) {
304           return false;
305         }
306       } else if (*count == maxCount) {
307         if (vi + (maxCount - 1) >= v.length())
308           return false;
309 
310         std::string str = v.substr(vi, maxCount);
311         vi += maxCount;
312 
313         try {
314           *value = Utils::stoi(str);
315         } catch (std::exception&) {
316           return false;
317         }
318       } else
319         fatalFormatError(format, *count, letter[i]);
320     }
321 
322     *count = 0;
323   }
324 
325   if (parse.a) {
326     if (vi + 1 >= v.length())
327       return false;
328 
329     std::string str = v.substr(vi, 2);
330     vi += 2;
331     parse.haveAMPM = true;
332 
333     if (str == "am" || str == "AM")
334       parse.pm = false;
335     else if (str == "pm" || str == "PM")
336       parse.pm = true;
337     else
338       return false;
339 
340     parse.a = 0;
341   }
342 
343   return true;
344 }
345 
toString()346 WString WTime::toString() const
347 {
348   return WTime::toString(defaultFormat());
349 }
350 
toString(const WString & format)351 WString WTime::toString(const WString& format) const
352 {
353   return WDateTime::toString(nullptr, this, format, true, 0);
354 }
355 
toTimeDuration()356 std::chrono::duration<int, std::milli> WTime::toTimeDuration() const
357 {
358   if(isValid())
359       return std::chrono::duration<int, std::milli>(std::chrono::hours(hour()) + std::chrono::minutes(minute()) + std::chrono::seconds(second())
360               + std::chrono::milliseconds(msec()));
361   return std::chrono::duration<int, std::milli>(0);
362 }
363 
fromTimeDuration(const std::chrono::duration<int,std::milli> & duration)364 WTime WTime::fromTimeDuration(const std::chrono::duration<int, std::milli>& duration)
365 {
366     auto time = date::make_time(duration);
367     std::chrono::duration<int, std::milli> ms = std::chrono::duration_cast<std::chrono::milliseconds>(time.subseconds());
368     return WTime(time.hours().count(), time.minutes().count(), time.seconds().count(), ms.count());
369 }
370 
pmhour()371 int WTime::pmhour() const
372 {
373   int result = hour() % 12;
374   return result != 0 ? result : 12;
375 }
376 
writeSpecial(const std::string & f,unsigned & i,WStringStream & result,bool useAMPM,int zoneOffset)377 bool WTime::writeSpecial(const std::string& f, unsigned& i,
378                          WStringStream& result, bool useAMPM,
379                          int zoneOffset) const
380 {
381   char buf[30];
382 
383   switch (f[i]) {
384     case '+':
385       if (f[i + 1] == 'h' || f[i + 1] == 'H') {
386         result << ((hour() >= 0) ? '+' : '-');
387         return true;
388       }
389 
390       return false;
391 
392     case 'h':
393       if (f[i + 1] == 'h') {
394         ++i;
395         result << Utils::pad_itoa(std::abs(useAMPM ? pmhour() : hour()), 2, buf);
396       } else
397         result << Utils::itoa(std::abs(useAMPM ? pmhour() : hour()), buf);
398 
399       return true;
400 
401     case 'H':
402       if (f[i + 1] == 'H') {
403         ++i;
404         result << Utils::pad_itoa(std::abs(hour()), 2, buf);
405       } else
406         result << Utils::itoa(std::abs(hour()), buf);
407 
408       return true;
409 
410     case 'm':
411       if (f[i + 1] == 'm') {
412         ++i;
413         result << Utils::pad_itoa(minute(), 2, buf);
414       } else
415         result << Utils::itoa(minute(), buf);
416 
417       return true;
418 
419     case 's':
420       if (f[i + 1] == 's') {
421         ++i;
422         result << Utils::pad_itoa(second(), 2, buf);
423       } else
424         result << Utils::itoa(second(), buf);
425 
426       return true;
427 
428     case 'Z': {
429         bool negate = zoneOffset < 0;
430 
431         if (!negate)
432           result << '+';
433         else {
434           result << '-';
435           zoneOffset = -zoneOffset;
436         }
437 
438         int hours = zoneOffset / 60;
439         int minutes = zoneOffset % 60;
440         result << Utils::pad_itoa(hours, 2, buf);
441         result << Utils::pad_itoa(minutes, 2, buf);
442         return true;
443       }
444 
445     case 'z':
446       if (f.substr(i + 1, 2) == "zz") {
447         i += 2;
448         result << Utils::pad_itoa(msec(), 3, buf);
449       } else
450         result << Utils::itoa(msec(), buf);
451 
452       return true;
453 
454     case 'a':
455     case 'A':
456       if (hour() < 12)
457         result << ((f[i] == 'a') ? "am" : "AM");
458       else
459         result << ((f[i] == 'a') ? "pm" : "PM");
460 
461       if (f[i + 1] == 'p' || f[i + 1] == 'P')
462         ++i;
463 
464       return true;
465 
466     default:
467       return false;
468   }
469 }
470 
WTime(long t)471 WTime::WTime(long t)
472   : valid_(true),
473     null_(false),
474     time_(t)
475 { }
476 
formatHourToRegExp(WTime::RegExpInfo & result,const std::string & format,unsigned & i,int & currentGroup)477 WTime::RegExpInfo WTime::formatHourToRegExp(WTime::RegExpInfo& result,
478 					    const std::string& format,
479                         unsigned& i, int& currentGroup)
480 {
481   /* Possible values */
482   /* h, hh, H, HH */
483   char next = -1;
484 
485   bool ap = (format.find("AP") != std::string::npos)
486     || (format.find("ap") != std::string::npos); //AM-PM
487   std::string sf;
488   sf += format[i];
489 
490   if (i < format.size() - 1)
491     next = format[i + 1];
492 
493   if (next == 'h' || next == 'H') {
494     sf+= next;
495     i++;
496   } else{
497     next = -1;
498     sf = format[i];
499   }
500 
501   if (sf == "HH" || (sf == "hh" && !ap)) { //Hour with leading 0 0-23
502     result.regexp += "([0-1][0-9]|[2][0-3])";
503   } else if (sf == "hh" && ap) {          //Hour with leading 0 01-12
504     result.regexp += "(0[1-9]|[1][012])";
505   } else if (sf == "H" || (sf == "h" && !ap)) { //Hour without leading 0 0-23
506 	result.regexp += "(0|[1-9]|[1][0-9]|2[0-3])";
507   } else if (sf == "h" && ap) {                  //Hour without leading 0 0-12
508     result.regexp += "([1-9]|1[012])";
509   }
510   result.hourGetJS = "return parseInt(results[" +
511     std::to_string(currentGroup++) + "], 10);";
512   return result;
513 }
514 
formatMinuteToRegExp(WTime::RegExpInfo & result,const std::string & format,unsigned & i,int & currentGroup)515 WTime::RegExpInfo WTime::formatMinuteToRegExp(WTime::RegExpInfo& result,
516 					      const std::string& format,
517                           unsigned& i, int& currentGroup)
518 {
519   char next = -1;
520   std::string sf;
521   if (i < format.size() - 1) next = format[i + 1];
522 
523   if (next == 'm') {
524     sf = "mm";
525     i++;
526   } else {
527     sf = "m";
528     next = -1;
529   }
530 
531   if (sf == "m") /* Minutes without leading 0 */
532     result.regexp += "(0|[1-5]?[0-9])";
533   else /* Minutes with leading 0 */
534     result.regexp += "([0-5][0-9])";
535 
536   result.minuteGetJS = "return parseInt(results["
537           + std::to_string(currentGroup++) + "], 10);";
538 
539   return result;
540 }
541 
formatSecondToRegExp(WTime::RegExpInfo & result,const std::string & format,unsigned & i,int & currentGroup)542 WTime::RegExpInfo WTime::formatSecondToRegExp(WTime::RegExpInfo& result,
543 					      const std::string& format,
544                           unsigned& i, int& currentGroup)
545 {
546   char next = -1;
547   std::string sf;
548 
549   if (i < format.size() - 1) next = format[i + 1];
550 
551   if (next == 's') {
552     sf = "ss";
553     i++;
554   } else {
555     sf = "s";
556     next = -1;
557   }
558 
559   if (sf == "s") /* Seconds without leading 0 */
560     result.regexp += "(0|[1-5]?[0-9])";
561   else /* Seconds with leading 0 */
562     result.regexp += "([0-5][0-9])";
563 
564   result.secGetJS = "return parseInt(results["
565           + std::to_string(currentGroup++) + "], 10);";
566 
567   return result;
568 }
569 
formatMSecondToRegExp(WTime::RegExpInfo & result,const std::string & format,unsigned & i,int & currentGroup)570 WTime::RegExpInfo WTime::formatMSecondToRegExp(WTime::RegExpInfo& result,
571 					       const std::string& format,
572                            unsigned& i, int& currentGroup)
573 {
574   char next = -1;
575   std::string sf;
576   sf += format[i];
577 
578   for (int k = 0; k < 2; ++k) {
579     if (i < format.size() - 1) next = format[i + 1];
580 
581     if (next == 'z'){
582       sf += "z";
583       next = -1;
584       i++;
585     } else {
586       next = -1;
587       break;
588     }
589   }
590 
591   if (sf == "z") /* The Ms without trailing 0 */
592     result.regexp += "(0|[1-9][0-9]{0,2})";
593   else if (sf == "zzz")
594     result.regexp += "([0-9]{3})";
595 
596   result.msecGetJS = "return parseInt(results["
597           + std::to_string(currentGroup++) + "], 10);";
598 
599   return result;
600 }
601 
formatAPToRegExp(WTime::RegExpInfo & result,const std::string & format,unsigned & i)602 WTime::RegExpInfo WTime::formatAPToRegExp(WTime::RegExpInfo& result,
603 					       const std::string& format,
604 					       unsigned& i)
605 {
606   if(i < format.size() - 1) {
607 	if(format[i] ==  'A' && format[i+1] == 'P') {
608 	  result.regexp += "([AP]M)";
609 	  i++;
610 	}
611 	else if(format[i]  == 'a' && format[i+1] == 'p'){
612 	  result.regexp += "([ap]m)";
613 	  i++;
614 	}
615   } else
616 	result.regexp += format[i];
617 
618   return result;
619 }
620 
processChar(WTime::RegExpInfo & result,const std::string & format,unsigned & i)621 WTime::RegExpInfo WTime::processChar(WTime::RegExpInfo &result, const std::string& format, unsigned& i)
622 {
623   switch(format[i])
624   {
625 	case '.':
626 	case '+':
627 	case '$':
628 	case '^':
629 	case '*':
630 	case '[':
631 	case ']':
632 	case '{':
633 	case '}':
634 	case '(':
635 	case ')':
636 	case '?':
637 	case '!':
638 	  result.regexp += "\\";
639 	  break;
640   }
641   result.regexp += format[i];
642   return result;
643 }
644 
formatToRegExp(const WT_USTRING & format)645 WTime::RegExpInfo WTime::formatToRegExp(const WT_USTRING& format)
646 {
647   RegExpInfo result;
648   std::string f = format.toUTF8();
649   int currentGroup = 1;
650 
651   result.hourGetJS = "return 1";
652   result.minuteGetJS = "return 1";
653   result.secGetJS = "return 1";
654   result.msecGetJS = "return 1";
655 
656   //result.regexp = "^";
657   bool inQuote = false;
658 
659   for (unsigned i = 0; i < f.size(); ++i) {
660     if (inQuote && f[i] != '\'') {
661 	  processChar(result, f, i);
662       continue;
663     }
664 
665     switch (f[i]) {
666     case '\'':
667       if (i < f.size() - 2 && f[i+1] == f[i+2] && f[i+1] == '\'')
668         result.regexp += f[i];
669       else
670         inQuote = !inQuote;
671     case 'h':
672     case 'H':
673       formatHourToRegExp(result, f, i, currentGroup);
674       break;
675     case 'm':
676       formatMinuteToRegExp(result,f, i, currentGroup);
677       break;
678     case 's':
679       formatSecondToRegExp(result, f, i, currentGroup);
680       break;
681     case 'z':
682       formatMSecondToRegExp(result, f, i, currentGroup);
683       break;
684     case 'Z':
685       result.regexp+="(\\+[0-9]{4})";
686       break;
687     case 'A':
688     case 'a':
689 	  formatAPToRegExp(result, f, i);
690 	  break;
691     case '+':
692       if (i < f.size() - 1 && (f[i+1] == 'h' || f[i+1] == 'H'))
693         result.regexp += "\\+";
694       break;
695     default:
696 	  processChar(result, f, i);
697 	  break;
698     }
699 
700   }
701 
702   //result.regexp += "$";
703 
704   return result;
705 }
706 
usesAmPm(const WString & format)707 bool WTime::usesAmPm(const WString& format)
708 {
709   std::string f = format.toUTF8() + std::string(3, 0);
710 
711   bool inQuote = false;
712   bool gotQuoteInQuote = false;
713   bool useAMPM = false;
714 
715   for (unsigned i = 0; i < f.length() - 3; ++i) {
716     if (inQuote) {
717       if (f[i] != '\'') {
718 	if (gotQuoteInQuote) {
719 	  gotQuoteInQuote = false;
720 	  inQuote = false;
721 	}
722       } else {
723 	if (gotQuoteInQuote)
724 	  gotQuoteInQuote = false;
725 	else
726 	  gotQuoteInQuote = true;
727       }
728     }
729 
730     if (!inQuote) {
731       if (f[i] == 'a' || f[i] == 'A') {
732 	useAMPM = true;
733 	break;
734       } else if (f[i] == '\'') {
735 	inQuote = true;
736 	gotQuoteInQuote = false;
737       }
738     }
739   }
740 
741   return useAMPM;
742 }
743 
744 }
745