Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
TimeOfDay.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 <iomanip>
9#include <sstream>
10
13#include "Stroika/Foundation/Containers/Sequence.h"
16#include "Stroika/Foundation/Execution/Throw.h"
17#include "Stroika/Foundation/Linguistics/Words.h"
18
19#if qStroika_Foundation_Common_Platform_Windows
20#include "Stroika/Foundation/Characters/Platform/Windows/SmartBSTR.h"
21#include "Stroika/Foundation/Execution/Platform/Windows/HRESULTErrorException.h"
22#endif
23
24#include "TimeOfDay.h"
25
26using namespace Stroika::Foundation;
28using namespace Stroika::Foundation::Execution;
29using namespace Stroika::Foundation::Memory;
30using namespace Stroika::Foundation::Time;
31
33
34using namespace Time;
35
36// Comment this in to turn on aggressive noisy DbgTrace in this module
37// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
38
39#if qStroika_Foundation_Common_Platform_Windows
40namespace {
41 TimeOfDay mkTimeOfDay_ (const ::SYSTEMTIME& sysTime)
42 {
43 WORD hour = max (sysTime.wHour, static_cast<WORD> (0));
44 hour = min (hour, static_cast<WORD> (23));
45 WORD minute = max (sysTime.wMinute, static_cast<WORD> (0));
46 minute = min (minute, static_cast<WORD> (59));
47 WORD secs = max (sysTime.wSecond, static_cast<WORD> (0));
48 secs = min (secs, static_cast<WORD> (59));
49 return TimeOfDay{hour, minute, secs};
50 }
51}
52#endif
53
54namespace {
55 constexpr bool kLocaleIndependent_iso8601_PerformanceOptimization_ = true; // TBD if this is actual performance help or not
56 optional<TimeOfDay> LocaleIndependent_Parse_iso8601_ (const String& rep)
57 {
58 int hour = 0;
59 int minute = 0;
60 int secs = 0;
61 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
62 if (::swscanf (rep.As<wstring> ().c_str (), L"%d:%d:%d", &hour, &minute, &secs) >= 2) {
63 hour = std::max (hour, 0);
64 hour = std::min (hour, 23);
65 minute = std::max (minute, 0);
66 minute = std::min (minute, 59);
67 secs = std::max (secs, 0);
68 secs = std::min (secs, 59);
69 return TimeOfDay{static_cast<unsigned> (hour), static_cast<unsigned> (minute), static_cast<unsigned> (secs)};
70 }
71 DISABLE_COMPILER_MSC_WARNING_END (4996)
72 return nullopt;
73 }
74 String LocaleIndependent_Format_iso8601_ (uint32_t timeInSeconds)
75 {
76 uint32_t hour = timeInSeconds / (60 * 60);
77 uint32_t minutes = (timeInSeconds - hour * 60 * 60) / 60;
78 uint32_t secs = timeInSeconds - hour * 60 * 60 - minutes * 60;
79 Assert (hour >= 0 and hour < 24);
80 Assert (minutes >= 0 and minutes < 60);
81 Assert (secs >= 0 and secs < 60);
82 return "{:02}:{:02}:{:02}"_f(hour, minutes, secs);
83 }
84}
85
86/*
87 ********************************************************************************
88 *********************************** TimeOfDay **********************************
89 ********************************************************************************
90 */
91TimeOfDay::FormatException::FormatException ()
92 : Execution::RuntimeErrorException<>{"Invalid Time Format"sv}
93{
94}
95
96/*
97 ********************************************************************************
98 *********************************** TimeOfDay **********************************
99 ********************************************************************************
100 */
101#if qStroika_Foundation_Common_Platform_Windows
102namespace {
103 TimeOfDay Parse_ (const String& rep, LCID lcid)
104 {
105 using namespace Execution::Platform::Windows;
106 if (rep.empty ()) {
107 Execution::Throw (TimeOfDay::FormatException::kThe);
108 }
109 ::DATE d{};
110 try {
111 ThrowIfErrorHRESULT (::VarDateFromStr (Characters::Platform::Windows::SmartBSTR{rep.As<wstring> ().c_str ()}, lcid, VAR_TIMEVALUEONLY, &d));
112 }
113 catch (...) {
114 // Apparently military time (e.g. 1300 hours - where colon missing) - is rejected as mal-formed.
115 // Detect that - and try to interpret it appropriately.
116 String newRep = rep;
117 if (newRep.length () == 4 and newRep[0].IsDigit () and newRep[1].IsDigit () and newRep[2].IsDigit () and newRep[3].IsDigit ()) {
118 newRep = newRep.substr (0, 2) + ":"sv + newRep.substr (2, 2);
121 ::VarDateFromStr (Characters::Platform::Windows::SmartBSTR{get<0> (newRep.c_str (&buf))}, lcid, VAR_TIMEVALUEONLY, &d));
122 }
123 else {
124 Execution::Throw (TimeOfDay::FormatException::kThe);
125 }
126 }
127 // SHOULD CHECK ERR RESULT (not sure if/when this can fail - so do a Verify for now)
128 ::SYSTEMTIME sysTime{};
129 Verify (::VariantTimeToSystemTime (d, &sysTime));
130 return mkTimeOfDay_ (sysTime);
131 }
132}
133#endif
134
135TimeOfDay::TimeOfDay (unsigned int hour, unsigned int minute, unsigned int seconds, DataExchange::ValidationStrategy validationStrategy)
136 : TimeOfDay{
138 0
139#else
140 hour, minute, seconds
141#endif
142 }
143{
144 // Subtle - but we can let the base constructor run on the unvalidated data in NO-DEBUG mode, cuz it will just compute a bogus
145 // value that will be ignored because of the below exception
146 //
147 // But for the qStroika_Foundation_Debug_AssertionsChecked case, we have to initialize with a valid value and only assign if it passes muster
148 if (hour >= 24 or minute >= 60 or seconds > 60) {
149 if (validationStrategy == DataExchange::ValidationStrategy::eThrow) {
150 Execution::Throw (FormatException::kThe);
151 }
152 else {
153 Require (false);
154 }
155 }
156#if qStroika_Foundation_Debug_AssertionsChecked
157 *this = TimeOfDay{hour, minute, seconds};
158#endif
159}
160TimeOfDay::TimeOfDay (uint32_t t, DataExchange::ValidationStrategy validationStrategy)
161 : fTime_{t}
162{
163 switch (validationStrategy) {
164 case DataExchange::ValidationStrategy::eAssertion:
165 Require (t < kMaxSecondsPerDay);
166 break;
167 case DataExchange::ValidationStrategy::eThrow:
168 if (not(t < kMaxSecondsPerDay)) {
169 Execution::Throw (FormatException::kThe);
170 }
171 break;
172 }
173 Assert (fTime_ < kMaxSecondsPerDay);
174}
175
176TimeOfDay TimeOfDay::Parse (const String& rep, const locale& l)
177{
178#if USE_NOISY_TRACE_IN_THIS_MODULE_
179 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs (L"TimeOfDay::Parse", L"rep='%s', l='%s'", rep.c_str (),
180 String::FromNarrowSDKString (l.name ()).c_str ())};
181#endif
182 if (rep.empty ()) {
183 Execution::Throw (FormatException::kThe);
184 }
185 auto result = Parse (rep, l, kDefaultParseFormats);
186#if USE_NOISY_TRACE_IN_THIS_MODULE_
187 DbgTrace ("returning {}"_f, result);
188#endif
189 return result;
190}
191
192TimeOfDay TimeOfDay::Parse (const String& rep, const locale& l, const Traversal::Iterable<String>& formatPatterns)
193{
194#if USE_NOISY_TRACE_IN_THIS_MODULE_
195 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("TimeOfDay::Parse", "rep='{}', l='{}', formatPatterns={}"_f,
196 rep, String::FromNarrowSDKString (l.name ()), formatPatterns)};
197#endif
198 if (rep.empty ()) {
199 Execution::Throw (FormatException::kThe);
200 }
201 wstring wRep = rep.As<wstring> ();
202 const time_get<wchar_t>& tmget = use_facet<time_get<wchar_t>> (l);
203 for (const auto& formatPattern : formatPatterns) {
204 if (auto o = ParseQuietly_ (wRep, tmget, formatPattern)) {
205#if USE_NOISY_TRACE_IN_THIS_MODULE_
206 DbgTrace ("returning {}"_f, *o);
207#endif
208 return *o;
209 }
210 }
211 Execution::Throw (FormatException::kThe);
212}
213
214TimeOfDay TimeOfDay::Parse (const String& rep, const String& formatPattern)
215{
216#if USE_NOISY_TRACE_IN_THIS_MODULE_
217 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs (L"TimeOfDay::Parse", L"rep=%s", rep.c_str ())};
218#endif
219 if (rep.empty ()) {
220 Execution::Throw (FormatException::kThe);
221 }
222 if (auto o = ParseQuietly_ (rep.As<wstring> (), formatPattern)) {
223#if USE_NOISY_TRACE_IN_THIS_MODULE_
224 DbgTrace ("returning {}"_f, *o);
225#endif
226 return *o;
227 }
228 Execution::Throw (FormatException::kThe);
229}
230
231TimeOfDay TimeOfDay::Parse (const String& rep, const locale& l, const String& formatPattern)
232{
233#if USE_NOISY_TRACE_IN_THIS_MODULE_
234 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs (L"TimeOfDay::Parse", L"rep='%s', l='%s'", rep.c_str (),
235 String::FromNarrowSDKString (l.name ()).c_str ())};
236#endif
237 if (rep.empty ()) {
238 Execution::Throw (FormatException::kThe);
239 }
240 if (auto o = ParseQuietly_ (rep.As<wstring> (), use_facet<time_get<wchar_t>> (l), formatPattern)) {
241#if USE_NOISY_TRACE_IN_THIS_MODULE_
242 DbgTrace ("returning {}"_f, *o);
243#endif
244 return *o;
245 }
246 Execution::Throw (FormatException::kThe);
247}
248
249optional<TimeOfDay> TimeOfDay::ParseQuietly (const String& rep, const String& formatPattern)
250{
251 if (rep.empty ()) {
252 return nullopt;
253 }
254 return ParseQuietly_ (rep.As<wstring> (), formatPattern);
255}
256
257optional<TimeOfDay> TimeOfDay::ParseQuietly (const String& rep, const locale& l, const String& formatPattern)
258{
259 if (rep.empty ()) {
260 return nullopt;
261 }
262 return ParseQuietly_ (rep.As<wstring> (), use_facet<time_get<wchar_t>> (l), formatPattern);
263}
264
265optional<TimeOfDay> TimeOfDay::ParseQuietly_ (const wstring& rep, const String& formatPattern)
266{
267 if (kLocaleIndependent_iso8601_PerformanceOptimization_ and formatPattern == kISO8601Format) {
268 return LocaleIndependent_Parse_iso8601_ (rep);
269 }
270 return ParseQuietly_ (rep, use_facet<time_get<wchar_t>> (locale{}), formatPattern);
271}
272
273optional<TimeOfDay> TimeOfDay::ParseQuietly_ (const wstring& rep, const time_get<wchar_t>& tmget, const String& formatPattern)
274{
275 ios::iostate errState = ios::goodbit;
276 tm when{};
277 wistringstream iss{rep};
278 istreambuf_iterator<wchar_t> itbegin{iss}; // beginning of iss
279 istreambuf_iterator<wchar_t> itend; // end-of-stream
280 wstring wsFormatPattern = formatPattern.As<wstring> ();
281 (void)tmget.get (itbegin, itend, iss, errState, &when, wsFormatPattern.c_str (), wsFormatPattern.c_str () + wsFormatPattern.length ());
282 if ((errState & ios::badbit) or (errState & ios::failbit)) [[unlikely]] {
283#if qCompilerAndStdLib_locale_get_time_needsStrptime_sometimes_Buggy
284 errState = (::strptime (String{rep}.AsNarrowSDKString ().c_str (), formatPattern.AsNarrowSDKString ().c_str (), &when) == nullptr)
285 ? ios::failbit
286 : ios::goodbit;
287#endif
288 }
289 if ((errState & ios::badbit) or (errState & ios::failbit)) [[unlikely]] {
290 return nullopt;
291 }
292
293 Ensure (0 <= when.tm_hour and when.tm_hour <= 23);
294 Ensure (0 <= when.tm_min and when.tm_min <= 59);
295 Ensure (0 <= when.tm_sec and when.tm_sec <= 59);
296 auto result = TimeOfDay{static_cast<unsigned> (when.tm_hour), static_cast<unsigned> (when.tm_min), static_cast<unsigned> (when.tm_sec)};
297#if USE_NOISY_TRACE_IN_THIS_MODULE_
298 DbgTrace ("returning {}"_f, result);
299#endif
300 return result;
301}
302
304{
305 switch (pf) {
306 case eCurrentLocale_WithZerosStripped: {
307 String tmp = Format (locale{});
308 /*
309 * This logic probably needs to be locale-specific, but this is good enuf for now...
310 */
311 optional<size_t> i;
312 while ((i = tmp.RFind (":00"sv))) {
313 // if its a TRAILING :00 - lose it...
314 bool trailing = false;
315 if (*i + 3 == tmp.size ()) {
316 trailing = true;
317 }
318 else if (*i + 3 < tmp.size () and tmp[*i + 3] == ' ') {
319 trailing = true;
320 }
321 if (trailing) {
322 tmp = tmp.SubString (0, *i) + tmp.SubString (*i + 3);
323 }
324 else {
325 break;
326 }
327 }
328 // Next lose prefxing 0, as in 01:04
329 if (not tmp.empty () and tmp[0] == '0') {
330 tmp = tmp.substr (1);
331 }
332 return tmp;
333 }
334 default: {
336 return String{};
337 }
338 }
339}
340
341String TimeOfDay::Format (const locale& l) const
342{
343 return Format (l, kLocaleStandardFormat);
344}
345
346String TimeOfDay::Format (const String& formatPattern) const
347{
348 if (kLocaleIndependent_iso8601_PerformanceOptimization_ and formatPattern == kISO8601Format) {
349 return LocaleIndependent_Format_iso8601_ (fTime_);
350 }
351 return Format (locale{}, formatPattern);
352}
353
354String TimeOfDay::Format (const locale& l, const String& formatPattern) const
355{
356 // http://new.cplusplus.com/reference/std/locale/time_put/put/
357 // http://en.cppreference.com/w/cpp/locale/time_put/put
358 tm when{};
359 when.tm_hour = GetHours ();
360 when.tm_min = GetMinutes ();
361 when.tm_sec = GetSeconds ();
362 const time_put<wchar_t>& tmput = use_facet<time_put<wchar_t>> (l);
363 wstring wsFormatPattern = formatPattern.As<wstring> ();
364 wostringstream oss;
365 tmput.put (oss, oss, ' ', &when, wsFormatPattern.c_str (), wsFormatPattern.c_str () + wsFormatPattern.length ());
366 return oss.str ();
367}
368
369void TimeOfDay::ClearSecondsField ()
370{
371 Assert (fTime_ < kMaxSecondsPerDay);
372 int hour = fTime_ / (60 * 60);
373 int minutes = (fTime_ - hour * 60 * 60) / 60;
374 int secs = fTime_ - hour * 60 * 60 - minutes * 60;
375 Assert (hour >= 0 and hour < 24);
376 Assert (minutes >= 0 and minutes < 60);
377 Assert (secs >= 0 and secs < 60);
378 fTime_ -= secs;
379 Assert (fTime_ < kMaxSecondsPerDay);
380}
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define AssertNotReached()
Definition Assertions.h:355
#define Verify(c)
Definition Assertions.h:419
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
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 tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
Definition String.inl:1049
nonvirtual string AsNarrowSDKString() const
Definition String.inl:830
static String FromNarrowSDKString(const char *from)
Definition String.inl:470
nonvirtual size_t size() const noexcept
Definition String.inl:534
nonvirtual String SubString(SZ from) const
nonvirtual optional< size_t > RFind(Character c) const noexcept
Definition String.cpp:1011
nonvirtual String substr(size_t from, size_t count=npos) const
Definition String.inl:1086
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
static constexpr uint32_t kMaxSecondsPerDay
Definition TimeOfDay.h:68
static constexpr string_view kISO8601Format
Definition TimeOfDay.h:107
nonvirtual constexpr uint8_t GetMinutes() const
Definition TimeOfDay.inl:36
static constexpr string_view kLocaleStandardFormat
Definition TimeOfDay.h:113
static const Traversal::Iterable< String > kDefaultParseFormats
Definition TimeOfDay.h:128
nonvirtual String Format(NonStandardPrintFormat pf=NonStandardPrintFormat::eDEFAULT) const
nonvirtual constexpr uint8_t GetHours() const
Definition TimeOfDay.inl:30
nonvirtual constexpr uint8_t GetSeconds() const
Definition TimeOfDay.inl:44
static TimeOfDay Parse(const String &rep, const locale &l=locale{})
static optional< TimeOfDay > ParseQuietly(const String &rep, const String &formatPattern)
like Parse(), but returns nullopt on parse error, not throwing exception. if locale is missing,...
constexpr TimeOfDay(TimeOfDay &&src) noexcept=default
NonStandardPrintFormat
NonStandardPrintFormat is a representation which a TimeOfDay can be transformed into.
Definition TimeOfDay.h:224
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