Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
DateTime.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <algorithm>
7#include <ctime>
8#include <sstream>
9
14#include "Stroika/Foundation/Execution/Exceptions.h"
15#include "Stroika/Foundation/Execution/Throw.h"
17
18#include "DateTime.h"
19
20using namespace Stroika::Foundation;
22using namespace Stroika::Foundation::Common;
23using namespace Stroika::Foundation::Execution;
24using namespace Stroika::Foundation::Memory;
25using namespace Stroika::Foundation::Time;
26
27// Comment this in to turn on aggressive noisy DbgTrace in this module
28//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
29
30namespace {
31 constexpr int kSecondsPerMinute_ = 60;
32 constexpr int kSecondsPerHour_ = 60 * kSecondsPerMinute_;
33 constexpr int kSecondsPerDay_ = 24 * kSecondsPerHour_;
34}
35
36namespace {
37 constexpr int kTM_Year_RelativeToYear_{1900}; // see https://man7.org/linux/man-pages/man3/ctime.3.html
38}
39
40namespace {
41 constexpr bool kRequireImbueToUseFacet_ =
42 false; // example uses it, and code inside windows tmget seems to reference it, but no logic for this, and no clear docs (and works same either way apparently)
43}
44
45/*
46 * Subtle implementation note:
47 * http://www.cplusplus.com/reference/ctime/tm/
48 *
49 * tm.year is years since 1900 (kTM_Year_RelativeToYear_)
50 */
51
52#if qStroika_Foundation_Common_Platform_Windows
53namespace {
54 TimeOfDay mkTimeOfDay_ (const ::SYSTEMTIME& sysTime)
55 {
56 ::WORD hour = max (sysTime.wHour, static_cast<WORD> (0));
57 hour = min (hour, static_cast<WORD> (23));
58 ::WORD minute = max (sysTime.wMinute, static_cast<WORD> (0));
59 minute = min (minute, static_cast<WORD> (59));
60 ::WORD secs = max (sysTime.wSecond, static_cast<WORD> (0));
61 secs = min (secs, static_cast<WORD> (59));
62 return TimeOfDay{hour, minute, secs, DataExchange::ValidationStrategy::eThrow};
63 }
64 Date mkDate_ (const SYSTEMTIME& sysTime)
65 {
66 return Date{Year (sysTime.wYear), MonthOfYear (sysTime.wMonth), DayOfMonth (sysTime.wDay), DataExchange::ValidationStrategy::eThrow};
67 }
68}
69#endif
70
71namespace {
72#if qStroika_Foundation_Common_Platform_Windows
73 ::SYSTEMTIME toSysTime_ (TimeOfDay tod)
74 {
75 ::SYSTEMTIME t{};
76 unsigned int seconds = tod.GetAsSecondsCount ();
77 unsigned int minutes = seconds / 60;
78 unsigned int hours = minutes / 60;
79
80 hours = min (hours, 23U);
81 t.wHour = static_cast<WORD> (hours);
82
83 minutes -= hours * 60;
84 minutes = min (minutes, 59U);
85 t.wMinute = static_cast<WORD> (minutes);
86
87 seconds -= (60 * 60 * hours + 60 * minutes);
88 seconds = min (seconds, 59U);
89 t.wSecond = static_cast<WORD> (seconds);
90 return t;
91 }
92#endif
93}
94
95namespace {
96 inline constexpr uint32_t GetSecondCount_ (const optional<TimeOfDay>& tod)
97 {
98 return tod.has_value () ? tod->GetAsSecondsCount () : 0;
99 }
100}
101
102namespace {
103#if qStroika_Foundation_Common_Platform_Windows
104 ::SYSTEMTIME toSYSTEM_ (const Date& date)
105 {
106 ::SYSTEMTIME st{};
107 st.wMonth = static_cast<::WORD> (static_cast<unsigned int> (date.As<year_month_day> ().month ()));
108 st.wDay = static_cast<::WORD> (static_cast<unsigned int> (date.As<year_month_day> ().day ()));
109 st.wYear = static_cast<::WORD> (static_cast<int> (date.As<year_month_day> ().year ()));
110 return st;
111 }
112#endif
113}
114
115namespace {
116 // @todo add error checking - so returns -1 outside UNIX EPOCH TIME
117 time_t mkgmtime_ (const tm* ptm)
118 {
119 // On GLIBC systems, could use _mkgmtime64 - https://github.com/leelwh/clib/blob/master/c/mktime64.c
120 // Based on https://stackoverflow.com/questions/12353011/how-to-convert-a-utc-date-time-to-a-time-t-in-c
121 constexpr int kDaysOfMonth_[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
122
123 time_t secs = 0;
124 int year = ptm->tm_year + kTM_Year_RelativeToYear_;
125 for (int y = 1970; y < year; ++y) {
126 secs += (chrono::year{y}.is_leap () ? 366 : 365) * kSecondsPerDay_;
127 }
128 // tm_mon is month from 0..11
129 for (int m = 0; m < ptm->tm_mon; ++m) {
130 secs += kDaysOfMonth_[m] * kSecondsPerDay_;
131 if (m == 1 && chrono::year{year}.is_leap ())
132 secs += kSecondsPerDay_;
133 }
134 secs += (ptm->tm_mday - 1) * kSecondsPerDay_;
135 secs += ptm->tm_hour * kSecondsPerHour_;
136 secs += ptm->tm_min * kSecondsPerMinute_;
137 secs += ptm->tm_sec;
138#if qCompilerAndStdLib_Supported_mkgmtime64
139 Assert (_mkgmtime64 (const_cast<tm*> (ptm)) == secs);
140#endif
141 return secs;
142 }
143}
144
145/*
146 ********************************************************************************
147 ********************** DateTime::FormatException *******************************
148 ********************************************************************************
149 */
150DateTime::FormatException::FormatException ()
151 : RuntimeErrorException<>{"Invalid DateTime Format"sv}
152{
153}
154
155/*
156 ********************************************************************************
157 *********************************** DateTime ***********************************
158 ********************************************************************************
159 */
160DateTime::DateTime (time_t unixEpochTime) noexcept
161 : fTimezone_{Timezone::kUTC}
162 , fDate_{Date::kMinJulianRep} // avoid initialization warning
163{
164 ::tm tmTime{};
165#if qStroika_Foundation_Common_Platform_Windows
166 (void)::_gmtime64_s (&tmTime, &unixEpochTime);
167#else
168 (void)::gmtime_r (&unixEpochTime, &tmTime);
169#endif
170 fDate_ = Date{Year (tmTime.tm_year + kTM_Year_RelativeToYear_), MonthOfYear (tmTime.tm_mon + 1), DayOfMonth (tmTime.tm_mday),
171 DataExchange::ValidationStrategy::eThrow};
172 fTimeOfDay_ = TimeOfDay{static_cast<unsigned> (tmTime.tm_hour), static_cast<unsigned> (tmTime.tm_min), static_cast<unsigned> (tmTime.tm_sec)};
173}
174
175DateTime::DateTime (const ::tm& tmTime, const optional<Timezone>& tz) noexcept
176 : fTimezone_{tz}
177 , fDate_{Year (tmTime.tm_year + kTM_Year_RelativeToYear_), MonthOfYear (tmTime.tm_mon + 1), DayOfMonth (tmTime.tm_mday)}
178 , fTimeOfDay_{TimeOfDay{static_cast<unsigned> (tmTime.tm_hour), static_cast<unsigned> (tmTime.tm_min),
179 static_cast<unsigned> (tmTime.tm_sec), DataExchange::ValidationStrategy::eThrow}}
180{
181}
182
183DateTime::DateTime (const ::timespec& tmTime, const optional<Timezone>& tz) noexcept
184 : fTimezone_{tz}
185 , fDate_{Date::kMinJulianRep} // avoid initialization warning
186{
187 time_t unixTime = tmTime.tv_sec; // IGNORE tv_nsec FOR NOW because we currently don't support fractional seconds in DateTime
188#if qStroika_Foundation_Common_Platform_POSIX
189 ::tm tmTimeDataBuf{};
190 ::tm* tmTimeData = ::gmtime_r (&unixTime, &tmTimeDataBuf);
191#elif qStroika_Foundation_Common_Platform_Windows
192 ::tm tmTimeDataBuf{};
193 if (errno_t e = ::gmtime_s (&tmTimeDataBuf, &unixTime)) {
194 ThrowPOSIXErrNo (e);
195 };
196 ::tm* tmTimeData = &tmTimeDataBuf;
197#else
198 ::tm* tmTimeData = ::gmtime (&unixTime); // not threadsafe
199#endif
200 fDate_ = Date{Year (tmTimeData->tm_year + kTM_Year_RelativeToYear_), MonthOfYear (tmTimeData->tm_mon + 1),
201 DayOfMonth (tmTimeData->tm_mday), DataExchange::ValidationStrategy::eThrow};
202 fTimeOfDay_ = TimeOfDay{static_cast<unsigned> (tmTimeData->tm_hour), static_cast<unsigned> (tmTimeData->tm_min),
203 static_cast<unsigned> (tmTimeData->tm_sec), DataExchange::ValidationStrategy::eThrow};
204}
205
206#if qStroika_Foundation_Common_Platform_POSIX
207DateTime::DateTime (const timeval& tmTime, const optional<Timezone>& tz) noexcept
208 : fTimezone_{tz}
209 , fDate_{Date::kMinJulianRep} // avoid initialization warning
210{
211 time_t unixTime = tmTime.tv_sec; // IGNORE tv_usec FOR NOW because we currently don't support fractional seconds in DateTime
212 tm tmTimeData{};
213 (void)::gmtime_r (&unixTime, &tmTimeData);
214 fDate_ = Date{Year (tmTimeData.tm_year + kTM_Year_RelativeToYear_), MonthOfYear (tmTimeData.tm_mon + 1),
215 DayOfMonth (tmTimeData.tm_mday), DataExchange::ValidationStrategy::eThrow};
216 fTimeOfDay_ = TimeOfDay{static_cast<unsigned> (tmTimeData.tm_hour), static_cast<unsigned> (tmTimeData.tm_min),
217 static_cast<unsigned> (tmTimeData.tm_sec), DataExchange::ValidationStrategy::eThrow};
218}
219#endif
220
221#if qStroika_Foundation_Common_Platform_Windows
222DateTime::DateTime (const ::SYSTEMTIME& sysTime, const optional<Timezone>& tz) noexcept
223 : fTimezone_{tz}
224 , fDate_{mkDate_ (sysTime)}
225 , fTimeOfDay_{mkTimeOfDay_ (sysTime)}
226{
227}
228
229DateTime::DateTime (const ::FILETIME& fileTime, const optional<Timezone>& tz) noexcept
230 : fTimezone_{tz}
231 , fDate_{Date::kMinJulianRep} // avoid initialization warning
232{
233 ::SYSTEMTIME sysTime{};
234 if (::FileTimeToSystemTime (&fileTime, &sysTime)) {
235 fDate_ = mkDate_ (sysTime);
236 fTimeOfDay_ = mkTimeOfDay_ (sysTime);
237 }
238}
239#endif
240
241DateTime DateTime::Parse (const String& rep, LocaleIndependentFormat format)
242{
243 size_t nCharsConsumed;
244 if (auto o = ParseQuietly (rep, format, &nCharsConsumed); o and nCharsConsumed == rep.size ()) {
245 return *o;
246 }
247 Execution::Throw (FormatException::kThe);
248}
249
250DateTime DateTime::Parse (const String& rep, const locale& l, const String& formatPattern)
251{
252 if (rep.empty ()) [[unlikely]] {
253 Execution::Throw (FormatException::kThe); // Since this API returns DateTime, not optional<>, no good value to return for empty argument (see ParseQuietly)
254 }
255 size_t nCharsConsumed;
256 if (auto o = ParseQuietly_ (rep.As<wstring> (), use_facet<time_get<wchar_t>> (l), formatPattern, &nCharsConsumed);
257 o and nCharsConsumed == rep.size ()) {
258 return *o;
259 }
260 Execution::Throw (FormatException::kThe);
261}
262
263DateTime DateTime::Parse (const String& rep, const locale& l, const Traversal::Iterable<String>& formatPatterns)
264{
265 if (rep.empty ()) [[unlikely]] {
266 Execution::Throw (FormatException::kThe);
267 }
268 wstring wRep = rep.As<wstring> ();
269 const time_get<wchar_t>& tmget = use_facet<time_get<wchar_t>> (l);
270 for (const auto& formatPattern : formatPatterns) {
271 size_t nCharsConsumed;
272 if (auto o = ParseQuietly_ (wRep, tmget, formatPattern, &nCharsConsumed); o and nCharsConsumed == rep.size ()) {
273 return *o;
274 }
275 }
276 Execution::Throw (FormatException::kThe);
277}
278
279DateTime DateTime::Parse (const String& rep, const String& formatPattern)
280{
281 return Parse (rep, locale{}, formatPattern);
282}
283
284optional<DateTime> DateTime::ParseQuietly (const String& rep, LocaleIndependentFormat format, size_t* consumedCharacters)
285{
286 if (rep.empty ()) [[unlikely]] {
287 return nullopt;
288 }
289 switch (format) {
290 case LocaleIndependentFormat::eISO8601: {
291 // SEE BNF in https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
292 int numCharsConsumed{};
293 // full-date part
294 optional<Date> d;
295 {
296 int year{};
297 int month{};
298 int day{};
299 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
300 int nItems = ::swscanf (rep.As<wstring> ().c_str (), L"%d-%d-%d%n", &year, &month, &day, &numCharsConsumed);
301 DISABLE_COMPILER_MSC_WARNING_END (4996)
302 if (nItems < 3 or numCharsConsumed < 8) [[unlikely]] {
303 return nullopt;
304 }
305 d = Date{Year{year}, MonthOfYear (month), DayOfMonth (day)};
306 }
307 Assert (d);
308 optional<TimeOfDay> t;
309 {
310 String timePart = rep.SubString (numCharsConsumed);
311 Memory::StackBuffer<wchar_t> timePartBuf{};
312 const wchar_t* startOfTimePart = get<0> (timePart.c_str (&timePartBuf));
313 // nb: OK to not check strlen cuz string NUL terminated
314 // https://www.rfc-editor.org/rfc/rfc822#section-5 says can be upper or lower case T, or even ' ', but 'T' preferred/most common/recommended
315 if (*startOfTimePart == 'T' or *startOfTimePart == 't' or *startOfTimePart == ' ') [[likely]] {
316 ++numCharsConsumed;
317 int hour{};
318 int minute{};
319 int second{};
320 int ncc{};
321 float secsFloat{};
322 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
323 int nItems = ::swscanf (startOfTimePart + 1, L"%d:%d:%f%n", &hour, &minute, &secsFloat, &ncc);
324 DISABLE_COMPILER_MSC_WARNING_END (4996)
325 if (nItems == 3 and ncc >= 8) {
326 // for now we only support integral number of seconds, but allow reading to not fail if fractions given
327 second = static_cast<int> (secsFloat);
328 }
329 else {
330 return nullopt;
331 }
332 numCharsConsumed += ncc; // @todo fix - this is count of wchar_t not necessary full 'char32_t' characters
333 t = TimeOfDay{static_cast<unsigned> (hour), static_cast<unsigned> (minute), static_cast<unsigned> (second),
334 DataExchange::ValidationStrategy::eThrow};
335 }
336 }
337 // see about timezone (aka time-offset)
338 optional<Timezone> tz;
339 if (t) { // only can be present - so only check - if there is a time
340 String tzArea = rep.SubString (numCharsConsumed);
341 wstring tzAreaW = tzArea.As<wstring> ();
342 const wchar_t* startTZArea = tzAreaW.c_str ();
343 if (*startTZArea == 'Z' or *startTZArea == 'z') { // nb: OK to not check strlen cuz string NUL terminated
344 tz = Timezone::kUTC;
345 numCharsConsumed += 1;
346 }
347 else {
348 int tzHr{};
349 int tzMn{};
350 wchar_t plusMinusChar{};
351 int ncc{};
352 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
353 int nItems = ::swscanf (startTZArea, L"%c%d:%d%n", &plusMinusChar, &tzHr, &tzMn, &ncc);
354 DISABLE_COMPILER_MSC_WARNING_END (4996)
355 if ((nItems == 3) and (plusMinusChar == '+' or plusMinusChar == '-')) {
356 if (plusMinusChar == '-') {
357 tzHr = -tzHr;
358 tzMn = -tzMn;
359 }
360 tz = Timezone{static_cast<int16_t> (tzHr * 60 + tzMn), DataExchange::ValidationStrategy::eThrow};
361 numCharsConsumed += ncc;
362 }
363 else if ((nItems == 2) and (plusMinusChar == '+' or plusMinusChar == '-')) {
364 // TZ can be -400 instead of -4:00
365 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
366 nItems = ::swscanf (startTZArea, L"%c%d%n", &plusMinusChar, &tzMn, &ncc);
367 DISABLE_COMPILER_MSC_WARNING_END (4996)
368 // According to https://en.wikipedia.org/wiki/UTC_offset#:~:text=The%20UTC%20offset%20is%20the,%2C%20or%20%C2%B1%5Bhh%5D a 4 digit timezone
369 // offset means HHMM, so adjust
370 switch (ncc) {
371 case 3:
372 tzMn *= 60;
373 break;
374 case 4:
375 case 5: {
376 int hrs = tzMn / 100;
377 int min = tzMn % 100;
378 tzMn = hrs * 60 + min;
379 } break;
380 default:
381 return nullopt;
382 }
383 if (plusMinusChar == '-') {
384 tzMn = -tzMn;
385 }
386 tz = Timezone{static_cast<int16_t> (tzMn), DataExchange::ValidationStrategy::eThrow};
387 numCharsConsumed += ncc;
388 }
389 else {
390 // if nItems == 0, this is OK, just means not specified. Else probably an issue, but caught by checking number of characters consumed
391 }
392 }
393 }
394 Assert (0 <= numCharsConsumed and numCharsConsumed <= static_cast<int> (rep.length ()));
395 if (consumedCharacters != nullptr) {
396 *consumedCharacters = numCharsConsumed;
397 }
398 return t.has_value () ? DateTime{*d, t, tz} : DateTime{*d};
399 } break;
400 case LocaleIndependentFormat::eRFC1123: {
401 /*
402 * From https://tools.ietf.org/html/rfc822#section-5
403 * 5.1. SYNTAX
404 *
405 * date-time = [ day "," ] date time ; dd mm yy
406 * ; hh:mm:ss zzz
407 *
408 * day = "Mon" / "Tue" / "Wed" / "Thu"
409 * / "Fri" / "Sat" / "Sun"
410 *
411 * date = 1*2DIGIT month 2DIGIT ; day month year
412 * ; e.g. 20 Jun 82
413 *
414 * month = "Jan" / "Feb" / "Mar" / "Apr"
415 * / "May" / "Jun" / "Jul" / "Aug"
416 * / "Sep" / "Oct" / "Nov" / "Dec"
417 *
418 * time = hour zone ; ANSI and Military
419 *
420 * hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
421 * ; 00:00:00 - 23:59:59
422 *
423 * zone = "UT" / "GMT" ; Universal Time
424 * ; North American : UT
425 * / "EST" / "EDT" ; Eastern: - 5/ - 4
426 * / "CST" / "CDT" ; Central: - 6/ - 5
427 * / "MST" / "MDT" ; Mountain: - 7/ - 6
428 * / "PST" / "PDT" ; Pacific: - 8/ - 7
429 * / 1ALPHA ; Military: Z = UT;
430 * ; A:-1; (J not used)
431 * ; M:-12; N:+1; Y:+12
432 * / ( ("+" / "-") 4DIGIT ) ; Local differential
433 * ; hours+min. (HHMM)
434 */
435 unsigned int numCharsConsumed{};
436 String tmp = rep;
437 if (auto i = tmp.Find (',')) {
438 tmp = tmp.SubString (*i + 1).LTrim (); // we can ignore the day of the week (string) since optional and redundant.
439 numCharsConsumed += static_cast<unsigned int> (rep.length () - tmp.length ());
440 }
441 int year{};
442 int month{};
443 int day{};
444 int hour{};
445 int minute{};
446 int second{};
447 wchar_t monthStr[4]{};
448 wchar_t tzStr[101]{};
449 int nItems;
450 {
451 int ncc{};
452 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
453 nItems = ::swscanf (tmp.As<wstring> ().c_str (), L"%d %3ls %d %d:%d:%d %100ls%n", &day, &monthStr, &year, &hour, &minute,
454 &second, &tzStr, &ncc);
455 DISABLE_COMPILER_MSC_WARNING_END (4996)
456
457 // tzStr captures the first token after the time, but there are often extra (ignored) tokens
458 // (e.g. +400 (PST))
459 // So just pretend we used the entire string
460 if (nItems == 7) {
461 // ncc += 1 + ::wcslen (tzStr);
462 ncc = static_cast<int> (tmp.size ());
463 }
464 numCharsConsumed += ncc;
465 }
466
467 constexpr wchar_t kMonths_[12][4] = {L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun",
468 L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec"};
469 for (size_t i = 0; i < NEltsOf (kMonths_); ++i) {
470 if (::wcscmp (monthStr, kMonths_[i]) == 0) {
471 month = static_cast<int> (i + 1); // one-based numbering
472 break;
473 }
474 }
475 if (nItems < 3) {
476 return nullopt;
477 }
478 Date d = Date{Year{year}, static_cast<MonthOfYear> (month), static_cast<DayOfMonth> (day), DataExchange::ValidationStrategy::eThrow};
479 optional<TimeOfDay> t;
480 if (nItems >= 5) {
481 t = TimeOfDay{static_cast<unsigned> (hour), static_cast<unsigned> (minute), static_cast<unsigned> (second),
482 DataExchange::ValidationStrategy::eThrow};
483 }
484 optional<Timezone> tz;
485 constexpr pair<const wchar_t*, Timezone> kNamedTimezones_[]{
486 {L"Z", Timezone::kUTC}, {L"UT", Timezone::kUTC}, {L"GMT", Timezone::kUTC}, {L"EST", Timezone{-5 * 60}},
487 {L"EDT", Timezone{-4 * 60}}, {L"CST", Timezone{-6 * 60}}, {L"CDT", Timezone{-5 * 60}}, {L"MST", Timezone{-7 * 60}},
488 {L"MDT", Timezone{-6 * 60}}, {L"PST", Timezone{-8 * 60}}, {L"PDT", Timezone{-7 * 60}}, {L"A", Timezone{-1 * 60}},
489 {L"B", Timezone{-2 * 60}}, {L"C", Timezone{-3 * 60}}, {L"D", Timezone{-4 * 60}}, {L"E", Timezone{-5 * 60}},
490 {L"F", Timezone{-6 * 60}}, {L"G", Timezone{-7 * 60}}, {L"H", Timezone{-8 * 60}}, {L"I", Timezone{-9 * 60}},
491 {L"K", Timezone{-10 * 60}}, {L"L", Timezone{-11 * 60}}, {L"M", Timezone{-12 * 60}}, {L"N", Timezone{1 * 60}},
492 {L"O", Timezone{2 * 60}}, {L"P", Timezone{3 * 60}}, {L"Q", Timezone{4 * 60}}, {L"R", Timezone{5 * 60}},
493 {L"S", Timezone{6 * 60}}, {L"T", Timezone{7 * 60}}, {L"U", Timezone{8 * 60}}, {L"V", Timezone{9 * 60}},
494 {L"W", Timezone{10 * 60}}, {L"X", Timezone{11 * 60}}, {L"Y", Timezone{12 * 60}},
495 };
496 for (size_t i = 0; i < NEltsOf (kNamedTimezones_); ++i) {
497 if (::wcscmp (tzStr, kNamedTimezones_[i].first) == 0) {
498 tz = kNamedTimezones_[i].second;
499 break;
500 }
501 }
502 if (not tz.has_value ()) {
504 }
505 Assert (0 <= numCharsConsumed and numCharsConsumed <= rep.length ());
506 if (consumedCharacters != nullptr) {
507 *consumedCharacters = numCharsConsumed;
508 }
509 return t.has_value () ? DateTime{d, *t, tz} : DateTime{d};
510 } break;
511 default: {
513 } break;
514 }
515 return nullopt;
516}
517
518optional<DateTime> DateTime::ParseQuietly (const String& rep, const locale& l, const Traversal::Iterable<String>& formatPatterns, size_t* consumedCharacters)
519{
520 if (rep.empty ()) [[unlikely]] {
521 return nullopt;
522 }
523 wstring wRep = rep.As<wstring> ();
524 const time_get<wchar_t>& tmget = use_facet<time_get<wchar_t>> (l);
525 for (const auto& formatPattern : formatPatterns) {
526 // @todo tecnhnically, the consumedCharacters may not be 100% right, but so hard to fix (if wchar_t != char32_t)
527 // unless I can use facet for char32_t - not just wchar_t???
528 if (auto o = ParseQuietly_ (wRep, tmget, formatPattern, consumedCharacters)) {
529 return *o;
530 }
531 }
532 return nullopt;
533}
534
535optional<DateTime> DateTime::ParseQuietly_ (const wstring& rep, const time_get<wchar_t>& tmget, const String& formatPattern, size_t* consumedCharacters)
536{
537 Require (not rep.empty ());
538
539 ios::iostate errState = ios::goodbit;
540 tm when{};
541 size_t nCharsConsumed{};
542 {
543 wstring formatPatternWS = formatPattern.As<wstring> ();
544 wistringstream iss{rep};
545 istreambuf_iterator<wchar_t> itbegin{iss}; // beginning of iss
546 istreambuf_iterator<wchar_t> i = tmget.get (itbegin, istreambuf_iterator<wchar_t>{}, iss, errState, &when, formatPatternWS.c_str (),
547 formatPatternWS.c_str () + formatPatternWS.length ());
548 if (errState & ios::eofbit) {
549 nCharsConsumed = rep.size ();
550 }
551 else {
552 //tmphack workaround msft bug
553 nCharsConsumed = static_cast<size_t> (distance (itbegin, i));
554 }
555 }
556
557 if constexpr (qCompilerAndStdLib_locale_time_get_reverses_month_day_with_2digit_year_Buggy) {
558 // Now that I've understood this bug better, I think I can do a better/wider workaround, not just this special case...
559 if (formatPattern == "%x %X"sv) {
560 // It now appears this MSFT-only issue is that if you have a 2-digit year, their %x-parse code reverses the month and day
561 wistringstream iss{rep};
562 istreambuf_iterator<wchar_t> itbegin{iss};
563 istreambuf_iterator<wchar_t> itend;
564 errState = ios::goodbit;
565 (void)tmget.get_date (itbegin, itend, iss, errState, &when); // just overwrite date portion - assume time portion remains unchanged by this
566 }
567 }
568
569 if ((errState & ios::badbit) or (errState & ios::failbit)) [[unlikely]] {
570 return nullopt;
571 }
572 Assert (0 <= nCharsConsumed and nCharsConsumed <= rep.length ());
573 if (consumedCharacters != nullptr) {
574 *consumedCharacters = nCharsConsumed;
575 }
576 // @todo probably could read TIMEZONE (occasionally) from the when output (maybe look at format string to tell if its being set)
577 // SEE http://stroika-bugs.sophists.com/browse/STK-671
578 return DateTime{when, Timezone::kUnknown};
579}
580
581DateTime DateTime::AsLocalTime () const
582{
583 if (GetTimezone () == Timezone::kUTC) {
584 DateTime tmp = AddSeconds (Timezone::kLocalTime.GetBiasFromUTC (fDate_, Memory::NullCoalesce (fTimeOfDay_, TimeOfDay{0})));
585 return DateTime{tmp.GetDate (), tmp.GetTimeOfDay (), Timezone::kLocalTime};
586 }
587 else if (GetTimezone () == Timezone::kLocalTime) {
588 return *this;
589 }
590 else if (GetTimezone () == Timezone::kUnknown) {
591 return DateTime{GetDate (), GetTimeOfDay (), Timezone::kLocalTime};
592 }
593 else {
594 // Convert to UTC, and then back to localtime
595 return AsUTC ().AsLocalTime ();
596 }
597}
598
599DateTime DateTime::AsUTC () const
600{
601 [[maybe_unused]] auto oldCode = [&] () {
602 if (GetTimezone () == Timezone::kUTC) {
603 return *this;
604 }
605 else {
606 DateTime tmp = fTimezone_.has_value ()
607 ? AddSeconds (-fTimezone_->GetBiasFromUTC (fDate_, Memory::NullCoalesce (fTimeOfDay_, TimeOfDay{0})))
608 : *this;
609 return DateTime{tmp.GetDate (), tmp.GetTimeOfDay (), Timezone::kUTC};
610 }
611 };
612 Ensure (AsTimezone (Timezone::kUTC) == oldCode ());
613 return AsTimezone (Timezone::kUTC);
614}
615
616DateTime DateTime::AsTimezone (Timezone tz) const
617{
618 if (GetTimezone () == tz) {
619 return *this;
620 }
621 else {
622 DateTime tmp =
623 fTimezone_.has_value () ? AddSeconds (-fTimezone_->GetBiasFromUTC (fDate_, Memory::NullCoalesce (fTimeOfDay_, TimeOfDay{0}))) : *this;
624 return DateTime{tmp.GetDate (), tmp.GetTimeOfDay (), tz};
625 }
626}
627
628DateTime DateTime::Now () noexcept
629{
630#if qStroika_Foundation_Common_Platform_POSIX
631 // time() returns the time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds.
632 // Convert to LocalTime - just for symetry with the windows version (and cuz our API spec say so)
633 return DateTime{::time (nullptr)}.AsLocalTime ();
634#elif qStroika_Foundation_Common_Platform_Windows
635 ::SYSTEMTIME st{};
636 ::GetLocalTime (&st);
637 return DateTime{st, Timezone::kLocalTime};
638#else
640 return DateTime{};
641#endif
642}
643
644DateTime DateTime ::NowUTC () noexcept
645{
646 // @todo COULD be more efficient, but KISS for now
647 return Now ().AsUTC ();
648}
649
650optional<bool> DateTime::IsDaylightSavingsTime () const
651{
652 if (optional<Timezone> otz = GetTimezone ()) {
653 return otz->IsDaylightSavingsTime (GetDate (), GetTimeOfDay ());
654 }
655 return {};
656}
657
658String DateTime::Format (LocaleIndependentFormat format) const
659{
660 switch (format) {
661 case LocaleIndependentFormat::eISO8601: {
662 StringBuilder r = fDate_.Format (Date::kISO8601Format);
663 if (fTimeOfDay_.has_value ()) {
664 String timeStr = fTimeOfDay_->Format (TimeOfDay::kISO8601Format);
665 r << "T"sv << timeStr;
666 if (fTimezone_) {
667 if (fTimezone_ == Timezone::kUTC) {
668 static const String kZ_{"Z"sv};
669 r << kZ_;
670 }
671 else {
672 auto tzBias = fTimezone_->GetBiasFromUTC (fDate_, Memory::NullCoalesce (fTimeOfDay_, TimeOfDay{0}));
673 int minuteBias = abs (static_cast<int> (tzBias)) / 60;
674 int hrs = minuteBias / 60;
675 int mins = minuteBias - hrs * 60;
676 r << ::Format ("{}{:02}:{:02}"_f, (tzBias < 0 ? L"-" : L"+"), hrs, mins);
677 }
678 }
679 }
680 return r;
681 } break;
682 case LocaleIndependentFormat::eRFC1123: {
683 optional<Timezone> tz = GetTimezone ();
684 static const String kFMT_ = "%a, %d %b %Y %H:%M:%S"_k;
685 String result = Format (locale::classic (), {kFMT_});
686 if (tz == Timezone::kUnknown) {
687 return result;
688 }
689 else {
690 return result + " "sv + tz->AsRFC1123 (fDate_, Memory::NullCoalesce (fTimeOfDay_, TimeOfDay{0}));
691 }
692 } break;
693 default: {
695 return String{};
696 }
697 }
698}
699
700String DateTime::Format (NonStandardPrintFormat pf) const
701{
702 switch (pf) {
703 case eCurrentLocale_WithZerosStripped: {
704 /*
705 * Use basic current locale formatting, and then use regexp to find special case 0s to strip.
706 */
707 String mungedData = Format (locale{});
708 {
709 // FIX Wed Jan 4 03:... to Wed Jan 4 3:
710 static const RegularExpression kZero2StripPattern_{"\\s0\\d"sv}; // space followed zero and a digit
711 size_t startAt = 0;
712 while (auto o = mungedData.Find (kZero2StripPattern_, startAt)) {
713 Assert (o->first >= startAt);
714 o->first++; // dont delete space
715 o->second--; // dont delete digit
716 mungedData = mungedData.RemoveAt (*o);
717 }
718 }
719 {
720 // FIX 04/03/01 => 4/3/01
721 static const RegularExpression kZero2StripPattern_{"0\\d/"sv}; // 0 digit slash
722 size_t startAt = 0;
723 while (auto o = mungedData.Find (kZero2StripPattern_, startAt)) {
724 Assert (o->first >= startAt);
725 mungedData = mungedData.RemoveAt (o->first);
726 }
727 }
728 {
729 // FIX 3:00 to 3
730 static const RegularExpression kZero2StripPattern_{":00\\s"sv}; // use \s not \b cuz dont do in middle of 3:00:01
731 size_t startAt = 0;
732 while (auto o = mungedData.Find (kZero2StripPattern_, startAt)) {
733 Assert (o->first >= startAt);
734 o->second--; // dont delete space
735 mungedData = mungedData.RemoveAt (*o);
736 }
737 }
738 return mungedData.NormalizeSpace (); // sometimes on windows, extra spaces left around
739 }
740 }
742 return String{};
743}
744
745String DateTime::Format (const locale& l) const
746{
747 if (GetTimeOfDay ().has_value ()) {
748 return Format (l, kLocaleStandardFormat);
749 }
750 else {
751 // otherwise we get a 'datetime' of 'XXX ' - with a space at the end
752 return GetDate ().Format (l);
753 }
754}
755
756String DateTime::Format (const String& formatPattern) const
757{
758 return Format (locale{}, formatPattern);
759}
760
761String DateTime::Format (const locale& l, const String& formatPattern) const
762{
763 // https://en.cppreference.com/w/cpp/locale/time_put/put
764 const time_put<wchar_t>& tmput = use_facet<time_put<wchar_t>> (l);
765 wostringstream oss;
766
767 if constexpr (kRequireImbueToUseFacet_) {
768 oss.imbue (l);
769 }
770
771 tm when = As<tm> ();
772
773 if constexpr (qCompilerAndStdLib_locale_pctC_returns_numbers_not_alphanames_Buggy) {
774 if (l == locale::classic () and formatPattern == kLocaleStandardFormat) {
775 // this seems a weird format, but from https://en.cppreference.com/w/cpp/chrono/c/strftime: writes standard date and time string, e.g. Sun Oct 17 04:41:13 2010 (locale dependent)
776 static const wstring_view kAltPattern_{L"%a %b %e %T %Y"sv};
777 tmput.put (oss, oss, ' ', &when, kAltPattern_.data (), kAltPattern_.data () + kAltPattern_.length ());
778 return String{oss.str ()};
779 }
780 }
781
782 wstring formatPatternWS = formatPattern.As<wstring> ();
783 tmput.put (oss, oss, ' ', &when, formatPatternWS.c_str (), formatPatternWS.c_str () + formatPatternWS.length ());
784 // docs aren't clear about expectations, but glibc (gcc8) produces trailing whitespace which
785 // is not good. Unsure if that's a glibc bug or my correction here makes sense -- LGP 2018-10-16
786 return String{oss.str ()}.RTrim ();
787}
788
789String DateTime::ToString () const
790{
791 // @todo - reconsider how we format this cuz unclear if Format() already includes timezone -- LGP 2018-10-16
792 StringBuilder tmp = Format ();
793 if (const auto& tz = GetTimezone ()) {
794 tmp << " "sv << Characters::ToString (*tz);
795 }
796 return tmp;
797}
798
799Date::JulianDayNumber DateTime::DaysSince () const
800{
801 int r = DayDifference (GetToday (), As<Date> ());
802 if (r < 0) {
803 return 0;
804 }
805 else {
806 return r;
807 }
808}
809
810template <>
811time_t DateTime::As_Simple_ () const
812{
813 DateTime useDT = this->AsUTC (); // time_t defined in UTC
814 Date d = useDT.GetDate ();
815
816 if (useDT.GetDate ().GetYear () < Year{1970}) [[unlikely]] {
817 static const range_error kRangeErrror_{"DateTime cannot be converted to time_t - before 1970"};
818 Execution::Throw (kRangeErrror_);
819 }
820
821 ::tm tm{};
822 tm.tm_year = static_cast<int> (d.GetYear ()) - kTM_Year_RelativeToYear_;
823 tm.tm_mon = static_cast<unsigned int> (d.GetMonth ()) - 1;
824 tm.tm_mday = static_cast<unsigned int> (d.GetDayOfMonth ());
825 unsigned int totalSecondsRemaining = GetSecondCount_ (useDT.GetTimeOfDay ());
826 tm.tm_hour = totalSecondsRemaining / (60 * 60);
827 totalSecondsRemaining -= tm.tm_hour * 60 * 60;
828 tm.tm_min = totalSecondsRemaining / 60;
829 totalSecondsRemaining -= tm.tm_min * 60;
830 tm.tm_sec = totalSecondsRemaining;
831 time_t result = mkgmtime_ (&tm);
832 // NB: This CAN return -1 - if outside unix EPOCH time
833 return result;
834}
835
836template <>
837tm DateTime::As_Simple_ () const
838{
839 if (GetDate ().GetYear () < Year{kTM_Year_RelativeToYear_}) [[unlikely]] {
840 static const range_error kRangeErrror_{"DateTime cannot be convered to time_t - before 1900"};
841 Execution::Throw (kRangeErrror_);
842 }
843 tm tm{};
844 tm.tm_year = static_cast<int> (fDate_.GetYear ()) - kTM_Year_RelativeToYear_;
845 tm.tm_mon = static_cast<unsigned int> (fDate_.GetMonth ()) - 1;
846 tm.tm_mday = static_cast<unsigned int> (fDate_.GetDayOfMonth ());
847 tm.tm_wday = fDate_.GetDayOfWeek ().c_encoding ();
848 unsigned int totalSecondsRemaining = fTimeOfDay_.has_value () ? fTimeOfDay_->GetAsSecondsCount () : 0;
849 tm.tm_hour = totalSecondsRemaining / (60 * 60);
850 totalSecondsRemaining -= tm.tm_hour * 60 * 60;
851 Assert (0 <= totalSecondsRemaining and totalSecondsRemaining < 60 * 60); // cuz would have gone into hours
852 tm.tm_min = totalSecondsRemaining / 60;
853 totalSecondsRemaining -= tm.tm_min * 60;
854 Assert (0 <= totalSecondsRemaining and totalSecondsRemaining < 60); // cuz would have gone into minutes
855 tm.tm_sec = totalSecondsRemaining;
856 tm.tm_isdst = -1;
857 Ensure (0 <= tm.tm_hour and tm.tm_hour <= 23);
858 Ensure (0 <= tm.tm_min and tm.tm_min <= 59);
859 Ensure (0 <= tm.tm_sec and tm.tm_sec <= 59);
860 return tm;
861}
862
863template <>
864timespec DateTime::As_Simple_ () const
865{
866 timespec tspec;
867 tspec.tv_sec = As<time_t> ();
868 tspec.tv_nsec = 0; // IGNORE tv_nsec because we currently don't support fractional seconds in DateTime
869 return tspec;
870}
871
872#if qStroika_Foundation_Common_Platform_Windows
873::SYSTEMTIME DateTime::AsSYSTEMTIME_ () const
874{
875 // CAN GET RID OF toSYSTEM_/toSysTime_ and just inline logic here...
876 ::SYSTEMTIME d = toSYSTEM_ (fDate_);
877 ::SYSTEMTIME t = toSysTime_ (Memory::NullCoalesce (fTimeOfDay_, TimeOfDay{0}));
878 ::SYSTEMTIME r = d;
879 r.wHour = t.wHour;
880 r.wMinute = t.wMinute;
881 r.wSecond = t.wSecond;
882 r.wMilliseconds = t.wMilliseconds;
883 return r;
884}
885#endif
886
887DateTime DateTime::Add (const Duration& d) const
888{
889 return AddSeconds (d.As<int64_t> ());
890}
891
892DateTime DateTime::AddDays (int days) const
893{
894 return DateTime{GetDate ().Add (days), GetTimeOfDay (), GetTimezone ()};
895}
896
897DateTime DateTime::AddSeconds (int64_t seconds) const
898{
899 /* @todo - SHOULD BE MORE CAREFUL ABOUT OVERFLOW */
900 int64_t n = GetSecondCount_ (GetTimeOfDay ());
901 n += seconds;
902 int64_t dayDiff = 0;
903 if (n < 0) {
904 dayDiff = int64_t (-(-n + int64_t (TimeOfDay::kMaxSecondsPerDay) - 1) / int64_t (TimeOfDay::kMaxSecondsPerDay));
905 Assert (dayDiff < 0);
906 }
907 n -= dayDiff * static_cast<int64_t> (TimeOfDay::kMaxSecondsPerDay);
908 Assert (n >= 0);
909
910 // Now see if we overflowed
911 if (n >= static_cast<int64_t> (TimeOfDay::kMaxSecondsPerDay)) {
912 Assert (dayDiff == 0);
913 dayDiff = int64_t (n / int64_t (TimeOfDay::kMaxSecondsPerDay));
914 n -= dayDiff * static_cast<int64_t> (TimeOfDay::kMaxSecondsPerDay);
915 }
916 Assert (n >= 0);
917
918 Ensure (0 <= n and n < static_cast<int64_t> (TimeOfDay::kMaxSecondsPerDay));
919 Assert (numeric_limits<int>::lowest () <= dayDiff and dayDiff <= numeric_limits<int>::max ());
920 if (n == 0) {
921 return DateTime{GetDate ().Add (days{dayDiff}), GetTimeOfDay (), GetTimezone ()};
922 }
923 else {
924 return DateTime{GetDate ().Add (days{dayDiff}), TimeOfDay{static_cast<uint32_t> (n)}, GetTimezone ()};
925 }
926}
927
928Duration DateTime::Difference (const DateTime& rhs) const
929{
930 if (GetTimezone () == rhs.GetTimezone ()) {
931 int64_t dayDiff = static_cast<int64_t> (GetDate ().GetJulianRep ()) - static_cast<int64_t> (rhs.GetDate ().GetJulianRep ());
932 Time::DurationSeconds intraDaySecDiff = static_cast<Time::DurationSeconds> (GetSecondCount_ (GetTimeOfDay ())) -
933 static_cast<Time::DurationSeconds> (GetSecondCount_ (rhs.GetTimeOfDay ()));
934 return Duration{Time::DurationSeconds (kSecondsPerDay_ * dayDiff) + intraDaySecDiff};
935 }
936 else {
937 return AsUTC ().Difference (rhs.AsUTC ());
938 }
939}
940
941/*
942 ********************************************************************************
943 ************************* DateTime::ThreeWayComparer ***************************
944 ********************************************************************************
945 */
946strong_ordering DateTime::ThreeWayComparer::operator() (const DateTime& lhs, const DateTime& rhs) const
947{
948 if (lhs.GetTimezone () == rhs.GetTimezone () or (lhs.GetTimezone () == Timezone::kUnknown) or (rhs.GetTimezone () == Timezone::kUnknown)) {
949 if (auto cmp = lhs.GetDate () <=> rhs.GetDate (); cmp != strong_ordering::equal) {
950 return cmp;
951 }
952 return StdCompat::compare_three_way{}(lhs.GetTimeOfDay (), rhs.GetTimeOfDay ());
953 }
954 else if (fCoerceToCommonTimezone) {
955 return operator() (lhs.AsUTC (), rhs.AsUTC ());
956 }
957 else {
958 // if not coercing to common timezone, unclear how best to compare times. Probably default to the way we already compare datetime
959 // with first index being date, and then time, and only if those are the same use timezone as tie breaker
960 //
961 // This isn't a clearly good choice, so leave open the possability of changing this in the future -- LGP 2020-05-24
962 if (auto cmp = lhs.GetDate () <=> rhs.GetDate (); cmp != strong_ordering::equal) {
963 return cmp;
964 }
965 if (auto cmp = StdCompat::compare_three_way{}(lhs.GetTimeOfDay (), rhs.GetTimeOfDay ()); cmp != strong_ordering::equal) {
966 return cmp;
967 }
968 return StdCompat::compare_three_way{}(lhs.GetTimezone (), rhs.GetTimezone ());
969 }
970}
971
972/*
973 ********************************************************************************
974 ************************** DateTime operators *********************************
975 ********************************************************************************
976 */
977DateTime Time::operator+ (const DateTime& lhs, const Duration& rhs)
978{
979 // Define in .cpp file to avoid #include Duration in DateTime.h
980 return lhs.Add (rhs);
981}
982
983DateTime Time::operator- (const DateTime& lhs, const Duration& rhs)
984{
985 // Define in .cpp file to avoid #include Duration in DateTime.h
986 return lhs.Add (-rhs);
987}
988
989Duration Time::operator- (const DateTime& lhs, const DateTime& rhs)
990{
991 // Define in .cpp file to avoid #include Duration in DateTime.h
992 return lhs.Difference (rhs);
993}
994
995/*
996 ********************************************************************************
997 ************************** Math::NearlyEquals **********************************
998 ********************************************************************************
999 */
1000bool Math::NearlyEquals (Time::DateTime l, Time::DateTime r)
1001{
1002 return NearlyEquals (l, r, static_cast<Time::DurationSeconds> (1.0));
1003}
1004
1005bool Math::NearlyEquals (Time::DateTime l, Time::DateTime r, Time::DurationSeconds epsilon)
1006{
1007 return l == r or Math::NearlyEquals (static_cast<Time::DurationSeconds::rep> (l.As<time_t> ()),
1008 static_cast<Time::DurationSeconds::rep> (r.As<time_t> ()), epsilon.count ());
1009}
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotReached()
Definition Assertions.h:385
#define AssertNotReached()
Definition Assertions.h:355
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
RegularExpression is a compiled regular expression which can be used to match on a String class.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual size_t length() const noexcept
Definition String.inl:1045
nonvirtual String NormalizeSpace(Character useSpaceCharacter=' ') const
Replace sequences of whitespace characters (space, tab, newline etc) with a single space (or argument...
Definition String.cpp:1229
nonvirtual tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
Definition String.inl:1049
nonvirtual size_t size() const noexcept
Definition String.inl:534
nonvirtual String SubString(SZ from) const
nonvirtual String LTrim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1443
nonvirtual String RemoveAt(size_t charAt) const
Definition String.inl:604
nonvirtual String RTrim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1508
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
Definition String.inl:681
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
static constexpr string_view kISO8601Format
Y-M-D format - locale independent, and ISO-8601 date format standard.
Definition Date.h:465
static const JulianDayNumber kMinJulianRep
Definition Date.h:413
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
static constexpr uint32_t kMaxSecondsPerDay
Definition TimeOfDay.h:68
static constexpr string_view kISO8601Format
Definition TimeOfDay.h:107
nonvirtual constexpr uint32_t GetAsSecondsCount() const
Definition TimeOfDay.inl:25
static const optional< Timezone > kUnknown
Definition Timezone.h:159
static const Timezone kUTC
Definition Timezone.h:144
static const Timezone kLocalTime
Definition Timezone.h:153
static optional< Timezone > ParseTimezoneOffsetString(const char *tzStr)
Definition Timezone.cpp:75
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats errNo as a POSIX errno value, and throws a SystemError (subclass of @std::system_error) except...
Simple wrapper on std::chrono::day, with some helpful validation properties (assures constructed 'ok'...
Definition Date.h:163
Simple wrapper on std::chrono::month, with some helpful validation properties (assures constructed 'o...
Definition Date.h:131
Simple wrapper on std::chrono::year, with some helpful validation properties (assures constructed 'ok...
Definition Date.h:215