Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
Duration.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_Duration_h_
5#define _Stroika_Foundation_Time_Duration_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <chrono>
10#include <climits>
11#include <string>
12
13#if qStroika_Foundation_Common_Platform_POSIX
14#include <sys/time.h>
15#elif qStroika_Foundation_Common_Platform_Windows
16#include <Winsock2.h>
17#endif
18
19#include "Stroika/Foundation/Characters/FloatConversion.h"
21#include "Stroika/Foundation/Common/Common.h"
22#include "Stroika/Foundation/Execution/Exceptions.h"
26
27/**
28 * \file
29 *
30 * \note Code-Status: <a href="Code-Status.md">Beta</a>
31 *
32 * TODO:
33 * @todo PT3,4S and PT3.4S both must be interpreted as 3.4 seconds. I think we can generate
34 * either, but parser must accept either. Right now we use atof(), and I'm not sure that
35 * handles either form of decimal separator! Add to regression tests, and make sure
36 * it works.
37 *
38 * @todo Do better job rounding. Right now we round (?)properly for seconds, but nothing else.
39 *
40 * @todo Consider using long double for InternalNumericFormatType_;
41 *
42 * @todo Consider adding 'significantFigures' property to PrettyPrintInfo. Think about significantFigures support/design of
43 * boost (maybe use bignum or rational?). Probably no - but document clearly why or why not.
44 *
45 * @todo Add PRECISION support to PrettyPrintInfo argument to PrettyPrint () function.
46 * o Number of seconds. Anything less than that number is truncated.
47 * o So .001 'precision' means show 3.44444 as 3.444 and 60 means show 67 seconds as 'one minute'
48 * o Maybe add option so can show > or < as in < one minute or > one minute for being passed sentinel values?
49 *
50 */
51
52namespace Stroika::Foundation::Time {
53
54 /**
55 * \brief Duration is a chrono::duration<double> (=\see DurationSeconds) - but adding ISO-8601 string representation support
56 *
57 * (basic) support for ISO 8601 Durations
58 * http://en.wikipedia.org/wiki/ISO_8601#Durations
59 *
60 * Note: according to glibc docs - year is always 365 days, month always 30 days, etc, as far
61 * as these duration objects go - at least for conversion to/from time_t. Seek a better
62 * reference for this claim!
63 *
64 * Note also - there are two iso 8601 duration formats - one date like (YYYY-MM....) and this
65 * one P...T...3S). As far as I know - XML always uses the later. For now - this implementation
66 * only supports the later.
67 *
68 * @see http://bugzilla/show_bug.cgi?id=468
69 *
70 * This class is roughly equivalent to the .Net Timespan class, and also might be called
71 * "time period", or "timespan".
72 *
73 * Note that a Duration may be negative.
74 *
75 * It is best to logically think of Duration as a number of seconds (perhaps lossily)
76 * since for comparisons that's how things are normalized. #days etc are dumbed down
77 * to number of seconds for comparison sakes.
78 *
79 * \note constexpr not really working (though declared) - see @todo above
80 *
81 * \note Reason Duration functionality not merged into DurationSeconds (why two separate types):
82 * Duration maintains the ability to recover the original string (in case conversion to float loses
83 * precision) - so Duration{"PT2.3M"} always returns that - As<String> () value, even if its converted
84 * to something else; DurationSeconds is more lightweight (and constexpr friendly).
85 *
86 * \note Design Note - why no c_str () method
87 * In order to implement c_str () - we would need to return an internal pointer. That would
88 * constrain the internal implementation, and would need careful definition of lifetime. The
89 * simplest way around this is to have the caller pass something in, or to return something
90 * whose lifetime is controlled (an object). So now - just call As<String> ().c_str () or
91 * As<wstring> ().c_str ()
92 *
93 * \note <a href="Design-Overview.md#Comparisons">Comparisons</a>:
94 * o static_assert (totally_ordered<Duration>);
95 */
96 class Duration final : public chrono::duration<double> {
97 private:
98 using inherited = chrono::duration<double>;
99
100 public:
101 /**
102 * The character set of the std::string CTOR is expected to be all ascii, or the code throws FormatException
103 *
104 * Throws (FormatException) if bad format
105 *
106 * \note constexpr not really working (though declared) - see @todo above
107 *
108 * \note for numeric overloads (includes chrono::duration), require (not isnan (src)) - but allow isinf()
109 */
110 constexpr Duration ();
111 Duration (Duration&& src) noexcept;
112 Duration (const Duration& src);
113 template <Characters::IConvertibleToString STRINGISH_T>
114 explicit Duration (STRINGISH_T&& durationStr);
115 constexpr Duration (int durationInSeconds);
116 constexpr Duration (long durationInSeconds);
117 constexpr Duration (long long durationInSeconds);
118 constexpr Duration (float durationInSeconds);
119 constexpr Duration (double durationInSeconds);
120 constexpr Duration (long double durationInSeconds) noexcept;
121 template <typename DURATION_REP, typename DURATION_PERIOD>
122 constexpr Duration (const chrono::duration<DURATION_REP, DURATION_PERIOD>& d);
123
124 public:
125 constexpr ~Duration ();
126
127 public:
128 nonvirtual Duration& operator= (Duration&& rhs) noexcept;
129 nonvirtual Duration& operator= (const Duration& rhs);
130
131 private:
132 using InternalNumericFormatType_ = inherited::rep;
133
134 public:
135 /**
136 */
137 nonvirtual void clear ();
138
139 public:
140 /**
141 */
142 constexpr bool empty () const;
143
144 public:
145 /**
146 * Add the given duration to this (equivalent to *this = *this + rhs;).
147 */
148 nonvirtual Duration& operator+= (const Duration& rhs);
149
150 public:
151 /**
152 */
153 nonvirtual Duration& operator-= (const Duration& rhs);
154
155 public:
156 /**
157 * Only specifically specialized variants are supported. Defined for
158 * o timeval
159 * o integral<T>
160 * o floating_point<T>
161 * o String
162 * o Common::IDuration<T>
163 * o Common::ITimePoint<T>
164 *
165 * Note this implies inclusion of:
166 * o time_t
167 * o int, float, etc...
168 * o std::chrono::milliseconds etc...
169 *
170 * Note - if 'empty' - As<> for numeric types returns 0.
171 *
172 * \note SignificantFigures - if given - refers to the precision of the seconds part of the ISO-8601 duration string.
173 *
174 * \note Change since before 3.0d12 - with Duration::As<String> () - used
175 * to default to full precision, and now defaults to default precision (FloatConversions::SignificantFigures{} == 6).
176 *
177 * \@todo unsafe if value out of range - decode how to handle - probably should throw if out of range, but unclear - see AsPinned()
178 *
179 * \note Stroika v2.1 also supported wstring, which was (tentatively) de-supported in Stroika v3.0d5
180 *
181 * \note No exponential numerical format is generated (though it is read). This IS supported in ISO 8601 in Part 2,
182 * but not the original ISO 8601, and the luxon JS library (probably among others) doesn't support it.
183 */
184 template <typename T>
185 nonvirtual T As () const
186 requires (Common::IAnyOf<T, timeval, Characters::String> or integral<T> or floating_point<T> or Common::IDuration<T> or
187 Common::ITimePoint<T>);
188 template <typename T>
189 nonvirtual T As (Characters::FloatConversion::SignificantFigures p) const
190 requires (Common::IAnyOf<T, Characters::String>);
191
192 public:
193 /**
194 * \see As<T>, but automatically takes care of pinning values so always safe and in range.
195 *
196 * Same as As<> - except that it handles overflows, so if you pass in Duration {numeric_limits<long double>::max ()} and convert to seconds, you wont overflow,
197 * but get chrono::seconds::max
198 *
199 * @todo same requires stuff as we have with As<T>()
200 */
201 template <typename T>
202 nonvirtual T AsPinned () const
203 requires (same_as<T, timeval> or integral<T> or floating_point<T> or same_as<T, Characters::String> or Common::IDuration<T> or
204 Common::ITimePoint<T>);
205
206 public:
207 /**
208 * Shorthand for As<String> ().AsUTF8 ()
209 */
210 nonvirtual u8string AsUTF8 () const;
211
212 public:
213 /**
214 */
215 struct PrettyPrintInfo {
216 /**
217 */
218 struct Labels {
219 Characters::String fYear;
220 Characters::String fYears;
221 Characters::String fMonth;
222 Characters::String fMonths;
223 Characters::String fWeek;
224 Characters::String fWeeks;
226 Characters::String fDays;
227 Characters::String fHour;
228 Characters::String fHours;
229 Characters::String fMinute;
230 Characters::String fMinutes;
231 Characters::String fSecond;
232 Characters::String fSeconds;
233 Characters::String fMilliSecond;
234 Characters::String fMilliSeconds;
235 Characters::String fMicroSecond;
236 Characters::String fMicroSeconds;
237 Characters::String fNanoSecond;
238 Characters::String fNanoSeconds;
239 Characters::String fPicoSecond;
240 Characters::String fPicoSeconds;
241 } fLabels;
242 };
243
244 public:
245 static const PrettyPrintInfo kDefaultPrettyPrintInfo;
246
247 public:
248 /**
249 * Consider deprecating, and using 'Format'
250 */
251 nonvirtual Characters::String PrettyPrint (const PrettyPrintInfo& prettyPrintInfo = kDefaultPrettyPrintInfo) const;
252
253 public:
254 /**
255 * \brief like javascript 'humanize' APIs
256 *
257 * \par Example Usage
258 * \code
259 * Assert (Duration{3}.Format () == "3 seconds")
260 * \endcode
261 */
262 nonvirtual Characters::String Format (const PrettyPrintInfo& prettyPrintInfo = kDefaultPrettyPrintInfo) const;
263
264 public:
265 struct AgePrettyPrintInfo {
266 struct Labels {
269 Characters::String fFromNow; // could use 'until' or 'from now'
270 } fLabels;
271 double fNowThreshold{};
272 };
273
274 public:
275 static const AgePrettyPrintInfo kDefaultAgePrettyPrintInfo;
276
277 public:
278 /**
279 * Inspired by useful JQuery plugin http://ksylvest.github.io/jquery-age/; or https://momentjs.com/
280 *
281 * Technically, this isn't an 'age' but just a 'now-centric' pretty printing of durations.
282 *
283 * \todo Just a DRAFT impl for now ... --LGP 2014-09-01 -- @todo FIX/revise/test
284 *
285 * \par Example Usage
286 * \code
287 * EXPECT_EQ (Duration{"PT1.4S"}.PrettyPrintAge (), "now");
288 * EXPECT_EQ (Duration{"-PT9M"}.PrettyPrintAge (), "now");
289 * EXPECT_EQ (Duration{"-PT20M"}.PrettyPrintAge (), "20 minutes ago");
290 * EXPECT_EQ (Duration{"PT20M"}.PrettyPrintAge (), "20 minutes from now");
291 * EXPECT_EQ (Duration{"PT4H"}.PrettyPrintAge (), "4 hours from now");
292 * \endcode
293 */
294 nonvirtual Characters::String PrettyPrintAge (const AgePrettyPrintInfo& agePrettyPrintInfo = kDefaultAgePrettyPrintInfo,
295 const PrettyPrintInfo& prettyPrintInfo = kDefaultPrettyPrintInfo) const;
296
297 public:
298 /**
299 * @see Characters::ToString ();
300 */
301 nonvirtual Characters::String ToString () const;
302
303 public:
304 /**
305 * Duration::kMin is the least duration this Duration class supports representing.
306 */
307 static constexpr Duration min ();
308
309 public:
310 /**
311 * Duration::kMax is the largest duration this Duration class supports representing
312 */
313 static constexpr Duration max ();
314
315 public:
316 /**
317 * Unary negation
318 */
319 nonvirtual Duration operator- () const;
320
321 public:
322 class FormatException;
323
324 private:
325 static InternalNumericFormatType_ ParseTime_ (const string& s);
326 static Characters::String UnParseTime_ (InternalNumericFormatType_ t, Characters::FloatConversion::SignificantFigures p);
327
328 private:
329 /**
330 * 3 types - 'empty' (possibly we can lose this due to presence of optional) - and
331 * string, and numeric.
332 *
333 * We ALWAYS store the numeric value. We OPTIONALLY store the string value. So fNumericRepOrCache_
334 * is ALWAYS valid (unless fRepType==eEmpty_).
335 */
336 static constexpr InternalNumericFormatType_ kValueWhenEmptyRenderedAsNumber_{0};
337 enum RepType_ {
338 eEmpty_,
339 eString_,
340 eNumeric_
341 };
342 RepType_ fRepType_{eEmpty_};
343 union {
344 char fNonStringRep_{}; // unused except to allow constexpr initialization (allow selecting non fStringRep_ to initialize since union must be initialized)
345 string fStringRep_;
346 };
347 constexpr void destroy_ (); // allow call if already empty
348 };
349 static_assert (totally_ordered<Duration>);
350
351 class Duration::FormatException : public Execution::RuntimeErrorException<> {
352 private:
353 using inherited = Execution::RuntimeErrorException<>;
354
355 public:
356 FormatException ();
357
358 public:
359 static const FormatException kThe;
360 };
361 inline const Duration::FormatException Duration::FormatException::kThe;
362
363 inline namespace Literals {
364
365 /**
366 * \brief user defined literal for Duration, specified in ISO8601 format.
367 */
368 [[nodiscard]] Duration operator""_duration (const char* str, size_t len);
369 [[nodiscard]] Duration operator""_duration (const wchar_t* str, size_t len);
370 [[nodiscard]] Duration operator""_duration (const char8_t* str, size_t len);
371 [[nodiscard]] Duration operator""_duration (const char16_t* str, size_t len);
372 [[nodiscard]] Duration operator""_duration (const char32_t* str, size_t len);
373 [[nodiscard]] Duration operator""_duration (long double _Val) noexcept;
374 }
375
376 /**
377 * Return the sum of the two durations.
378 *
379 * Must operators not needed (inherited from duration<>) - but these needed when LHS of operator is not a duration type
380 */
381 Duration operator+ (const DurationSeconds& lhs, const Duration& rhs);
382
383 /**
384 * Multiply the duration by the floating point argument
385 *
386 * Must operators not needed (inherited from duration<>) - but these needed when LHS of operator is not a duration type
387 */
388 Duration operator* (long double lhs, const Duration& rhs);
389
390}
391
393
394 template <>
395 struct DefaultOpenness<Time::Duration> : ExplicitOpenness<Openness::eClosed, Openness::eClosed> {};
396 template <>
397 struct DefaultDifferenceTypes<Time::Duration> : ExplicitDifferenceTypes<Time::DurationSeconds> {};
398 /**
399 * \note This type's properties (kLowerBound/kUpperBound) can only be used after static initialization, and before
400 * static de-initialization.
401 */
402 template <>
403 struct Default<Time::Duration> : ExplicitOpennessAndDifferenceType<Time::Duration> {
404 static const Time::Duration kLowerBound;
405 static const Time::Duration kUpperBound;
406
407 static Time::Duration GetNext (Time::Duration i);
408 static Time::Duration GetPrevious (Time::Duration i);
409 };
410
411}
412
413/*
414 ********************************************************************************
415 ***************************** Implementation Details ***************************
416 ********************************************************************************
417 */
418#include "Duration.inl"
419
420#endif /*_Stroika_Foundation_Time_Duration_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
nonvirtual Characters::String ToString() const
Definition Duration.inl:291
nonvirtual Characters::String Format(const PrettyPrintInfo &prettyPrintInfo=kDefaultPrettyPrintInfo) const
like javascript 'humanize' APIs
Definition Duration.inl:287
nonvirtual Duration & operator+=(const Duration &rhs)
Definition Duration.cpp:65
nonvirtual u8string AsUTF8() const
Definition Duration.inl:114
nonvirtual Characters::String PrettyPrintAge(const AgePrettyPrintInfo &agePrettyPrintInfo=kDefaultAgePrettyPrintInfo, const PrettyPrintInfo &prettyPrintInfo=kDefaultPrettyPrintInfo) const
Definition Duration.cpp:282
nonvirtual Characters::String PrettyPrint(const PrettyPrintInfo &prettyPrintInfo=kDefaultPrettyPrintInfo) const
Definition Duration.cpp:105
static constexpr Duration min()
Definition Duration.inl:295
nonvirtual Duration operator-() const
Definition Duration.cpp:322
static constexpr Duration max()
Definition Duration.inl:299