Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
DateTime.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Time_DateTime_h_
5#define _Stroika_Foundation_Time_DateTime_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <climits>
10#include <compare>
11#include <ctime>
12#include <locale>
13#include <string>
14
15#if qStroika_Platform_Windows
16#include <Windows.h>
17#endif
18
20#include "Stroika/Foundation/Common/Common.h"
22#include "Stroika/Foundation/Math/Common.h"
28
29/**
30 * \file
31 *
32 * \note Code-Status: <a href="Code-Status.md#Release">Release</a>
33 *
34 * TODO:
35 * @todo - https://github.com/SophistSolutions/Stroika/issues/806 (STK-671) - DateTime::Format and Parse () incorrectly handle the format strings %z and %Z (sort of)
36 *
37 * @todo Support various 64bit int (epoch time) types - even if time_t is 32-bit (such as on AIX).
38 * Be careful about overflow in underlying types like Date and TimeOfDay() however.
39 *
40 * @todo I'm not sure eCurrentLocale_WithZerosStripped is a good idea. Not sure if better
41 * to use separate format print arg or???
42 *
43 * @todo Should we PIN or throw OVERFLOW exception on values/requests which are out of range?
44 *
45 * @todo Future directions consider representing as big struct
46 * o And maybe store cached string reps for common cases as optimization and
47 * store cached second-offset (mutable) for quick compares
48 * o Note for timeofday it COULD be enhanced in the future to store TOD as
49 * fractional number of seconds. COULD use LINUX style struct with number of seconds and fixed
50 * point like number of nanoseconds (or some such)
51 */
52
53namespace Stroika::Foundation::Time {
54
55 using Characters::String;
56
57 class Duration; // forward declare for Difference ()
58
59 /*
60 * Description:
61 *
62 * DateTime is more than just a combination of Date, and Time. It also introduces the notion of TIMEZONE.
63 *
64 * Date part is always valid/required (use optional <DateTime> to support old DateTime{}.empty() behavior).
65 * TimeOfDay may be "unknown" (missing).
66 * Timezone may be "unknown" (missing), or a Timezone object (@see Timezone).
67 *
68 * \note DateTime constructors REQUIRE valid inputs, and any operations which might overflow throw range_error
69 * instead of creating invalid values.
70 *
71 * \note DateTime is only precise up to the nearest second - use TimePointSeconds for more precision.
72 *
73 * \note DateTime values are (optionally) associated with a particular timezone. If the value of the timezone is localtime and
74 * localtime changes, the DateTime then is relative to that new localtime. If the associated timezone is localtime, the
75 * interpretation of that timezone happens at the time a request requires it.
76 *
77 * \note Formats: kISO8601Format, kRFC1123Format and kHTTPDateFormat are LocaleIndependentFormat values - not strftime-style
78 * format strings, as Date's and TimeOfDay's kISO8601Format are - since an ISO 8601 or RFC 1123 date-time has a time zone, which
79 * std::time_put cannot quite write, nor std::time_get read. So DateTime writes and reads them itself (the
80 * LocaleIndependentFormat overloads of Format, Parse and ParseQuietly); call sites read the same either way -
81 * d.Format (Date::kISO8601Format), dt.Format (DateTime::kISO8601Format).
82 *
83 * \note c++20 clocks - and clock_cast etc.
84 * This class roughly corresponds to system_clock. Sadly - for conversions to / from TickCount () -
85 * clock_cast doesn't work - https://stackoverflow.com/questions/35282308/convert-between-c11-clocks
86 *
87 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
88 *
89 * \note <a href="Design-Overview.md#Comparisons">Comparisons</a>:
90 * o Standard Stroika Comparison support (operator<=>,operator==, etc);
91 *
92 * Also note - if the datetimes differ in their GetTimeZone() value, they are not necessarily
93 * considered different. If either one is unknown, they will both be treated as the same timezone.
94 * Otherwise, they will BOTH be converted to GMT, and compared as GMT.
95 *
96 * This coercion to GMT can be avoided by optional constructor argument to DateTime::ThreeWayComparer
97 */
98 class DateTime {
99 public:
100 /**
101 * Construct a DateTime record with the given date and time value. Presume that these values
102 * apply to the given (argument) timezone. This does not adjust the value of date/time for the given tz -
103 * but just records that this datetime refers to the given timezone.
104 *
105 * To change TO a target timezone, use AsUTC () or AsLocalTime ().
106 *
107 * explicit DateTime (time_t unixEpochTime) noexcept
108 * Creates a DateTime object in UTC, using UNIX Epoch time.
109 *
110 * explicit DateTime (const FILETIME& fileTime, const optional<Timezone>& tz = Timezone::UTC ()) noexcept;
111 * Most windows APIs return file-times in UTC (or so it appears). Because of this,
112 * our default interpretation of a FILETIME structure as as UTC.
113 * Call DateTime (ft).AsLocalTime () to get the value returned in local time.
114 *
115 * \note DateTime (time_t unixEpochTime) creates a datetime in UTC
116 * \note DateTime (ITimePoint) creates a datetime in UTC
117 *
118 * \note All DateTime constructors REQUIRE valid (in range) arguments.
119 *
120 * \note Since DateTime is only precise up to the nearest second, some constructions (like from time_point)
121 * will necessarily round/lose precision.
122 */
123 constexpr DateTime (DateTime&& src) noexcept = default;
124 constexpr DateTime (const DateTime& src) = default;
125 constexpr explicit DateTime (const Date& d) noexcept;
126 constexpr explicit DateTime (const DateTime& dt, const Date& updateDate) noexcept;
127 constexpr explicit DateTime (const DateTime& dt, const TimeOfDay& updateTOD) noexcept;
128 constexpr DateTime (const Date& date, const optional<TimeOfDay>& timeOfDay, const optional<Timezone>& tz = Timezone::kUnknown) noexcept;
129 explicit DateTime (time_t unixEpochTime) noexcept;
130 explicit DateTime (const ::tm& tmTime, const optional<Timezone>& tz = Timezone::kUnknown) noexcept;
131 explicit DateTime (const ::timespec& tmTime, const optional<Timezone>& tz = Timezone::kUnknown) noexcept;
132#if qStroika_Platform_POSIX
133 explicit DateTime (const ::timeval& tmTime, const optional<Timezone>& tz = Timezone::kUnknown) noexcept;
134#elif qStroika_Platform_Windows
135 explicit DateTime (const ::SYSTEMTIME& sysTime, const optional<Timezone>& tz = Timezone::kLocalTime) noexcept;
136 explicit DateTime (const ::FILETIME& fileTime, const optional<Timezone>& tz = Timezone::kUTC) noexcept;
137#endif
138 template <Common::ITimePoint T>
139 explicit DateTime (T timePoint) noexcept;
140
141 public:
142 /**
143 */
144 nonvirtual DateTime& operator= (DateTime&& rhs) noexcept = default;
145 nonvirtual DateTime& operator= (const DateTime& rhs) = default;
146
147 public:
148 /**
149 * \brief A date-time format DateTime writes and reads itself, whatever the locale. Use the constants kISO8601Format,
150 * kRFC1123Format and kHTTPDateFormat - the same values, and documented there.
151 */
152 enum class LocaleIndependentFormat {
153 eISO8601, ///< \brief the value of kISO8601Format
154 eRFC1123, ///< \brief the value of kRFC1123Format
155 eHTTPDate, ///< \brief the value of kHTTPDateFormat
156
157 Stroika_Define_Enum_Bounds (eISO8601, eHTTPDate)
158 };
159
160 public:
161 /**
162 * \brief ISO 8601's date-time, as RFC 3339 profiles it - the format to prefer for DateTime data you store or exchange:
163 * simple, readable, its sort order works naturally (alphabetical == by time), and the date-time of JSON APIs, XML
164 * Schema and most formats designed since. (RFC 1123's lives on in older protocols - HTTP, email - for
165 * compatibility.) It is what Stroika itself writes: a VariantValue - so JSON - holds a DateTime in this format.
166 *
167 * Format writes the date, then T and the time (if it has one), then Z for UTC, or the offset - or nothing, if its time
168 * zone is unknown:
169 * 2026-10-05T21:30:00Z
170 * 2026-10-05T17:30:00-04:00
171 * 2026-10-05
172 *
173 * Parse takes those, with t or a space in place of the T, seconds with a fraction (dropped - a DateTime keeps whole
174 * seconds), and an offset written -0400 or -04.
175 *
176 * See https://datatracker.ietf.org/doc/html/rfc3339
177 */
178 static constexpr auto kISO8601Format = LocaleIndependentFormat::eISO8601;
179
180 public:
181 /**
182 * \brief RFC 1123's date-time (RFC 822's, with a 4-digit year), as in email. HTTP's dates are a stricter form of it: for
183 * those, use kHTTPDateFormat.
184 *
185 * Format writes the names of the day and month in English (whatever the locale), the day of the month in 2 digits, then
186 * GMT for UTC, or the offset - or nothing, if its time zone is unknown:
187 * Sun, 06 Nov 1994 08:49:37 GMT
188 * Tue, 06 Nov 2018 06:25:51 -0800
189 *
190 * Parse takes those, and RFC 822's other forms: no day name, a 1-digit day of the month, a 2-digit year (read as the most
191 * recent year with those last digits not more than 50 years ahead), a named zone (UT, EST, PDT ..., or a military
192 * letter), extra spaces, and anything after the zone - as in "Tue, 6 Nov 2018 06:25:51 -0800 (PST)", whose (PST) just
193 * repeats the -0800.
194 *
195 * See https://tools.ietf.org/html/rfc1123#5.2.14 and https://tools.ietf.org/html/rfc822#section-5
196 */
197 static constexpr auto kRFC1123Format = LocaleIndependentFormat::eRFC1123;
198
199 public:
200 /**
201 * \brief HTTP's date format (RFC 9110 section 5.6.7's HTTP-date): of its Date, Expires, Last-Modified and
202 * If-Modified-Since headers, and a cookie's Expires.
203 *
204 * Format writes the one form an HTTP sender may, IMF-fixdate: kRFC1123Format's, always in GMT, whatever the DateTime's
205 * time zone (an unknown one taken as UTC, as AsUTC () does):
206 * Sun, 06 Nov 1994 08:49:37 GMT
207 *
208 * Parse takes anything kRFC1123Format reads, and HTTP's two obsolete forms, as an HTTP recipient must - both in GMT, with
209 * RFC 850's 2-digit year read as kRFC1123Format reads one:
210 * Sunday, 06-Nov-94 08:49:37 GMT (RFC 850)
211 * Sun Nov 6 08:49:37 1994 (asctime)
212 *
213 * See https://www.rfc-editor.org/rfc/rfc9110#section-5.6.7
214 */
215 static constexpr auto kHTTPDateFormat = LocaleIndependentFormat::eHTTPDate;
216
217 public:
218 /**
219 * \note https://en.cppreference.com/w/cpp/locale/time_get/get and https://en.cppreference.com/w/cpp/locale/time_put/put
220 */
221 static constexpr string_view kLocaleStandardFormat = "%c"sv;
222
223 public:
224 /**
225 * \note https://en.cppreference.com/w/cpp/locale/time_get/get and https://en.cppreference.com/w/cpp/locale/time_put/put
226 */
227 static constexpr string_view kLocaleStandardAlternateFormat = "%Ec"sv;
228
229 public:
230 /**
231 * Format patterns defined in:
232 * https://en.cppreference.com/w/cpp/locale/time_get/get
233 * https://en.cppreference.com/w/cpp/locale/time_put/put
234 *
235 * The default is locale-dependent DATE format followed by space and locale-dependent TIME.
236 *
237 * Note - there were two good choices here (all locale dependent):
238 * o %c - writes standard date and time string
239 * o %x %X - writes localized date representation / writes localized time representation
240 *
241 * \note VERY UNRELIABLE for year handling - @see Time::StdCPctxTraits
242 */
243 static constexpr string_view kShortLocaleFormatPattern = "%x %X"sv;
244
245 public:
246 /**
247 * Format patterns defined in:
248 * https://en.cppreference.com/w/cpp/locale/time_get/get
249 * https://en.cppreference.com/w/cpp/locale/time_put/put
250 *
251 * Default formats used by Date::Parse () to parse DateTime strings. The first of these - %c and %x %X, are
252 * locale-specific time formats.
253 */
254 static const Traversal::Iterable<String> kDefaultParseFormats;
255
256 public:
257 class FormatException;
258
259 public:
260 /**
261 * Parse will throw if the argument cannot be parsed as a valid DateTime.
262 *
263 * \note If the timezone cannot be identified in the source string, it will be returned as 'unknown'.
264 *
265 * \note for Parse () overloads which don't specify a locale, the default locale (locale{}) is used (unless the formatPattern is locale independent)
266 *
267 * For formatPattern, see https://en.cppreference.com/w/cpp/locale/time_get/get, and defaults to kLocaleStandardFormat
268 *
269 * \note A locale has no associated timezone (despite somewhat confusing documentation relating to this).
270 * @see https://stackoverflow.com/questions/52839648/does-a-c-locale-have-an-associated-timezone-and-if-yes-how-do-you-access-it
271 *
272 * \note an empty string produces BadFormat exception (whereas before 2.1d11 it produced an empty DateTime object (DateTime {}).
273 *
274 * \note @todo - https://github.com/SophistSolutions/Stroika/issues/806 (STK-671) - DateTime::Format and Parse () incorrectly handle the format strings %z and %Z (sort of)
275 *
276 * \note Handling of extra junk (including whitespace) at the start or end of the date MAY or MAY not be tollerated, depending on the
277 * format parameters given. But generally this API is struct, and will treat junk at the start or end of the date as a format
278 * exception. To get looser interpretation, use ParseQuietly().
279 */
280 static DateTime Parse (const String& rep, LocaleIndependentFormat format);
281 static DateTime Parse (const String& rep, const locale& l = locale{});
282 static DateTime Parse (const String& rep, const locale& l, const String& formatPattern);
283 static DateTime Parse (const String& rep, const locale& l, const Traversal::Iterable<String>& formatPatterns);
284 static DateTime Parse (const String& rep, const String& formatPattern);
285
286 public:
287 /**
288 * \brief like Parse(), but returns nullopt on parse error, not throwing exception.
289 *
290 * if locale is missing, and formatPattern is not locale independent, the current locale (locale{}) is used.
291 * if rep is empty, this will return nullopt
292 *
293 * if argument consumedCharacters != nullptr, and if ParseQuietly returns has_value (), then *consumedCharacters will contain
294 * the number of characters consumed from rep.
295 */
296 static optional<DateTime> ParseQuietly (const String& rep, LocaleIndependentFormat format, size_t* consumedCharacters = nullptr);
297 static optional<DateTime> ParseQuietly (const String& rep, const String& formatPattern, size_t* consumedCharacters = nullptr);
298 static optional<DateTime> ParseQuietly (const String& rep, const locale& l, const String& formatPattern, size_t* consumedCharacters = nullptr);
299 static optional<DateTime> ParseQuietly (const String& rep, const locale& l, const Traversal::Iterable<String>& formatPatterns = kDefaultParseFormats,
300 size_t* consumedCharacters = nullptr);
301
302 private:
303 // this requires rep!= ""
304 static optional<DateTime> ParseQuietly_ (const wstring& rep, const time_get<wchar_t>& tmget, const String& formatPattern, size_t* consumedCharacters);
305
306 public:
307 /**
308 * Return the current DateTime (in LocalTime)
309 */
310 static DateTime Now () noexcept;
311
312 public:
313 /**
314 * Return the current DateTime in UTC
315 *
316 * \brief equivilent to Now().AsUTC ()
317 */
318 static DateTime NowUTC () noexcept;
319
320 public:
321 /**
322 * Return the current Date (in LocalTime - local timezone)
323 */
324 static Date GetToday () noexcept;
325
326 public:
327 /**
328 * DateTime::kMin is the first date this DateTime class supports representing.
329 */
330 static const DateTime kMin; // defined constexpr
331
332 public:
333 /**
334 * DateTime::kMax is the last date this DateTime class supports representing.
335 */
336 static const DateTime kMax; // defined constexpr
337
338 public:
339 /**
340 */
341 nonvirtual constexpr optional<Timezone> GetTimezone () const noexcept;
342
343 public:
344 /**
345 * Creates a new DateTime object known to be in localtime. If this DateTime timezone is unknown, then the
346 * the datetime will be just assumed to have been in localtime.
347 *
348 * \post result.GetTimezone () == Timezone::LocalTime ()
349 */
350 nonvirtual DateTime AsLocalTime () const;
351
352 public:
353 /**
354 * Creates a new DateTime object known to be in UTC. If this DateTime timezone is unknown, then the
355 * the datetime will be just assumed to have been in UTC.
356 *
357 * Either way, this will always produce a result in the timezone UTC.
358 *
359 * \post result.GetTimezone () == Timezone::UTC ()
360 *
361 * @see AsTimezone ()
362 */
363 nonvirtual DateTime AsUTC () const;
364
365 public:
366 /**
367 * Return this DateTime converted to the given timezone. If the current timezone is unknown, this just
368 * sets the timezone. But if it is known, the time value is adjusted to correspond for the given timezone.
369 *
370 * Either way, this will always produce a result in the timezone tz.
371 *
372 * \post result.GetTimezone () == Timezone::UTC ()
373 */
374 nonvirtual DateTime AsTimezone (Timezone tz) const;
375
376 public:
377 /**
378 * return true if known true, and false if known false, and {} otherwise.
379 */
380 nonvirtual optional<bool> IsDaylightSavingsTime () const;
381
382 public:
383 /**
384 * \brief NonStandardPrintFormat is a representation which a datetime can be transformed into
385 *
386 * eCurrentLocale_WithZerosStripped
387 * eCurrentLocale_WithZerosStripped is eCurrentLocale, but with many cases of trailing zero's,
388 * and sometimes leading zeros, stripped, so for example, 01:03:05 PM will become 1:03:05 PM,
389 * and 04:06:00 PM will become 4:06 PM.
390 *
391 * This also may normalize extra space characters to a single space.
392 */
393 enum class NonStandardPrintFormat : uint8_t {
394 eCurrentLocale_WithZerosStripped,
395
396 eDEFAULT = eCurrentLocale_WithZerosStripped,
397
398 Stroika_Define_Enum_Bounds (eCurrentLocale_WithZerosStripped, eCurrentLocale_WithZerosStripped)
399 };
400
401 public:
402 static constexpr NonStandardPrintFormat eCurrentLocale_WithZerosStripped = NonStandardPrintFormat::eCurrentLocale_WithZerosStripped;
403
404 public:
405 /**
406 * For formatPattern, see http://en.cppreference.com/w/cpp/locale/time_put/put and defaults to kLocaleStandardFormat
407 * If only formatPattern specified, and no locale, use default (global) locale.
408 *
409 * \note A locale has no associated timezone (despite somewhat confusing documentation relating to this).
410 * @see https://stackoverflow.com/questions/52839648/does-a-c-locale-have-an-associated-timezone-and-if-yes-how-do-you-access-it
411 *
412 * \note for APIs with the locale not specified, its assumed to be the default (locale{}), except (perhaps) where the formatString is locale-independent.
413 *
414 * \note the default for Format() with no arguments is to use the default locale, but with the eCurrentLocale_WithZerosStripped flag set.
415 *
416 * \note @todo - https://github.com/SophistSolutions/Stroika/issues/806 (STK-671) - DateTime::Format and Parse () incorrectly handle the format strings %z and %Z (sort of)
417 *
418 * \par Example Usage
419 * \code
420 * // note example assumes current locale is 'C'
421 * DateTime d{Date{January / 4 / 2017y}, TimeOfDay{3, 3, 0}};
422 * EXPECT_EQ (d.Format (locale{}).NormalizeSpace (), "Wed Jan 4 03:03:00 2017"); // locale{} on windows adds extra space...
423 * EXPECT_EQ (d.Format (DateTime::eCurrentLocale_WithZerosStripped), d.Format ());
424 * EXPECT_EQ (d.Format (DateTime::eCurrentLocale_WithZerosStripped), "Wed Jan 4 3:03 2017"); // zeros stripped
425 * \endcode
426 *
427 * \par Example Usage
428 * \code
429 * DateTime d{Date (Year{1903}, April, DayOfMonth{5})};
430 * EXPECT_EQ (d.Format (DateTime::eCurrentLocale_WithZerosStripped), "4/5/03");
431 * \endcode
432 */
433 nonvirtual String Format (NonStandardPrintFormat pf = NonStandardPrintFormat::eDEFAULT) const;
434 nonvirtual String Format (LocaleIndependentFormat format) const;
435 nonvirtual String Format (const locale& l) const;
436 nonvirtual String Format (const locale& l, const String& formatPattern) const;
437 nonvirtual String Format (const String& formatPattern) const;
438
439 public:
440 /**
441 * @see Characters::ToString ()
442 */
443 nonvirtual String ToString () const;
444
445 public:
446 /**
447 * Returns number of days since this point - relative to NOW. Never less than zero.
448 */
449 nonvirtual Date::JulianDayNumber DaysSince () const;
450
451 public:
452 /**
453 * Defined for
454 * time_t
455 * struct tm
456 * struct timespec
457 * SYSTEMTIME (WINDOWS ONLY)
458 * Date
459 * String (Format () - so in the current locale, zeros stripped)
460 * chrono::time_point<CLOCK,DURATION> (satisfies Common::ITimePoint)
461 *
462 * NB: Intentionally NOT defined for TimeOfDay () - cuz it wouldn't make sense. A DateTime IS a Date, but its not a TimeOfDay. Time of day just
463 * logically extends Date with extra (TOD) information.
464 *
465 * \note As<time_t> Returns seconds since midnight 1970 UTC (UNIX 'Epoch time')
466 *
467 * \note As<struct tm> Returns a struct tm record without paying attention to timezone (in whatever timezone *this is) - but will throw if the date is before 1900
468 *
469 * \note As<struct timespec> inherits the limitations (UTC since 1970 - since it uses time_t internally)
470 *
471 * \note As<Date> ignores timezone, and returns the date object in this DateTime's timezone
472 *
473 * \note As<> will throw range_error() if it cannot perform the required conversions and produce a valid value.
474 */
475 template <typename T>
476 nonvirtual T As () const
477 // new bug define for clang/XCode? cannot do requires and tmeplate specialize?
478#if !qCompilerAndStdLib_template_requires_doesnt_work_with_specialization_Buggy
479 requires (Common::IAnyOf<T, time_t, struct tm, struct timespec, Date, Characters::String
480#if qStroika_Platform_Windows
481 ,
482 SYSTEMTIME
483#endif
484 > or
485 Common::ITimePoint<T>)
486#endif
487 ;
488
489 public:
490 /**
491 * returns the Date part of the DateTime object (in this datetime's timezone).
492 */
493 nonvirtual constexpr Date GetDate () const noexcept;
494
495 public:
496 /**
497 * returns the TimeOfDay part of the DateTime object (in this datetime's timezone). It returns 'missing' if there
498 * is no TimeOfDay associated with this DateTime object.
499 *
500 * \post (not result.has_value () or not result->empty ())
501 *
502 * \note Operations on a DateTime which require a 'TimeOfDay' will implicitly create a '0' time of day. So for example,
503 * if you AddSeconds (1) to a DateTime with a missing TimeOfDay it creates a TimeOfDay with value 12:00:01.
504 */
505 nonvirtual constexpr optional<TimeOfDay> GetTimeOfDay () const noexcept;
506
507 public:
508 /**
509 * Add the given amount of time to construct a new DateTime object. This function does NOT change
510 * the timezone value nor adjust for timezone issues.
511 *
512 * Add doesn't modify *this.
513 *
514 * \note This function will throw range_error() if it cannot perform the required conversions and produce a valid value.
515 */
516 nonvirtual DateTime Add (const Duration& d) const;
517
518 public:
519 /**
520 * Add the given number of days to construct a new DateTime object. This function does NOT change
521 * the timezone value nor adjust for timezone issues.
522 *
523 * AddDays doesn't modify this.
524 *
525 * \note This function will throw range_error() if it cannot perform the required conversions and produce a valid value.
526 */
527 nonvirtual DateTime AddDays (int days) const;
528
529 public:
530 /**
531 * Add the given number of seconds to construct a new DateTime object. This function does NOT change
532 * the timezone value nor adjust for timezone issues.
533 *
534 * AddSeconds doesn't modify this.
535 *
536 * \note This function will throw range_error() if it cannot perform the required conversions and produce a valid value.
537 *
538 * \note Operations on a DateTime which require a 'TimeOfDay' will implicitly create a '0' time of day. So for example,
539 * if you AddSeconds (1) to a DateTime with a missing TimeOfDay it creates a TimeOfDay with value 12:00:01.
540 */
541 nonvirtual DateTime AddSeconds (int64_t seconds) const;
542
543 public:
544 /**
545 * Returns the difference between the two DateTime records. This can then be easily converted to
546 * seconds, or days, or whatever.
547 *
548 * If the DateTime objects have different timezones, they are converted to a common timezone before the difference
549 * is computed.
550 */
551 nonvirtual Duration Difference (const DateTime& rhs) const;
552
553 public:
554 /**
555 */
556 nonvirtual strong_ordering operator<=> (const DateTime& rhs) const;
557
558 public:
559 /**
560 */
561 nonvirtual bool operator== (const DateTime& rhs) const;
562
563 public:
564 struct ThreeWayComparer;
565
566 private:
567 template <typename T>
568 nonvirtual T As_Simple_ () const
569#if !qCompilerAndStdLib_template_requires_doesnt_work_with_specialization_Buggy
570 requires (same_as<T, time_t> or same_as<T, struct tm> or same_as<T, struct timespec> or same_as<T, Date> or same_as<T, Characters::String>)
571#endif
572 ;
573#if qStroika_Platform_Windows
574 nonvirtual SYSTEMTIME AsSYSTEMTIME_ () const;
575#endif
576 template <typename CLOCK_T, typename DURATION_T>
577 nonvirtual time_point<CLOCK_T, DURATION_T> As_TP_ () const;
578
579 private:
580 optional<Timezone> fTimezone_;
581 Date fDate_;
582 optional<TimeOfDay> fTimeOfDay_; // for now - still can be 'empty' - but API (as of v2.1d4) disallows passing in or getting out empty TimeOfDay
583
584 public:
585 [[deprecated ("Since Stroika v3.0d5 - just use AsLocalTime ().As<Time::TimePointSeconds> ()")]] Time::TimePointSeconds ToTickCount () const
586 {
587 return AsLocalTime ().As<Time::TimePointSeconds> ();
588 }
589 [[deprecated ("Since Stroika v3.0d5 - just construct the DateTime with the value of GetTickCount()")]] static DateTime
590 FromTickCount (Time::TimePointSeconds tickCount)
591 {
592 return DateTime{tickCount};
593 }
594 };
595
596 class DateTime::FormatException : public Execution::Exception<runtime_error> {
597 public:
598 FormatException ();
599
600 public:
601 /**
602 * just a predefined instance of the FormatException you can use, to avoid creating your own.
603 */
604 static const FormatException kThe;
605 };
606 inline const DateTime::FormatException DateTime::FormatException::kThe;
607
608 inline const Traversal::Iterable<String> DateTime::kDefaultParseFormats{
609 kLocaleStandardFormat,
610 kLocaleStandardAlternateFormat,
611 "%x %X"sv,
612 "%Ex %EX"sv,
613 "%Y-%b-%d %H:%M:%S"sv, // no obvious reference for this so maybe not a good idea
614 "%D%t%T"sv, // no obvious reference for this so maybe not a good idea
615 "%D%t%r"sv, // no obvious reference for this so maybe not a good idea
616 "%D%t%R"sv, // no obvious reference for this so maybe not a good idea
617 "%a %b %e %T %Y"sv, // no obvious reference for this so maybe not a good idea
618 };
619
620 /**
621 */
622 struct DateTime::ThreeWayComparer : Common::ComparisonRelationDeclarationBase<Common::ComparisonRelationType::eThreeWayCompare> {
623 /**
624 */
625 constexpr ThreeWayComparer (bool coerceToCommonTimezone = true);
626
627 /**
628 */
629 nonvirtual strong_ordering operator() (const DateTime& lhs, const DateTime& rhs) const;
630 bool fCoerceToCommonTimezone;
631 };
632
633 /**
634 * Syntactic sugar on Add()
635 */
636 DateTime operator+ (const DateTime& lhs, const Duration& rhs);
637
638 /**
639 * Syntactic sugar on Difference()
640 */
641 Duration operator- (const DateTime& lhs, const DateTime& rhs);
642
643 /**
644 * Syntactic sugar on Add() or Difference()
645 */
646 DateTime operator- (const DateTime&, const Duration& rhs);
647 Duration operator- (const DateTime&, const DateTime& rhs);
648
649}
650
651namespace Stroika::Foundation::Math {
652 bool NearlyEquals (Time::DateTime l, Time::DateTime r);
653 bool NearlyEquals (Time::DateTime l, Time::DateTime r, Time::DurationSeconds epsilon);
654}
655
656namespace Stroika::Foundation::Time {
657 class Duration;
658}
659
661
662 template <>
663 struct DefaultOpenness<Time::DateTime> : ExplicitOpenness<Openness::eClosed, Openness::eClosed> {};
664 template <>
665 struct DefaultDifferenceTypes<Time::DateTime> : ExplicitDifferenceTypes<Time::Duration> {};
666 /**
667 * \note Default<Time::DateTime> properties (kLowerBound/kUpperBound) can only be used after static initialization, and before
668 * static de-initialization.
669 */
670 template <>
671 struct Default<Time::DateTime> : ExplicitOpennessAndDifferenceType<Time::DateTime> {
672 static const Time::DateTime kLowerBound;
673 static const Time::DateTime kUpperBound;
674
675 // @todo - not important (just used for PinValue I think)
676 // static Time::DateTime GetNext (Time::DateTime n);
677 // static Time::DateTime GetPrevious (Time::DateTime n);
678 };
679
680}
681
682/*
683 ********************************************************************************
684 ***************************** Implementation Details ***************************
685 ********************************************************************************
686 */
687#include "DateTime.inl"
688
689#endif /*_Stroika_Foundation_Time_DateTime_h_*/
#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM)
static const optional< Timezone > kUnknown
Definition Timezone.h:150
static const Timezone kUTC
Definition Timezone.h:141
static const Timezone kLocalTime
Definition Timezone.h:144
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})