Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Duration.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. 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 'precision' property to PrettyPrintInfo. Think about precision 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 [[nodiscard]] 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, 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 Precision - 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::Precision{} == 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 template <typename T>
182 nonvirtual T As () const
183 requires (Common::IAnyOf<T, timeval, Characters::String> or integral<T> or floating_point<T> or Common::IDuration<T> or
184 Common::ITimePoint<T>);
185 template <typename T>
186 nonvirtual T As (Characters::FloatConversion::Precision p) const
187 requires (Common::IAnyOf<T, Characters::String>);
188
189 public:
190 /**
191 * \see As<T>, but automatically takes care of pinning values so always safe and in range.
192 *
193 * 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,
194 * but get chrono::seconds::max
195 *
196 * @todo same requires stuff as we have with As<T>()
197 */
198 template <typename T>
199 nonvirtual T AsPinned () const
200 requires (same_as<T, timeval> or integral<T> or floating_point<T> or same_as<T, Characters::String> or Common::IDuration<T> or
201 Common::ITimePoint<T>);
202
203 public:
204 /**
205 * Shorthand for As<String> ().AsUTF8 ()
206 */
207 nonvirtual u8string AsUTF8 () const;
208
209 public:
210 /**
211 */
212 struct PrettyPrintInfo {
213 /**
214 */
215 struct Labels {
216 Characters::String fYear;
217 Characters::String fYears;
218 Characters::String fMonth;
219 Characters::String fMonths;
220 Characters::String fWeek;
221 Characters::String fWeeks;
223 Characters::String fDays;
224 Characters::String fHour;
225 Characters::String fHours;
226 Characters::String fMinute;
227 Characters::String fMinutes;
228 Characters::String fSecond;
229 Characters::String fSeconds;
230 Characters::String fMilliSecond;
231 Characters::String fMilliSeconds;
232 Characters::String fMicroSecond;
233 Characters::String fMicroSeconds;
234 Characters::String fNanoSecond;
235 Characters::String fNanoSeconds;
236 Characters::String fPicoSecond;
237 Characters::String fPicoSeconds;
238 } fLabels;
239 };
240
241 public:
242 static const PrettyPrintInfo kDefaultPrettyPrintInfo;
243
244 public:
245 /**
246 * Consider deprecating, and using 'Format'
247 */
248 nonvirtual Characters::String PrettyPrint (const PrettyPrintInfo& prettyPrintInfo = kDefaultPrettyPrintInfo) const;
249
250 public:
251 /**
252 * \brief like javascript 'humanize' APIs
253 *
254 * \par Example Usage
255 * \code
256 * Assert (Duration{3}.Format () == "3 seconds")
257 * \endcode
258 */
259 nonvirtual Characters::String Format (const PrettyPrintInfo& prettyPrintInfo = kDefaultPrettyPrintInfo) const;
260
261 public:
262 struct AgePrettyPrintInfo {
263 struct Labels {
266 Characters::String fFromNow; // could use 'until' or 'from now'
267 } fLabels;
268 double fNowThreshold{};
269 };
270
271 public:
272 static const AgePrettyPrintInfo kDefaultAgePrettyPrintInfo;
273
274 public:
275 /**
276 * Inspired by useful JQuery plugin http://ksylvest.github.io/jquery-age/; or https://momentjs.com/
277 *
278 * Technically, this isn't an 'age' but just a 'now-centric' pretty printing of durations.
279 *
280 * \todo Just a DRAFT impl for now ... --LGP 2014-09-01 -- @todo FIX/revise/test
281 *
282 * \par Example Usage
283 * \code
284 * EXPECT_EQ (Duration{"PT1.4S"}.PrettyPrintAge (), "now");
285 * EXPECT_EQ (Duration{"-PT9M"}.PrettyPrintAge (), "now");
286 * EXPECT_EQ (Duration{"-PT20M"}.PrettyPrintAge (), "20 minutes ago");
287 * EXPECT_EQ (Duration{"PT20M"}.PrettyPrintAge (), "20 minutes from now");
288 * EXPECT_EQ (Duration{"PT4H"}.PrettyPrintAge (), "4 hours from now");
289 * \endcode
290 */
291 nonvirtual Characters::String PrettyPrintAge (const AgePrettyPrintInfo& agePrettyPrintInfo = kDefaultAgePrettyPrintInfo,
292 const PrettyPrintInfo& prettyPrintInfo = kDefaultPrettyPrintInfo) const;
293
294 public:
295 /**
296 * @see Characters::ToString ();
297 */
298 nonvirtual Characters::String ToString () const;
299
300 public:
301 /**
302 * Duration::kMin is the least duration this Duration class supports representing.
303 */
304 static constexpr Duration min ();
305
306 public:
307 /**
308 * Duration::kMax is the largest duration this Duration class supports representing
309 */
310 static constexpr Duration max ();
311
312 public:
313 /**
314 * Unary negation
315 */
316 nonvirtual Duration operator- () const;
317
318 public:
319 class FormatException;
320
321 private:
322 static InternalNumericFormatType_ ParseTime_ (const string& s);
323 static Characters::String UnParseTime_ (InternalNumericFormatType_ t, Characters::FloatConversion::Precision p);
324
325 private:
326 /**
327 * 3 types - 'empty' (possibly we can lose this due to presence of optional) - and
328 * string, and numeric.
329 *
330 * We ALWAYS store the numeric value. We OPTIONALLY store the string value. So fNumericRepOrCache_
331 * is ALWAYS valid (unless fRepType==eEmpty_).
332 */
333 static constexpr InternalNumericFormatType_ kValueWhenEmptyRenderedAsNumber_{0};
334 enum RepType_ {
335 eEmpty_,
336 eString_,
337 eNumeric_
338 };
339 RepType_ fRepType_{eEmpty_};
340 union {
341 char fNonStringRep_{}; // unused except to allow constexpr initialization (allow selecting non fStringRep_ to initialize since union must be initialized)
342 string fStringRep_;
343 };
344 constexpr void destroy_ (); // allow call if already empty
345 };
346 static_assert (totally_ordered<Duration>);
347
348 class Duration::FormatException : public Execution::RuntimeErrorException<> {
349 private:
350 using inherited = Execution::RuntimeErrorException<>;
351
352 public:
353 FormatException ();
354
355 public:
356 static const FormatException kThe;
357 };
358 inline const Duration::FormatException Duration::FormatException::kThe;
359
360 inline namespace Literals {
361
362 /**
363 * \brief user defined literal for Duration, specified in ISO8601 format.
364 */
365 [[nodiscard]] Duration operator"" _duration (const char* str, size_t len);
366 [[nodiscard]] Duration operator"" _duration (const wchar_t* str, size_t len);
367 [[nodiscard]] Duration operator"" _duration (const char8_t* str, size_t len);
368 [[nodiscard]] Duration operator"" _duration (const char16_t* str, size_t len);
369 [[nodiscard]] Duration operator"" _duration (const char32_t* str, size_t len);
370 [[nodiscard]] Duration operator"" _duration (long double _Val) noexcept;
371 }
372
373 /**
374 * Return the sum of the two durations.
375 *
376 * Must operators not needed (inherited from duration<>) - but these needed when LHS of operator is not a duration type
377 */
378 Duration operator+ (const DurationSeconds& lhs, const Duration& rhs);
379
380 /**
381 * Multiply the duration by the floating point argument
382 *
383 * Must operators not needed (inherited from duration<>) - but these needed when LHS of operator is not a duration type
384 */
385 Duration operator* (long double lhs, const Duration& rhs);
386
387}
388
390
391 template <>
392 struct DefaultOpenness<Time::Duration> : ExplicitOpenness<Openness::eClosed, Openness::eClosed> {};
393 template <>
394 struct DefaultDifferenceTypes<Time::Duration> : ExplicitDifferenceTypes<Time::DurationSeconds> {};
395 /**
396 * \note This type's properties (kLowerBound/kUpperBound) can only be used after static initialization, and before
397 * static de-initialization.
398 */
399 template <>
400 struct Default<Time::Duration> : ExplicitOpennessAndDifferenceType<Time::Duration> {
401 static const Time::Duration kLowerBound;
402 static const Time::Duration kUpperBound;
403
404 static Time::Duration GetNext (Time::Duration i);
405 static Time::Duration GetPrevious (Time::Duration i);
406 };
407
408}
409
410/*
411 ********************************************************************************
412 ***************************** Implementation Details ***************************
413 ********************************************************************************
414 */
415#include "Duration.inl"
416
417#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