1 #include "libfilezilla/time.hpp"
2
3 #include "libfilezilla/format.hpp"
4
5 #ifndef FZ_WINDOWS
6 #include <errno.h>
7 #include <sys/time.h>
8 #endif
9
10 #include <wchar.h>
11
12 //#include <cassert>
13 #define TIME_ASSERT(x) //assert(x)
14
15 namespace fz {
16
datetime(zone z,int year,int month,int day,int hour,int minute,int second,int millisecond)17 datetime::datetime(zone z, int year, int month, int day, int hour, int minute, int second, int millisecond)
18 {
19 set(z, year, month, day, hour, minute, second, millisecond);
20 }
21
datetime(time_t t,accuracy a)22 datetime::datetime(time_t t, accuracy a)
23 : t_(static_cast<int64_t>(t) * 1000)
24 , a_(a)
25 {
26 TIME_ASSERT(clamped());
27 TIME_ASSERT(a != milliseconds);
28 }
29
30 namespace {
31 template<typename C>
skip(C const * & it,C const * const end)32 void skip(C const*& it, C const* const end)
33 {
34 while (it != end && (*it < '0' || *it > '9')) {
35 ++it;
36 }
37 }
38
39 template<typename T, typename C>
parse(C const * & it,C const * end,int count,T & v,int offset)40 bool parse(C const*& it, C const* end, int count, T & v, int offset)
41 {
42 skip(it, end);
43
44 if (end - it < count) {
45 return false;
46 }
47
48 T w = 0;
49
50 C const* const stop = it + count;
51 while (it != stop) {
52 if (*it < '0' || *it > '9') {
53 return false;
54 }
55 w *= 10;
56 w += *it - '0';
57 ++it;
58 }
59
60 w += offset;
61
62 v = w;
63 return true;
64 }
65
66 template<typename String>
do_set(datetime & dt,String const & str,datetime::zone z)67 bool do_set(datetime& dt, String const& str, datetime::zone z)
68 {
69 if (str.empty()) {
70 dt.clear();
71 return false;
72 }
73
74 auto const* it = str.data();
75 auto const* end = it + str.size();
76
77 #ifdef FZ_WINDOWS
78 SYSTEMTIME st{};
79 if (!parse(it, end, 4, st.wYear, 0) ||
80 !parse(it, end, 2, st.wMonth, 0) ||
81 !parse(it, end, 2, st.wDay, 0))
82 {
83 dt.clear();
84 return false;
85 }
86
87 datetime::accuracy a = datetime::days;
88 if (parse(it, end, 2, st.wHour, 0)) {
89 a = datetime::hours;
90 if (parse(it, end, 2, st.wMinute, 0)) {
91 a = datetime::minutes;
92 if (parse(it, end, 2, st.wSecond, 0)) {
93 a = datetime::seconds;
94 if (parse(it, end, 3, st.wMilliseconds, 0)) {
95 a = datetime::milliseconds;
96 }
97 }
98 }
99 }
100 return dt.set(st, a, z);
101 #else
102 tm t{};
103 if (!parse(it, end, 4, t.tm_year, -1900) ||
104 !parse(it, end, 2, t.tm_mon, -1) ||
105 !parse(it, end, 2, t.tm_mday, 0))
106 {
107 dt.clear();
108 return false;
109 }
110
111 datetime::accuracy a = datetime::days;
112 int64_t ms{};
113 if (parse(it, end, 2, t.tm_hour, 0)) {
114 a = datetime::hours;
115 if (parse(it, end, 2, t.tm_min, 0)) {
116 a = datetime::minutes;
117 if (parse(it, end, 2, t.tm_sec, 0)) {
118 a = datetime::seconds;
119 if (parse(it, end, 3, ms, 0)) {
120 a = datetime::milliseconds;
121 }
122 }
123 }
124 }
125 bool success = dt.set(t, a, z);
126 if (success) {
127 dt += duration::from_milliseconds(ms);
128 }
129 return success;
130 #endif
131 }
132 }
133
datetime(std::string_view const & str,zone z)134 datetime::datetime(std::string_view const& str, zone z)
135 {
136 do_set(*this, str, z);
137 }
138
datetime(std::wstring_view const & str,zone z)139 datetime::datetime(std::wstring_view const& str, zone z)
140 {
141 do_set(*this, str, z);
142 }
143
now()144 datetime datetime::now()
145 {
146 #ifdef FZ_WINDOWS
147 FILETIME ft{};
148 GetSystemTimeAsFileTime(&ft);
149 return datetime(ft, milliseconds);
150 #else
151 datetime ret;
152 timeval tv = { 0, 0 };
153 if (gettimeofday(&tv, nullptr) == 0) {
154 ret.t_ = static_cast<int64_t>(tv.tv_sec) * 1000 + tv.tv_usec / 1000;
155 ret.a_ = milliseconds;
156 }
157 return ret;
158 #endif
159 }
160
operator <(datetime const & op) const161 bool datetime::operator<(datetime const& op) const
162 {
163 if (t_ == invalid) {
164 return op.t_ != invalid;
165 }
166 else if (op.t_ == invalid) {
167 return false;
168 }
169
170 if (t_ < op.t_) {
171 return true;
172 }
173 if (t_ > op.t_) {
174 return false;
175 }
176
177 return a_ < op.a_;
178 }
179
operator <=(datetime const & op) const180 bool datetime::operator<=(datetime const& op) const
181 {
182 if (t_ == invalid) {
183 return true;
184 }
185 else if (op.t_ == invalid) {
186 return false;
187 }
188
189 if (t_ < op.t_) {
190 return true;
191 }
192 if (t_ > op.t_) {
193 return false;
194 }
195
196 return a_ <= op.a_;
197 }
198
operator ==(datetime const & op) const199 bool datetime::operator==(datetime const& op) const
200 {
201 return t_ == op.t_ && a_ == op.a_;
202 }
203
clamped()204 bool datetime::clamped()
205 {
206 bool ret = true;
207 tm t = get_tm(utc);
208 if (a_ < milliseconds && get_milliseconds() != 0) {
209 ret = false;
210 }
211 else if (a_ < seconds && t.tm_sec) {
212 ret = false;
213 }
214 else if (a_ < minutes && t.tm_min) {
215 ret = false;
216 }
217 else if (a_ < hours && t.tm_hour) {
218 ret = false;
219 }
220 return ret;
221 }
222
compare(datetime const & op) const223 int datetime::compare(datetime const& op) const
224 {
225 if (t_ == invalid) {
226 return (op.t_ == invalid) ? 0 : -1;
227 }
228 else if (op.t_ == invalid) {
229 return 1;
230 }
231
232 if (a_ == op.a_) {
233 // First fast path: Same accuracy
234 int ret = 0;
235 if (t_ < op.t_) {
236 ret = -1;
237 }
238 else if (t_ > op.t_) {
239 ret = 1;
240 }
241 TIME_ASSERT(compare_slow(op) == ret);
242 return ret;
243 }
244
245 // Second fast path: Lots of difference, at least 2 days
246 int64_t diff = t_ - op.t_;
247 if (diff > 60 * 60 * 24 * 1000 * 2) {
248 TIME_ASSERT(compare_slow(op) == 1);
249 return 1;
250 }
251 else if (diff < -60 * 60 * 24 * 1000 * 2) {
252 TIME_ASSERT(compare_slow(op) == -1);
253 return -1;
254 }
255
256 return compare_slow(op);
257 }
258
compare_slow(datetime const & op) const259 int datetime::compare_slow(datetime const& op) const
260 {
261 tm const t1 = get_tm(utc);
262 tm const t2 = op.get_tm(utc);
263 if (t1.tm_year < t2.tm_year) {
264 return -1;
265 }
266 else if (t1.tm_year > t2.tm_year) {
267 return 1;
268 }
269 if (t1.tm_mon < t2.tm_mon) {
270 return -1;
271 }
272 else if (t1.tm_mon > t2.tm_mon) {
273 return 1;
274 }
275 if (t1.tm_mday < t2.tm_mday) {
276 return -1;
277 }
278 else if (t1.tm_mday > t2.tm_mday) {
279 return 1;
280 }
281
282 accuracy a = (a_ < op.a_) ? a_ : op.a_;
283
284 if (a < hours) {
285 return 0;
286 }
287 if (t1.tm_hour < t2.tm_hour) {
288 return -1;
289 }
290 else if (t1.tm_hour > t2.tm_hour) {
291 return 1;
292 }
293
294 if (a < minutes) {
295 return 0;
296 }
297 if (t1.tm_min < t2.tm_min) {
298 return -1;
299 }
300 else if (t1.tm_min > t2.tm_min) {
301 return 1;
302 }
303
304 if (a < seconds) {
305 return 0;
306 }
307 if (t1.tm_sec < t2.tm_sec) {
308 return -1;
309 }
310 else if (t1.tm_sec > t2.tm_sec) {
311 return 1;
312 }
313
314 if (a < milliseconds) {
315 return 0;
316 }
317 auto ms1 = get_milliseconds();
318 auto ms2 = op.get_milliseconds();
319 if (ms1 < ms2) {
320 return -1;
321 }
322 else if (ms1 > ms2) {
323 return 1;
324 }
325
326 return 0;
327 }
328
operator +=(duration const & op)329 datetime& datetime::operator+=(duration const& op)
330 {
331 if (!empty()) {
332 if (a_ < hours) {
333 t_ += op.get_days() * 24 * 3600 * 1000;
334 }
335 else if (a_ < minutes) {
336 t_ += op.get_hours() * 3600 * 1000;
337 }
338 else if (a_ < seconds) {
339 t_ += op.get_minutes() * 60 * 1000;
340 }
341 else if (a_ < milliseconds) {
342 t_ += op.get_seconds() * 1000;
343 }
344 else {
345 t_ += op.get_milliseconds();
346 }
347 }
348 return *this;
349 }
350
operator -=(duration const & op)351 datetime& datetime::operator-=(duration const& op)
352 {
353 *this += -op;
354 return *this;
355 }
356
set(zone z,int year,int month,int day,int hour,int minute,int second,int millisecond)357 bool datetime::set(zone z, int year, int month, int day, int hour, int minute, int second, int millisecond)
358 {
359 accuracy a;
360 if (hour == -1) {
361 a = days;
362 TIME_ASSERT(minute == -1);
363 TIME_ASSERT(second == -1);
364 TIME_ASSERT(millisecond == -1);
365 hour = minute = second = millisecond = 0;
366 }
367 else if (minute == -1) {
368 a = hours;
369 TIME_ASSERT(second == -1);
370 TIME_ASSERT(millisecond == -1);
371 minute = second = millisecond = 0;
372 }
373 else if (second == -1) {
374 a = minutes;
375 TIME_ASSERT(millisecond == -1);
376 second = millisecond = 0;
377 }
378 else if (millisecond == -1) {
379 a = seconds;
380 millisecond = 0;
381 }
382 else {
383 a = milliseconds;
384 }
385
386 #ifdef FZ_WINDOWS
387 SYSTEMTIME st{};
388 st.wYear = year;
389 st.wMonth = month;
390 st.wDay = day;
391 st.wHour = hour;
392 st.wMinute = minute;
393 st.wSecond = second;
394 st.wMilliseconds = millisecond;
395
396 return set(st, a, z);
397 #else
398
399 tm t{};
400 t.tm_isdst = -1;
401 t.tm_year = year - 1900;
402 t.tm_mon = month - 1;
403 t.tm_mday = day;
404 t.tm_hour = hour;
405 t.tm_min = minute;
406 t.tm_sec = second;
407
408 bool const success = set(t, a, z);
409
410 if (success) {
411 t_ += millisecond;
412 }
413
414 return success;
415 #endif
416 }
417
set(std::string_view const & str,zone z)418 bool datetime::set(std::string_view const& str, zone z)
419 {
420 return do_set(*this, str, z);
421 }
422
set(std::wstring_view const & str,zone z)423 bool datetime::set(std::wstring_view const& str, zone z)
424 {
425 return do_set(*this, str, z);
426 }
427
428 #ifdef FZ_WINDOWS
429
430 namespace {
do_set(datetime & dt,SYSTEMTIME const & st,datetime::accuracy a,datetime::zone z)431 bool do_set(datetime & dt, SYSTEMTIME const& st, datetime::accuracy a, datetime::zone z)
432 {
433 FILETIME ft{};
434 if (a >= datetime::hours && z == datetime::local) {
435 SYSTEMTIME st2{};
436 if (!TzSpecificLocalTimeToSystemTime(nullptr, &st, &st2)) {
437 return false;
438 }
439 if (!SystemTimeToFileTime(&st2, &ft)) {
440 return false;
441 }
442 }
443 else if (!SystemTimeToFileTime(&st, &ft)) {
444 return false;
445 }
446 return dt.set(ft, a);
447 }
448 }
449
set(SYSTEMTIME const & st,accuracy a,zone z)450 bool datetime::set(SYSTEMTIME const& st, accuracy a, zone z)
451 {
452 clear();
453
454 bool success = do_set(*this, st, a, z);
455 if (!success) {
456 // Check for alternate midnight format
457 if (st.wHour == 24 && !st.wMinute && !st.wSecond && !st.wMilliseconds) {
458 SYSTEMTIME st2 = st;
459 st2.wHour = 23;
460 st2.wMinute = 59;
461 st2.wSecond = 59;
462 st2.wMilliseconds = 999;
463 success = do_set(*this, st2, a, z);
464 if (success) {
465 t_ += 1;
466 }
467 }
468 }
469 return success;
470 }
471
472 namespace {
473 template<typename T>
make_int64_t(T hi,T lo)474 int64_t make_int64_t(T hi, T lo)
475 {
476 return (static_cast<int64_t>(hi) << 32) + static_cast<int64_t>(lo);
477 }
478
479 // This is the offset between FILETIME epoch in 100ns and the Unix epoch in ms.
480 int64_t const EPOCH_OFFSET_IN_MSEC = 11644473600000ll;
481 }
482
set(FILETIME const & ft,accuracy a)483 bool datetime::set(FILETIME const& ft, accuracy a)
484 {
485 if (ft.dwHighDateTime || ft.dwLowDateTime) {
486 // See http://trac.wxwidgets.org/changeset/74423 and http://trac.wxwidgets.org/ticket/13098
487 // Directly converting to time_t
488
489 int64_t t = make_int64_t(ft.dwHighDateTime, ft.dwLowDateTime);
490 t /= 10000; // Convert hundreds of nanoseconds to milliseconds.
491 t -= EPOCH_OFFSET_IN_MSEC;
492 if (t != invalid) {
493 t_ = t;
494 a_ = a;
495 TIME_ASSERT(clamped());
496 return true;
497 }
498 }
499 clear();
500 return false;
501 }
502
503 #else
504
set(tm & t,accuracy a,zone z)505 bool datetime::set(tm& t, accuracy a, zone z)
506 {
507 time_t tt;
508
509 errno = 0;
510
511 if (a >= hours && z == local) {
512 tt = mktime(&t);
513 }
514 else {
515 tt = timegm(&t);
516 }
517
518 if (tt != time_t(-1) || !errno) {
519 t_ = static_cast<int64_t>(tt) * 1000;
520 a_ = a;
521
522 TIME_ASSERT(clamped());
523
524 return true;
525 }
526
527 clear();
528 return false;
529 }
530
531 #endif
532
imbue_time(int hour,int minute,int second,int millisecond)533 bool datetime::imbue_time(int hour, int minute, int second, int millisecond)
534 {
535 if (empty() || a_ > days) {
536 return false;
537 }
538
539 if (second == -1) {
540 a_ = minutes;
541 TIME_ASSERT(millisecond == -1);
542 second = millisecond = 0;
543 }
544 else if (millisecond == -1) {
545 a_ = seconds;
546 millisecond = 0;
547 }
548 else {
549 a_ = milliseconds;
550 }
551
552 if (hour < 0 || hour >= 24) {
553 // Allow alternate midnight representation
554 if (hour != 24 || minute != 0 || second != 0 || millisecond != 0) {
555 return false;
556 }
557 }
558 if (minute < 0 || minute >= 60) {
559 return false;
560 }
561 if (second < 0 || second >= 60) {
562 return false;
563 }
564 if (millisecond < 0 || millisecond >= 1000) {
565 return false;
566 }
567
568 t_ += (hour * 3600 + minute * 60 + second) * 1000 + millisecond;
569 return true;
570 }
571
empty() const572 bool datetime::empty() const
573 {
574 return t_ == invalid;
575 }
576
clear()577 void datetime::clear()
578 {
579 a_ = days;
580 t_ = invalid;
581 }
582
583 #ifdef __VISUALC__
584
585 #include <mutex.h>
586
587 namespace {
588
589 // Sadly wcsftime has shitty error handling, instead of returning 0 and setting errrno, it invokes some crt debug machinary.
590 // Fortunately we don't build the official FZ binaries with Visual Studio.
NullInvalidParameterHandler(const wchar_t *,const wchar_t *,const wchar_t *,unsigned int,uintptr_t)591 extern "C" void NullInvalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, uintptr_t)
592 {
593 }
594
595 struct CrtAssertSuppressor
596 {
CrtAssertSuppressorfz::__anon40529c1b0411::CrtAssertSuppressor597 CrtAssertSuppressor()
598 {
599 scoped_lock l(m_);
600
601 if (!refs_++) {
602 oldError = _CrtSetReportMode(_CRT_ERROR, 0);
603 oldAssert = _CrtSetReportMode(_CRT_ASSERT, 0);
604 oldHandler = _set_invalid_parameter_handler(NullInvalidParameterHandler);
605 }
606 }
607
~CrtAssertSuppressorfz::__anon40529c1b0411::CrtAssertSuppressor608 ~CrtAssertSuppressor()
609 {
610 scoped_lock l(m_);
611
612 if (!--refs_) {
613 _set_invalid_parameter_handler(oldHandler);
614 _CrtSetReportMode(_CRT_ASSERT, oldAssert);
615 _CrtSetReportMode(_CRT_ERROR, oldError);
616 }
617 }
618
619 static int oldError;
620 static int oldAssert;
621 static _invalid_parameter_handler oldHandler;
622
623 static mutex m_;
624 static int refs_;
625 };
626
627 int CrtAssertSuppressor::oldError{};
628 int CrtAssertSuppressor::oldAssert{};
629 _invalid_parameter_handler CrtAssertSuppressor::oldHandler{};
630
631 mutex CrtAssertSuppressor::m_{};
632 int CrtAssertSuppressor::refs_{};
633
634 }
635 #endif
636
verify_format(std::string const & fmt)637 bool datetime::verify_format(std::string const& fmt)
638 {
639 tm const t = datetime::now().get_tm(utc);
640 char buf[4096];
641
642 #ifdef __VISUALC__
643 CrtAssertSuppressor suppressor;
644 #endif
645
646 return strftime(buf, sizeof(buf) / sizeof(char), fmt.c_str(), &t) != 0;
647 }
648
verify_format(std::wstring const & fmt)649 bool datetime::verify_format(std::wstring const& fmt)
650 {
651 tm const t = datetime::now().get_tm(utc);
652 wchar_t buf[4096];
653
654 #ifdef __VISUALC__
655 CrtAssertSuppressor suppressor;
656 #endif
657
658 return wcsftime(buf, sizeof(buf) / sizeof(wchar_t), fmt.c_str(), &t) != 0;
659 }
660
operator -(datetime const & a,datetime const & b)661 duration operator-(datetime const& a, datetime const& b)
662 {
663 TIME_ASSERT(a.IsValid());
664 TIME_ASSERT(b.IsValid());
665
666 return duration::from_milliseconds(a.t_ - b.t_);
667 }
668
format(std::string const & fmt,zone z) const669 std::string datetime::format(std::string const& fmt, zone z) const
670 {
671 tm t = get_tm(z);
672
673 int const count = 1000;
674 char buf[count];
675
676 #ifdef __VISUALC__
677 CrtAssertSuppressor suppressor;
678 #endif
679 strftime(buf, count - 1, fmt.c_str(), &t);
680 buf[count - 1] = 0;
681
682 return buf;
683 }
684
format(std::wstring const & fmt,zone z) const685 std::wstring datetime::format(std::wstring const& fmt, zone z) const
686 {
687 tm t = get_tm(z);
688
689 int const count = 1000;
690 wchar_t buf[count];
691
692 #ifdef __VISUALC__
693 CrtAssertSuppressor suppressor;
694 #endif
695 wcsftime(buf, count - 1, fmt.c_str(), &t);
696 buf[count - 1] = 0;
697
698 return buf;
699 }
700
get_time_t() const701 time_t datetime::get_time_t() const
702 {
703 return t_ / 1000;
704 }
705
get_tm(zone z) const706 tm datetime::get_tm(zone z) const
707 {
708 tm ret{};
709 time_t t = get_time_t();
710 #ifdef FZ_WINDOWS
711 // gmtime_s/localtime_s don't work with negative times
712 if (t < 86400) {
713 FILETIME ft = get_filetime();
714 SYSTEMTIME st;
715 if (FileTimeToSystemTime(&ft, &st)) {
716
717 if (a_ >= hours && z == local) {
718 SYSTEMTIME st2;
719 if (SystemTimeToTzSpecificLocalTime(nullptr, &st, &st2)) {
720 st = st2;
721 }
722 }
723
724 ret.tm_year = st.wYear - 1900;
725 ret.tm_mon = st.wMonth - 1;
726 ret.tm_mday = st.wDay;
727 ret.tm_wday = st.wDayOfWeek;
728 ret.tm_hour = st.wHour;
729 ret.tm_min = st.wMinute;
730 ret.tm_sec = st.wSecond;
731 ret.tm_yday = -1;
732 }
733 }
734 else {
735 // Special case: If having only days, don't perform conversion
736 if (z == utc || a_ == days) {
737 gmtime_s(&ret, &t);
738 }
739 else {
740 localtime_s(&ret, &t);
741 }
742 }
743 #else
744 if (z == utc || a_ == days) {
745 gmtime_r(&t, &ret);
746 }
747 else {
748 localtime_r(&t, &ret);
749 }
750 #endif
751 return ret;
752 }
753
754 #ifdef FZ_WINDOWS
755
datetime(FILETIME const & ft,accuracy a)756 datetime::datetime(FILETIME const& ft, accuracy a)
757 {
758 set(ft, a);
759 }
760
get_filetime() const761 FILETIME datetime::get_filetime() const
762 {
763 FILETIME ret{};
764 if (!empty()) {
765 int64_t t = t_;
766
767 t += EPOCH_OFFSET_IN_MSEC;
768 t *= 10000;
769
770 ret.dwHighDateTime = t >> 32;
771 ret.dwLowDateTime = t & 0xffffffffll;
772 }
773
774 return ret;
775 }
776
777 #endif
778
get_rfc822() const779 std::string datetime::get_rfc822() const
780 {
781 if (empty()) {
782 return std::string();
783 }
784 tm const t = get_tm(datetime::zone::utc);
785 if (t.tm_wday < 0 || t.tm_wday > 6 || t.tm_mon < 0 || t.tm_mon > 11) {
786 return std::string();
787 }
788
789 static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
790 static const char* wdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
791
792 return sprintf("%s, %02d %s %d %02d:%02d:%02d GMT", wdays[t.tm_wday], t.tm_mday, months[t.tm_mon], t.tm_year + 1900,
793 t.tm_hour, t.tm_min, t.tm_sec);
794 }
795
796 using namespace std::literals;
797
798 namespace {
799 template<typename String>
do_set_rfc822(datetime & dt,String const & str)800 bool do_set_rfc822(datetime& dt, String const& str)
801 {
802 auto const tokens = strtok_view(str, fzS(typename String::value_type, ", :-"));
803 if (tokens.size() >= 7) {
804 auto getMonth = [](auto const& m) {
805 if (m == fzS(typename String::value_type, "Jan")) return 1;
806 if (m == fzS(typename String::value_type, "Feb")) return 2;
807 if (m == fzS(typename String::value_type, "Mar")) return 3;
808 if (m == fzS(typename String::value_type, "Apr")) return 4;
809 if (m == fzS(typename String::value_type, "May")) return 5;
810 if (m == fzS(typename String::value_type, "Jun")) return 6;
811 if (m == fzS(typename String::value_type, "Jul")) return 7;
812 if (m == fzS(typename String::value_type, "Aug")) return 8;
813 if (m == fzS(typename String::value_type, "Sep")) return 9;
814 if (m == fzS(typename String::value_type, "Oct")) return 10;
815 if (m == fzS(typename String::value_type, "Nov")) return 11;
816 if (m == fzS(typename String::value_type, "Dec")) return 12;
817 return 0;
818 };
819
820 int day = to_integral<int>(tokens[1]);
821 int month;
822 if (!day) {
823 day = to_integral<int>(tokens[2]);
824 month = getMonth(tokens[1]);
825 }
826 else {
827 month = getMonth(tokens[2]);
828 }
829
830 int year = to_integral<int>(tokens[6]);
831 int hour;
832 int minute;
833 int second;
834 if (year < 1000) {
835 year = to_integral<int>(tokens[3]);
836 if (year < 1000) {
837 year += 1900;
838 }
839 hour = to_integral<int>(tokens[4]);
840 minute = to_integral<int>(tokens[5]);
841 second = to_integral<int>(tokens[6]);
842 }
843 else {
844 hour = to_integral<int>(tokens[3]);
845 minute = to_integral<int>(tokens[4]);
846 second = to_integral<int>(tokens[5]);
847 }
848
849 bool set = dt.set(datetime::utc, year, month, day, hour, minute, second);
850 if (set && tokens.size() >= 8) {
851 int minutes{};
852 if (tokens[7].size() == 5 && tokens[7][0] == '+') {
853 minutes = -fz::to_integral<int>(tokens[7].substr(1, 2), -10000) * 60 + fz::to_integral<int>(tokens[7].substr(3), -10000);
854 }
855 else if (tokens[7].size() == 4) {
856 minutes = fz::to_integral<int>(tokens[7].substr(0, 2), 10000) * 60 + fz::to_integral<int>(tokens[7].substr(2), 10000);
857 }
858 if (minutes < 10000) {
859 dt += fz::duration::from_minutes(minutes);
860 }
861 }
862
863 return set;
864 }
865 else {
866 dt.clear();
867 return false;
868 }
869 }
870 }
871
set_rfc822(std::string_view const & str)872 bool datetime::set_rfc822(std::string_view const& str)
873 {
874 return do_set_rfc822(*this, str);
875 }
876
set_rfc822(std::wstring_view const & str)877 bool datetime::set_rfc822(std::wstring_view const& str)
878 {
879 return do_set_rfc822(*this, str);
880 }
881
882 namespace {
883 template<typename String>
do_set_rfc3339(fz::datetime & dt,String str)884 bool do_set_rfc3339(fz::datetime& dt, String str)
885 {
886 if (str.size() < 19) {
887 dt.clear();
888 return false;
889 }
890
891 auto separator_pos = str.find_first_of(fzS(typename String::value_type, "tT ")); // Including space, there is a lowercase 'may' in section 5.6 of the RFC
892 if (separator_pos == String::npos) {
893 dt.clear();
894 return false;
895 }
896
897 auto date_part = str.substr(0, separator_pos);
898 auto const date_tokens = fz::strtok_view(date_part, fzS(typename String::value_type, "-"));
899
900 auto offset_pos = str.find_first_of(fzS(typename String::value_type, "+-Zz"), separator_pos);
901
902 String time_part;
903 if (offset_pos == String::npos) {
904 // Allow the offset part to be missing
905 time_part = str.substr(separator_pos + 1);
906 }
907 else {
908 time_part = str.substr(separator_pos + 1, offset_pos - separator_pos - 1);
909 }
910
911 auto const time_tokens = fz::strtok_view(time_part, fzS(typename String::value_type, ":."));
912 if (date_tokens.size() == 3 && (time_tokens.size() == 3 || time_tokens.size() == 4)) {
913 int year = fz::to_integral<int>(date_tokens[0]);
914 if (year < 1000) {
915 if (year < 1000) {
916 year += 1900;
917 }
918 }
919 int month = fz::to_integral<int>(date_tokens[1]);
920 int day = fz::to_integral<int>(date_tokens[2]);
921
922 int hour = fz::to_integral<int>(time_tokens[0]);
923 int minute = fz::to_integral<int>(time_tokens[1]);
924 int second = fz::to_integral<int>(time_tokens[2]);
925
926 bool set{};
927 if (time_tokens.size() == 4) {
928 // Convert fraction, .82 is 820ms
929 int ms = fz::to_integral<int>(time_tokens[3].substr(0, 3));
930 if (time_tokens[3].size() == 1) {
931 ms *= 100;
932 }
933 else if (time_tokens[3].size() == 2) {
934 ms *= 10;
935 }
936 set = dt.set(fz::datetime::utc, year, month, day, hour, minute, second, ms);
937 }
938 else {
939 set = dt.set(fz::datetime::utc, year, month, day, hour, minute, second);
940 }
941
942 if (set && offset_pos != String::npos && str[offset_pos] != 'Z') {
943 auto const offset_tokens = fz::strtok_view(str.substr(offset_pos + 1), ':');
944 if (offset_tokens.size() != 2) {
945 dt.clear();
946 return false;
947 }
948
949 int minutes = fz::to_integral<int>(offset_tokens[0], 10009) * 60 + fz::to_integral<int>(offset_tokens[1], 10000);
950 if (minutes < 10000) {
951 if (str[offset_pos] == '+') {
952 minutes = -minutes;
953 }
954 dt += fz::duration::from_minutes(minutes);
955 }
956 }
957
958 return set;
959 }
960
961 dt.clear();
962 return false;
963 }
964 }
965
set_rfc3339(std::string_view const & str)966 bool datetime::set_rfc3339(std::string_view const& str)
967 {
968 return do_set_rfc3339(*this, str);
969 }
970
set_rfc3339(std::wstring_view const & str)971 bool datetime::set_rfc3339(std::wstring_view const& str)
972 {
973 return do_set_rfc3339(*this, str);
974 }
975
976 }
977