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