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