Line data Source code
1 : // Copyright (c) 2021-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Class extending the timespec structure.
22 : *
23 : * The C library includes a timespec structure and various functions used
24 : * to retrieve the system time. We also offer ways to compute durations
25 : * and add/remove a duration from a timespec.
26 : *
27 : * The class also supports convertions to string, negation, and a few
28 : * other features.
29 : */
30 :
31 : // self
32 : //
33 : #include <snapdev/join_strings.h>
34 : #include <snapdev/tokenize_format.h>
35 :
36 :
37 :
38 : // libexcept
39 : //
40 : #include <libexcept/exception.h>
41 :
42 :
43 : // C++
44 : //
45 : #include <cmath>
46 : #include <cstdint>
47 : #include <iomanip>
48 : #include <iostream>
49 : #include <sstream>
50 :
51 :
52 : // C
53 : //
54 : #include <langinfo.h>
55 : #include <stdlib.h>
56 : #include <string.h>
57 : #include <sys/time.h>
58 : #include <time.h>
59 :
60 :
61 :
62 :
63 : extern "C" {
64 : /** \brief Define the nl_langinfo() function type.
65 : *
66 : * This is primarily used to allow for testing with any format which
67 : * the test can control through an nl_langinfo() wrapper.
68 : *
69 : * See nl_langinfo(3) for details about the actualy function.
70 : */
71 : typedef char *(nl_langinfo_func_t)(nl_item item);
72 : }
73 :
74 :
75 : namespace snapdev
76 : {
77 :
78 5 : DECLARE_MAIN_EXCEPTION(timespec_ex_exception);
79 :
80 0 : DECLARE_EXCEPTION(timespec_ex_exception, clock_error);
81 2 : DECLARE_EXCEPTION(timespec_ex_exception, syntax_error);
82 3 : DECLARE_EXCEPTION(timespec_ex_exception, overflow);
83 :
84 :
85 : class timespec_ex
86 : : public timespec
87 : {
88 : public:
89 : /** \brief Initialize a timespec_ex to zero.
90 : *
91 : * This constructor is used to initialize a new timespec_ex to the
92 : * default value, which is 0.
93 : */
94 45 : timespec_ex()
95 45 : {
96 45 : set(0L);
97 45 : }
98 :
99 :
100 : /** \brief Initialize a timespec_ex from another.
101 : *
102 : * This constructors allows you to directly copy a timespec_ex
103 : * in another new timespec_ex object.
104 : *
105 : * \param[in] t The timespec_ex to directly copy.
106 : */
107 64 : timespec_ex(timespec_ex const & t)
108 64 : {
109 64 : set(t);
110 64 : }
111 :
112 :
113 : /** \brief Initialize a timespec_ex from a timespec structure.
114 : *
115 : * This constructors allows you to directly set a timespec_ex
116 : * to the specified timespec values.
117 : *
118 : * \param[in] t The timespec to directly copy.
119 : */
120 68 : timespec_ex(timespec const & t)
121 68 : {
122 68 : set(t);
123 68 : }
124 :
125 :
126 : /** \brief Initialize a timespec_ex from seconds and nanoseconds.
127 : *
128 : * This constructors allows you to directly initialize a timespec_ex
129 : * from seconds and nanoseconds.
130 : *
131 : * To create a valid timespec_ex object, you must pass a number
132 : * between 0 and 999'999'999 for the \p nsec parameter.
133 : *
134 : * \param[in] sec The number of seconds.
135 : * \param[in] nsec The number of nano-seconds.
136 : */
137 7 : timespec_ex(time_t sec, long nsec)
138 7 : {
139 7 : set(sec, nsec);
140 7 : }
141 :
142 :
143 : #if 0
144 : // keeping this just in case, for older versions of g++ or other C++ compiler
145 : // which may fail
146 : //
147 : /** \brief Initialize a timespec_ex from an int in seconds.
148 : *
149 : * This constructors allows us to compile our tests.
150 : *
151 : * \bug
152 : * It is unfortunate that at the moment this constructor is required.
153 : * This is likely to cause bugs because the choice between int64_t
154 : * and int is not a good choice here.
155 : *
156 : * \param[in] sec The seconds to copy to timespec_ex.
157 : */
158 : timespec_ex(int sec)
159 : {
160 : set(sec * static_cast<std::int64_t>(1'000'000'000));
161 : }
162 : #endif
163 :
164 :
165 : /** \brief Initialize a timespec_ex from an int64_t in nanoseconds.
166 : *
167 : * This constructors allows you to directly set a timespec_ex
168 : * to the specified \p nsec value.
169 : *
170 : * \param[in] nsec The nano-seconds to copy to timespec_ex.
171 : */
172 28 : timespec_ex(std::int64_t nsec)
173 28 : {
174 28 : set(nsec);
175 28 : }
176 :
177 :
178 : /** \brief Initialize a timespec_ex from an double in seconds.
179 : *
180 : * This constructors allows you to directly set a timespec_ex
181 : * to the specified \p sec value.
182 : *
183 : * \param[in] sec The seconds to copy to timespec_ex.
184 : */
185 14 : timespec_ex(double sec)
186 14 : {
187 14 : set(sec);
188 14 : }
189 :
190 :
191 : /** \brief Initialize a timespec_ex from a timestamp.
192 : *
193 : * When using the ostream << operator, you generate a simple Unix
194 : * timestamp with a nanosecond precision. This is the converse
195 : * function which you can use to convert the value back to a
196 : * timespec_ex value.
197 : *
198 : * \warning
199 : * This will not work properly if you used the to_string() function,
200 : * even if you used the format "%s.%N". In that case, make sure to
201 : * use the from_string() function instead.
202 : *
203 : * \param[in] timestamp The string to convert to a timespec_ex.
204 : */
205 4 : timespec_ex(std::string const & timestamp)
206 4 : {
207 4 : set(timestamp);
208 1 : }
209 :
210 :
211 : /** \brief Set the timespec_ex to the specified timespec_ex.
212 : *
213 : * This function copies the specified timespec_ex (\p t) to this
214 : * timespec_ex object.
215 : *
216 : * \param[in] t The timespec to copy in this timespec_ex.
217 : *
218 : * \return A reference to this object.
219 : */
220 48 : timespec_ex & operator = (timespec_ex t)
221 : {
222 48 : tv_sec = t.tv_sec;
223 48 : tv_nsec = t.tv_nsec;
224 48 : return *this;
225 : }
226 :
227 :
228 : /** \brief Set the timespec_ex to the specified timespec.
229 : *
230 : * This function copies the specified timespec to this timespec_ex
231 : * object.
232 : *
233 : * \param[in] t The timespec to copy in this timespec_ex.
234 : *
235 : * \return A reference to this object.
236 : */
237 1 : timespec_ex & operator = (timespec const & t)
238 : {
239 1 : tv_sec = t.tv_sec;
240 1 : tv_nsec = t.tv_nsec;
241 1 : return *this;
242 : }
243 :
244 :
245 : /** \brief Set the timespec_ex to the number of nanoseconds.
246 : *
247 : * This function saves the number of nanoseconds in \p nsec as a
248 : * tv_sec and tv_nsec representation.
249 : *
250 : * \param[in] nsec The nano-seconds to save in this timespec_ex.
251 : *
252 : * \return A reference to this object.
253 : */
254 1 : timespec_ex & operator = (std::int64_t nsec)
255 : {
256 1 : set(nsec);
257 1 : return *this;
258 : }
259 :
260 :
261 : /** \brief Set this timespec_ex to the number of seconds in \p sec.
262 : *
263 : * This function transforms the specified double \p sec in a timespec_ex.
264 : *
265 : * \note
266 : * At this time, the number of nanoseconds is floored.
267 : *
268 : * \param[in] sec The number of seconds defined in a double.
269 : *
270 : * \return A reference to this object.
271 : */
272 3 : timespec_ex & operator = (double sec)
273 : {
274 3 : set(sec);
275 3 : return *this;
276 : }
277 :
278 :
279 : /** \brief Set this timespec_ex to a timestamp.
280 : *
281 : * When using the ostream << operator, you generate a simple Unix
282 : * timestamp with a nanosecond precision. This is the converse
283 : * function which you can use to convert the value back to a
284 : * timespec_ex value.
285 : *
286 : * \warning
287 : * This will not work properly if you used the to_string() function,
288 : * even if you used the format "%s.%N". In that case, make sure to
289 : * use the from_string() function instead.
290 : *
291 : * \param[in] timestamp The string to convert to a timespec_ex.
292 : *
293 : * \return A reference to this object.
294 : */
295 1 : timespec_ex & operator = (std::string const & timestamp)
296 : {
297 1 : set(timestamp);
298 1 : return *this;
299 : }
300 :
301 :
302 : /** \brief Set the timespec_ex to the number of nanoseconds.
303 : *
304 : * This function saves the number of nanoseconds in \p nsec as a
305 : * tv_sec and tv_nsec representation.
306 : *
307 : * \param[in] t The nano-seconds to save in this timespec_ex.
308 : *
309 : * \return A reference to this object.
310 : */
311 64 : void set(timespec_ex const & t)
312 : {
313 64 : tv_sec = t.tv_sec;
314 64 : tv_nsec = t.tv_nsec;
315 64 : }
316 :
317 :
318 : /** \brief Set the timespec_ex to the specified timespec.
319 : *
320 : * This function copies the timespec in \p t in this timespec_ex object.
321 : *
322 : * \param[in] t The timespec to save in this timespec_ex.
323 : *
324 : * \return A reference to this object.
325 : */
326 68 : void set(timespec const & t)
327 : {
328 68 : tv_sec = t.tv_sec;
329 68 : tv_nsec = t.tv_nsec;
330 68 : }
331 :
332 :
333 : /** \brief Set the timespec_ex to the specified values.
334 : *
335 : * This function allows you to set the timespec_ex to the specified
336 : * number of seconds (\p sec) and nano-seconds (\p nsec).
337 : *
338 : * \param[in] sec The new number of seconds.
339 : * \param[in] nsec The new number of nano-seconds.
340 : */
341 7 : void set(time_t sec, long nsec)
342 : {
343 7 : tv_sec = sec;
344 7 : tv_nsec = nsec;
345 7 : }
346 :
347 :
348 : /** \brief Set the timespec_ex to the number of nanoseconds.
349 : *
350 : * This function saves the number of nanoseconds in \p nsec as a
351 : * tv_sec and tv_nsec representation.
352 : *
353 : * \param[in] nsec The nano-seconds to save in this timespec_ex.
354 : *
355 : * \return A reference to this object.
356 : */
357 74 : void set(std::int64_t nsec)
358 : {
359 74 : bool const neg(nsec < 0);
360 74 : if(neg)
361 : {
362 8 : nsec = -nsec;
363 : }
364 74 : tv_sec = static_cast<time_t>(nsec / 1'000'000'000LL);
365 74 : tv_nsec = static_cast<long>(nsec % 1'000'000'000LL);
366 74 : if(neg)
367 : {
368 8 : *this = -*this;
369 : }
370 74 : }
371 :
372 :
373 : /** \brief Set this timespec_ex to the number of seconds in \p sec.
374 : *
375 : * This function transforms the specified double \p sec in a timespec_ex.
376 : *
377 : * \note
378 : * At this time, the number of nanoseconds is floored.
379 : *
380 : * \param[in] sec The number of seconds defined in a double.
381 : *
382 : * \return A reference to this object.
383 : */
384 17 : void set(double sec)
385 : {
386 17 : bool const neg(sec < 0.0);
387 17 : if(neg)
388 : {
389 1 : sec = -sec;
390 : }
391 17 : tv_sec = static_cast<time_t>(floor(sec));
392 17 : tv_nsec = static_cast<long>((sec - floor(sec)) * 1.0e9);
393 17 : if(neg)
394 : {
395 1 : *this = -*this;
396 : }
397 17 : }
398 :
399 : /** \brief Convert a timestamp in a string to a timespec_ex structure.
400 : *
401 : * This function converts the timestamp as generated by the ostream
402 : * << operator back into a timespec_ex structure.
403 : *
404 : * The parser here assumes that the input is a valid Unix UTC timestamp
405 : * with seconds and nanoseconds separated by a period.
406 : *
407 : * The number may be negative (the number of seconds is a signed number).
408 : *
409 : * The number can end with an 's' representing the unit "second".
410 : *
411 : * The function trims the input string (ignores the leading and ending
412 : * spaces).
413 : *
414 : * Examples of valid input:
415 : *
416 : * \code
417 : * "1685372468.564231883"
418 : * "1685372468.564231883s"
419 : * " 1685372468.564231883s "
420 : * "1685372468.564"
421 : * \endcode
422 : *
423 : * Note that the precision does not need to be to the nanosecond. The
424 : * function properly interprets the number after the decimal point
425 : * by reading the equivalent of 9 digits, using '0' when all 9 digits
426 : * are not present.
427 : *
428 : * \exception overflow
429 : * If the number of seconds is too large, this exception is raised.
430 : *
431 : * \exception syntax_error
432 : * The number of seconds must be indicated with at least one digit,
433 : * even if zero (0). If the number starts with any other character,
434 : * this exception is raised. Further, if the string includes any
435 : * character other than digits, a decimal point, and the 's' unit
436 : * at the end, then a syntax error is also raised.
437 : *
438 : * \param[in] timestamp The string to convert to a timespec_ex.
439 : */
440 18 : void set(std::string const & timestamp)
441 : {
442 18 : char const * s(timestamp.c_str());
443 43 : while(isspace(*s))
444 : {
445 25 : ++s;
446 : }
447 18 : time_t sign(1);
448 18 : if(*s == '-')
449 : {
450 4 : ++s;
451 4 : sign = -1;
452 : }
453 14 : else if(*s == '+')
454 : {
455 2 : ++s;
456 : }
457 18 : time_t sec(0);
458 18 : long nsec(0);
459 18 : if(*s < '0' || *s > '9')
460 : {
461 1 : throw syntax_error("number of seconds must include at least one digit, even if '0'.");
462 : }
463 118 : for(; *s >= '0' && *s <= '9'; ++s)
464 : {
465 102 : sec = sec * 10 + (*s - '0');
466 102 : if(sec < 0)
467 : {
468 1 : throw overflow("number of seconds is too large.");
469 : }
470 : }
471 16 : if(*s == '.')
472 : {
473 14 : ++s;
474 :
475 : // we count and only read up to 9 digits which means we cannot
476 : // have an overflow or an invalid number (>= 1,000,000,000)
477 : //
478 14 : int count(0);
479 92 : for(; *s >= '0' && *s <= '9' && count < 9; ++s, ++count)
480 : {
481 78 : nsec = nsec * 10 + (*s - '0');
482 : }
483 :
484 : // skip additional digits
485 : //
486 17 : for(; *s >= '0' && *s <= '9'; ++s)
487 : {
488 3 : ++s;
489 : }
490 :
491 : // in case all 9 digits were not included
492 : //
493 62 : for(; count < 9; ++count)
494 : {
495 48 : nsec *= 10;
496 : }
497 : }
498 :
499 : // give user the ability to enter 's' for "second" as a unit
500 : //
501 16 : if(*s == 's')
502 : {
503 5 : ++s;
504 : }
505 33 : while(isspace(*s))
506 : {
507 17 : ++s;
508 : }
509 16 : if(*s != '\0')
510 : {
511 1 : throw syntax_error(std::string("number include unexpected characters (") + s + ").");
512 : }
513 :
514 15 : tv_sec = sec * sign;
515 15 : tv_nsec = nsec;
516 15 : }
517 :
518 : /** \brief Get system time.
519 : *
520 : * This function reads the system time and saves it into this
521 : * timespec_ex object.
522 : *
523 : * \todo
524 : * Look into whether we want to return an error if clock_gettime()
525 : * fails.
526 : *
527 : * \param[in] clk_id The type of clock you want to query.
528 : *
529 : * \return A timespec_ex representing the specified \p clk_id.
530 : */
531 1 : static timespec_ex gettime(clockid_t clk_id = CLOCK_REALTIME)
532 : {
533 1 : timespec_ex result;
534 1 : clock_gettime(clk_id, &result);
535 1 : return result;
536 : }
537 :
538 :
539 : /** \brief Extract the timespec_ex as an int64_t value.
540 : *
541 : * This function transforms a timespec_ex structure in an int64_t
542 : * in nanoseconds.
543 : *
544 : * \return This timespec_ex converted to nanoseconds.
545 : */
546 1 : std::int64_t to_nsec() const
547 : {
548 1 : return tv_nsec
549 1 : + tv_sec * 1'000'000'000LL;
550 : }
551 :
552 :
553 : /** \brief Extract the timespec_ex as an int64_t value.
554 : *
555 : * This function transforms a timespec_ex structure in an int64_t
556 : * in microseconds. The bottom 3 digits are lost. No rounding
557 : * happens.
558 : *
559 : * \note
560 : * To can round the value up by first adding 500 (round) or 999 (ceil)
561 : * to the timespec_ex value.
562 : *
563 : * \return This timespec_ex converted to microseconds.
564 : */
565 1 : std::int64_t to_usec() const
566 : {
567 1 : return tv_nsec / 1'000LL
568 1 : + tv_sec * 1'000'000LL;
569 : }
570 :
571 :
572 : /** \brief Extract the timespec_ex as a double value.
573 : *
574 : * This function transforms a timespec_ex structure into a double
575 : * in seconds. The nanoseconds are added as one billionth of a
576 : * second.
577 : *
578 : * \return The timespec_ex converted the seconds.
579 : */
580 6 : double to_sec() const
581 : {
582 6 : return static_cast<double>(tv_sec)
583 6 : + static_cast<double>(tv_nsec) / 1.0e9;
584 : }
585 :
586 :
587 : /** \brief Convert the timespec_ex to a simple Unix timestamp.
588 : *
589 : * This function converts this timespec_ex structure to a string.
590 : * The seconds and nanoseconds are not adjust in any way making
591 : * it possible to use this function to serialize the timespec_ex
592 : * value. Use the set(std::string const & timestamp) function
593 : * to convert the string back to a timespec_ex value.
594 : *
595 : * If the \p remove_ending_zeroes parameter is set to true, the
596 : * nanoseconds ending zeroes are removed from the resulting string.
597 : * If there were no nanoseconds (0), then the period also gets
598 : * removed.
599 : *
600 : * \return A string representing this timespec_ex exactly.
601 : *
602 : * \sa set(std::string const & timestamp);
603 : */
604 10 : std::string to_timestamp(bool remove_ending_zeroes = false) const
605 : {
606 10 : std::stringstream s;
607 10 : s << tv_sec << "." << std::setw(9) << std::setfill('0') << tv_nsec;
608 10 : std::string result(s.str());
609 10 : if(remove_ending_zeroes)
610 : {
611 27 : while(result.back() == '0')
612 : {
613 22 : result.pop_back();
614 : }
615 5 : if(result.back() == '.')
616 : {
617 1 : result.pop_back();
618 : }
619 : }
620 20 : return result;
621 10 : }
622 :
623 :
624 : /** \brief Format the date to the specified format.
625 : *
626 : * \warning
627 : * This function uses strftime() which interprets the input time as
628 : * localtime, no matter what. You may want to consider using the
629 : * ostream<<() function if you want to save the time as a UTC string
630 : * as seconds & nanoseconds.
631 : *
632 : * This function transforms the time in a string and returns that string.
633 : * The function uses the strftime(). See that manual page to define
634 : * the format properly.
635 : *
636 : * This function supports a format extension: `%N`, to output 9 digits
637 : * with the nanoseconds available in the `timespec_ex` object. Without
638 : * the `%N`, the precision is to the second.
639 : *
640 : * It is possible to retrieve the timezone using the `"%z"` or `"%Z"`
641 : * format. If you want the full name, since we are beyond C++20,
642 : * you can use `std::chrono` like so:
643 : *
644 : * \code
645 : * std::chrono::current_zone()->name()
646 : * \endcode
647 : *
648 : * \warning
649 : * Internally, the function uses a buffer of 256 bytes maximum.
650 : * Make sure your format is limited to the date and time. Things
651 : * you want to prepend or append should be managed outside of this
652 : * call.
653 : *
654 : * \warning
655 : * The `%N` should be preceeded by a period if included just after the
656 : * seconds (`%s` or `%S`). Some format arguments do not end with seconds,
657 : * such as the `%c`, `%r`, `%X`, `%EX`. If you want to use those, then
658 : * the `%N` should be separated by a space and probably followed by `ns`.
659 : * Note, however, that leading `0` are automatically added so `%N` is
660 : * always 9 characters at the moment.
661 : *
662 : * \exception overflow
663 : * In case the conversion of the `tv_sec` fails, this exception is raised.
664 : *
665 : * \param[in] format The format used to transform the date and time in
666 : * a string.
667 : *
668 : * \return The formatted date and time.
669 : *
670 : * \sa from_string()
671 : */
672 : template<nl_langinfo_func_t nl_langinfo_wrapper = nl_langinfo>
673 13 : std::string to_string(std::string const & format = std::string()) const
674 : {
675 13 : struct tm date_and_time = {};
676 13 : struct tm * ptr(nullptr);
677 13 : ptr = localtime_r(&tv_sec, &date_and_time);
678 13 : if(ptr == nullptr)
679 : {
680 : throw overflow("the specified number of seconds could not be transformed in a 'struct tm'."); // LCOV_EXCL_LINE
681 : }
682 13 : format_item<char>::list_t format_items;
683 13 : std::string f(format);
684 13 : if(f.empty())
685 : {
686 : // compute the default using the lcoale
687 : //
688 : // if there is a %r, we convert it to the T_FMT_AMPM
689 : // if there is a %X, we convert it to the T_FMT
690 : // if there is a %EX, we convert it to the ERA_T_FMT
691 : // and then search for "%T" or "%S" or "%s" and insert ".%N"
692 : // right after
693 : //
694 : // Note: our algorithm doesn't work very well if the format
695 : // includes multiple %r, %X, %EX, %T, %S
696 : //
697 4 : f = nl_langinfo_wrapper(D_T_FMT);
698 4 : if(f.empty())
699 : {
700 : // use POSIX default if %c is not defined in the locale
701 : //
702 1 : f = "%a %b %e %H:%M:%S %Y";
703 : }
704 4 : format_items = tokenize_format<
705 : char
706 : , snapdev::strftime_letter_traits<char>
707 : , snapdev::strftime_flag_traits<char>>(f);
708 :
709 : // replace 'r', 'X', 'EX' with their content because those
710 : // will include the actual 'T', 'S', or 's'
711 : //
712 : // count the number of times we loop, if more than 10, forget
713 : // it; that means the locale is broken (creates an infinite loop)
714 : //
715 4 : int loop(0);
716 36 : for(auto it(format_items.begin()); it != format_items.end() && loop < 10; )
717 : {
718 32 : int t(0);
719 32 : switch(it->format())
720 : {
721 1 : case 'r':
722 : // "r"
723 1 : t = T_FMT_AMPM;
724 1 : break;
725 :
726 2 : case 'X':
727 2 : if(it->has_flags(snapdev::strftime_flag_traits<char>::FORMAT_FLAG_EXTENDED))
728 : {
729 : // "EX"
730 1 : t = ERA_T_FMT;
731 : }
732 : else
733 : {
734 : // "X"
735 1 : t = T_FMT;
736 : }
737 2 : break;
738 :
739 29 : default:
740 29 : ++it;
741 29 : continue;
742 :
743 : }
744 6 : std::string const sub_format(nl_langinfo_wrapper(t));
745 3 : if(!sub_format.empty())
746 : {
747 3 : auto const & sub_format_items(tokenize_format<
748 : char
749 : , snapdev::strftime_letter_traits<char>
750 : , snapdev::strftime_flag_traits<char>>(sub_format));
751 3 : format_items.insert(
752 : it
753 : , sub_format_items.begin()
754 : , sub_format_items.end());
755 3 : }
756 3 : it = format_items.erase(it);
757 3 : ++loop;
758 : }
759 :
760 50 : for(auto it(format_items.begin()); it != format_items.end(); ++it)
761 : {
762 46 : switch(it->format())
763 : {
764 6 : case 'T':
765 : case 'S':
766 : case 's':
767 : {
768 6 : ++it;
769 :
770 6 : snapdev::format_item<char> period;
771 6 : period.string(".");
772 6 : format_items.insert(it, period);
773 :
774 6 : snapdev::format_item<char> nanoseconds;
775 6 : nanoseconds.string("%N");
776 6 : nanoseconds.format('N');
777 6 : it = format_items.insert(it, nanoseconds);
778 6 : }
779 : break;
780 :
781 : }
782 : }
783 : }
784 : else
785 : {
786 : // user format, do not temper with it, if no .%N, that's
787 : // the user's choice
788 : //
789 9 : format_items = tokenize_format<
790 : char
791 : , snapdev::strftime_letter_traits<char, true>
792 : , snapdev::strftime_flag_traits<char>
793 : , snapdev::strftime_number_traits<char>>(f);
794 : }
795 :
796 : // Add support for microseconds and milliseconds
797 : //
798 111 : for(auto it(format_items.begin()); it != format_items.end();)
799 : {
800 99 : if(it->format() == 'N')
801 : {
802 13 : std::string n(std::to_string(tv_nsec));
803 13 : if(n.length() > 9)
804 : {
805 1 : throw overflow("tv_nsec is 1 billion or more, which is invalid.");
806 : }
807 12 : if(!it->has_flags(snapdev::strftime_flag_traits<char>::FORMAT_FLAG_NO_PAD))
808 : {
809 : // prepend padding zeroes or spaces
810 : //
811 12 : char const pad(it->has_flags(snapdev::strftime_flag_traits<char>::FORMAT_FLAG_PAD_WITH_SPACES)
812 12 : ? ' '
813 : : '0');
814 24 : std::string const indent(9 - n.length(), pad);
815 12 : n = indent + n;
816 12 : }
817 12 : if(it->has_flags(snapdev::strftime_flag_traits<char>::FORMAT_FLAG_EXTENDED))
818 : {
819 : // remove ending zeroes
820 : //
821 2 : std::string::size_type const last_non_zero(n.find_last_not_of('0'));
822 2 : if(last_non_zero == std::string::npos)
823 : {
824 1 : n = "0";
825 : }
826 : else
827 : {
828 1 : n = n.substr(0, last_non_zero + 1);
829 : }
830 : }
831 :
832 : // replace the %N with the final nanoseconds string
833 : //
834 12 : snapdev::format_item<char> nanoseconds;
835 12 : nanoseconds.string(n);
836 12 : format_items.insert(it, nanoseconds);
837 :
838 12 : it = format_items.erase(it);
839 13 : }
840 : else
841 : {
842 86 : ++it;
843 : }
844 : }
845 :
846 : // convert the format items back in a format string
847 : //
848 12 : snapdev::format_item<char> empty_item;
849 12 : f = snapdev::join_strings(format_items, empty_item);
850 :
851 12 : char buf[256];
852 12 : std::size_t const sz(strftime(buf, sizeof(buf), f.c_str(), &date_and_time));
853 12 : if(sz == 0)
854 : {
855 : // this happens with just a "%p" and "wrong locale"
856 : // or when the buffer is too small, which should not
857 : // be the case unless you add much more than the format
858 : // in that string
859 : //
860 1 : throw overflow(
861 : "the specified strftime() format \""
862 : + format
863 : + "\" failed.");
864 : }
865 :
866 22 : return std::string(buf, sz);
867 16 : }
868 :
869 :
870 : /** \brief Convert a string with a date to a timespec_ex.
871 : *
872 : * This function is the converse of the to_string() function. It
873 : * converts a string (\p s) to a timespec_ex time and date using
874 : * the specified \p format.
875 : *
876 : * \note
877 : * The format is used with the strptime() function. Please refer
878 : * to that function for additional information. To some extend, the
879 : * function also supports our %N extension.
880 : *
881 : * \param[in] s The string to be converted to this timespec_ex.
882 : * \param[in] format The format to use for the input data.
883 : *
884 : * \sa to_string()
885 : */
886 2 : void from_string(std::string const & s, std::string const & format)
887 : {
888 : // I really have no clue how to properly support the %N option
889 : // without rewriting strptime() which I readlly don't want to do
890 : //
891 : // one way is to look for the '.' (assuming the %N is preceeded
892 : // by such) but some people write dates with those as in:
893 : //
894 : // 29.05.2023
895 : //
896 2 : if(format.find("%N") != std::string::npos)
897 : {
898 1 : throw libexcept::fixme("the from_string() %N extension is not yet implemented.");
899 : }
900 :
901 1 : struct tm t;
902 1 : strptime(s.c_str(), format.c_str(), &t);
903 1 : tv_sec = mktime(&t);
904 1 : tv_nsec = 0;
905 1 : }
906 :
907 :
908 : /** \brief Validate this timespec_ex structure.
909 : *
910 : * This function returns true if this timespec_ex structure is considered
911 : * valid.
912 : *
913 : * At this time, the validation consists of verifying that the
914 : * nanoseconds is a number between 0 and 1 billion (maximum excluded).
915 : *
916 : * \note
917 : * Negative timespec_ex are represented by a negative tv_sec. The
918 : * tv_nsec can never be negative after a valid operation.
919 : *
920 : * \return true if the timespec_ex is considered valid.
921 : */
922 7 : bool valid() const
923 : {
924 7 : return tv_nsec < 1'000'000'000LL;
925 : }
926 :
927 :
928 : /** \brief Check whether this timespec_ex is negative.
929 : *
930 : * This function checks whether the number represents a negative
931 : * timespec_ex. This is true if the number of seconds is negative.
932 : *
933 : * \note
934 : * The first negative timespec_ex is { -1, 999,999,999 }.
935 : *
936 : * \return true if the timespec_ex is considered negative.
937 : */
938 74 : bool negative() const
939 : {
940 74 : return tv_sec < 0LL;
941 : }
942 :
943 :
944 : /** \brief Add two timespec_ex together.
945 : *
946 : * This function adds \p rhs to this timespec_ex value and returns a
947 : * new timespec_ex with the result. This timespec_ex is not modified.
948 : *
949 : * \param[in] rhs The right handside to add to this number.
950 : *
951 : * \return A new timespec_ex representing the sum of 'this' and rhs.
952 : */
953 34 : timespec_ex add(timespec_ex const & rhs) const
954 : {
955 34 : bool const lneg(negative());
956 34 : bool const rneg(rhs.negative());
957 :
958 34 : timespec_ex lp(lneg ? -*this : *this);
959 34 : timespec_ex rp(rneg ? -rhs : rhs);
960 :
961 34 : timespec_ex result;
962 :
963 34 : switch((lneg ? 1 : 0) + (rneg ? 2 : 0))
964 : {
965 16 : case 0: // positive + positive
966 : case 3: // negative + negative
967 16 : result.tv_sec = lp.tv_sec + rp.tv_sec;
968 16 : result.tv_nsec = lp.tv_nsec + rp.tv_nsec;
969 16 : break;
970 :
971 3 : case 1: // negative + positive
972 3 : result.tv_sec = rp.tv_sec - lp.tv_sec;
973 3 : result.tv_nsec = rp.tv_nsec - lp.tv_nsec;
974 3 : break;
975 :
976 15 : case 2: // positive + negative
977 15 : result.tv_sec = lp.tv_sec - rp.tv_sec;
978 15 : result.tv_nsec = lp.tv_nsec - rp.tv_nsec;
979 15 : break;
980 :
981 : }
982 :
983 34 : if(result.tv_nsec < 0)
984 : {
985 5 : --result.tv_sec;
986 5 : result.tv_nsec += 1'000'000'000L;
987 : }
988 29 : else if(result.tv_nsec >= 1'000'000'000)
989 : {
990 5 : ++result.tv_sec;
991 5 : result.tv_nsec -= 1'000'000'000;
992 : }
993 :
994 34 : if(lneg && rneg)
995 : {
996 2 : result = -result;
997 : }
998 :
999 34 : return result;
1000 : }
1001 :
1002 :
1003 : /** \brief Compare two timespec_ex together.
1004 : *
1005 : * This function compares two timespecs and determine whether they
1006 : * are equal (0), 'this' is smaller (negative) or \p rhs is smaller
1007 : * (positive).
1008 : *
1009 : * \param[in] rhs The right handside to compare.
1010 : *
1011 : * \return negative, 0, or positive depending on the order between
1012 : * \p lhs and \p rhs.
1013 : */
1014 113 : int compare(timespec_ex const & rhs) const
1015 : {
1016 : // see operator <=> ... catch2 seems to not accept these just yet
1017 : //return tv_sec == rhs.tv_sec
1018 : // ? tv_nsec <=> rhs.tv_nsec
1019 : // : tv_sec <=> rhs.tv_sec;
1020 :
1021 113 : if(tv_sec == rhs.tv_sec)
1022 : {
1023 100 : return tv_nsec == rhs.tv_nsec
1024 127 : ? 0
1025 127 : : (tv_nsec < rhs.tv_nsec ? -1 : 1);
1026 : }
1027 :
1028 13 : return tv_sec < rhs.tv_sec ? -1 : 1;
1029 : }
1030 :
1031 :
1032 : /** \brief Check whether the timespec_ex is zero.
1033 : *
1034 : * This function returns true if the timespec_ex represents zero
1035 : * (i.e. zero seconds and zero nano-seconds).
1036 : *
1037 : * \return true if the timespec_ex is zero, false if not zero.
1038 : */
1039 11 : bool operator ! () const
1040 : {
1041 11 : return tv_sec == 0 && tv_nsec == 0;
1042 : }
1043 :
1044 :
1045 : /** \brief Add the right handside to this timespec_ex.
1046 : *
1047 : * This operator adds the right handside to this object.
1048 : *
1049 : * \param[in] rhs Another timespec_ex to add to this one.
1050 : *
1051 : * \return A reference to this timespec_ex object.
1052 : */
1053 19 : timespec_ex & operator += (timespec_ex const & rhs)
1054 : {
1055 19 : *this = add(rhs);
1056 19 : return *this;
1057 : }
1058 :
1059 :
1060 : /** \brief Add 1 nanosecond to this timespec_ex object.
1061 : *
1062 : * This function adds exactly one nanonsecond to this timespec_ex
1063 : * object.
1064 : *
1065 : * \return A reference to this timespec_ex object.
1066 : */
1067 3 : timespec_ex & operator ++ ()
1068 : {
1069 3 : *this += 1L;
1070 3 : return *this;
1071 : }
1072 :
1073 :
1074 : /** \brief Add 1 nanosecond to this timespec_ex object.
1075 : *
1076 : * This function adds exactly one nanonsecond to this timespec_ex
1077 : * object and returns the original value.
1078 : *
1079 : * \return A copy of this timespec_ex object before the add() occurs.
1080 : */
1081 1 : timespec_ex operator ++ (int)
1082 : {
1083 1 : timespec_ex result(*this);
1084 1 : *this += 1L;
1085 1 : return result;
1086 : }
1087 :
1088 :
1089 : /** \brief Add two timespec_ex objects and return the result.
1090 : *
1091 : * This function computes the addition of this timespec_ex object
1092 : * and the \p t timespec_ex and returns the result. The inputs
1093 : * are not modified.
1094 : *
1095 : * \param[in] t The right handside to add to this timespex_ex object.
1096 : *
1097 : * \return The sum of the inputs in a new timespec_ex object.
1098 : */
1099 7 : timespec_ex operator + (timespec_ex const & t) const
1100 : {
1101 7 : timespec_ex result(*this);
1102 7 : result += t;
1103 7 : return result;
1104 : }
1105 :
1106 :
1107 : /** \brief Subtract the right handside from this timespec_ex.
1108 : *
1109 : * This operator subtracts the right handside from this object.
1110 : *
1111 : * \param[in] rhs Another timespec_ex to subtract from this one.
1112 : *
1113 : * \return A reference to this timespec_ex object.
1114 : */
1115 15 : timespec_ex & operator -= (timespec_ex const & rhs)
1116 : {
1117 15 : *this = add(-rhs);
1118 15 : return *this;
1119 : }
1120 :
1121 :
1122 : /** \brief Subtract 1 nanosecond from timespec_ex object.
1123 : *
1124 : * This function subtracts exactly one nanonsecond from this
1125 : * timespec_ex object.
1126 : *
1127 : * \return A reference to this timespec_ex object.
1128 : */
1129 3 : timespec_ex & operator -- ()
1130 : {
1131 3 : *this -= 1L;
1132 3 : return *this;
1133 : }
1134 :
1135 :
1136 : /** \brief Subtract 1 nanosecond from timespec_ex object.
1137 : *
1138 : * This function subtracts exactly one nanonsecond from this
1139 : * timespec_ex object and returns the original value.
1140 : *
1141 : * \return A copy of this timespec_ex object before the subtract occurs.
1142 : */
1143 1 : timespec_ex operator -- (int)
1144 : {
1145 1 : timespec_ex result(*this);
1146 1 : *this -= 1L;
1147 1 : return result;
1148 : }
1149 :
1150 :
1151 : /** \brief Compute the additive opposite of the right handside timespec_ex.
1152 : *
1153 : * This function computers the opposite of the right handside timespec_ex
1154 : * and returns a copy with the result.
1155 : *
1156 : * This is equivalent to computing `0 - t`.
1157 : *
1158 : * \param[in] t The right handside time to negate.
1159 : *
1160 : * \return A timespec_ex representing the additive opposite of the input.
1161 : */
1162 51 : timespec_ex operator - () const
1163 : {
1164 51 : timespec_ex result(timespec{ -tv_sec, -tv_nsec });
1165 51 : if(result.tv_nsec < 0)
1166 : {
1167 50 : --result.tv_sec;
1168 50 : result.tv_nsec += 1'000'000'000L;
1169 : }
1170 51 : return result;
1171 : }
1172 :
1173 :
1174 : /** \brief Subtract \p t from this timespec_ex object.
1175 : *
1176 : * This function computes the difference of this timespec_ex object
1177 : * and the \p t timespec_ex object and returns the result. The inputs
1178 : * are not modified.
1179 : *
1180 : * \param[in] rhs The right handside to subtract from this timespex_ex
1181 : * object.
1182 : *
1183 : * \return The different of the inputs in a new timespec_ex object.
1184 : */
1185 7 : timespec_ex operator - (timespec_ex const & rhs) const
1186 : {
1187 7 : timespec_ex result(*this);
1188 7 : result -= rhs;
1189 7 : return result;
1190 : }
1191 :
1192 : #if 0
1193 : // it looks like catch is not quite ready for this one
1194 : auto operator <=> (timespec_ex const & t) const
1195 : {
1196 : return compare(t);
1197 : }
1198 : #endif
1199 :
1200 : /** \brief Compare whether the two timespec_ex are equal.
1201 : *
1202 : * \param[in] t The time to compare against.
1203 : *
1204 : * \return true if both timespec_ex objects are equal.
1205 : */
1206 38 : bool operator == (timespec_ex const & t) const
1207 : {
1208 38 : return compare(t) == 0;
1209 : }
1210 :
1211 :
1212 : /** \brief Compare whether the two timespec_ex are not equal.
1213 : *
1214 : * \param[in] t The time to compare against.
1215 : *
1216 : * \return true if both timespec_ex objects are not equal.
1217 : */
1218 15 : bool operator != (timespec_ex const & t) const
1219 : {
1220 15 : return compare(t) != 0;
1221 : }
1222 :
1223 :
1224 : /** \brief Compare whether the left handside is smaller.
1225 : *
1226 : * \param[in] t The time to compare against.
1227 : *
1228 : * \return true if the left handside timespec_ex object is smaller.
1229 : */
1230 16 : bool operator < (timespec_ex const & t) const
1231 : {
1232 16 : return compare(t) == -1;
1233 : }
1234 :
1235 :
1236 : /** \brief Compare whether the left handside is smaller or equal.
1237 : *
1238 : * \param[in] t The time to compare against.
1239 : *
1240 : * \return true if the left handside timespec_ex object is smaller
1241 : * or equal.
1242 : */
1243 15 : bool operator <= (timespec_ex const & t) const
1244 : {
1245 15 : return compare(t) <= 0;
1246 : }
1247 :
1248 :
1249 : /** \brief Compare whether the left handside is larger.
1250 : *
1251 : * \param[in] t The time to compare against.
1252 : *
1253 : * \return true if the left handside timespec_ex object is larger.
1254 : */
1255 14 : bool operator > (timespec_ex const & t) const
1256 : {
1257 14 : return compare(t) == 1;
1258 : }
1259 :
1260 :
1261 : /** \brief Compare whether the left handside is larger or equal.
1262 : *
1263 : * \param[in] t The time to compare against.
1264 : *
1265 : * \return true if the left handside timespec_ex object is larger
1266 : * or equal.
1267 : */
1268 15 : bool operator >= (timespec_ex const & t) const
1269 : {
1270 15 : return compare(t) >= 0;
1271 : }
1272 : };
1273 :
1274 :
1275 :
1276 : /** \brief Create a timespec_ex object with "now".
1277 : *
1278 : * This function creates a timespec_ex object with the time set to "now".
1279 : *
1280 : * This is a wrapper of the clock_gettime(2) function.
1281 : *
1282 : * \exception clock_error
1283 : * This function raises the clock_error if the clock could not be read.
1284 : *
1285 : * \param[in] clk_id The type of clock to read. By default this is
1286 : * CLOCK_REALTIME.
1287 : *
1288 : * \return A timespec_ex object with "now" as the time.
1289 : */
1290 1 : inline timespec_ex now(clockid_t clk_id = CLOCK_REALTIME)
1291 : {
1292 1 : timespec_ex n;
1293 1 : int const r(clock_gettime(clk_id, &n));
1294 1 : if(r != 0)
1295 : {
1296 : // LCOV_EXCL_START
1297 : int const e(errno);
1298 : throw clock_error(
1299 : "clock_gettime() failed: "
1300 : + std::to_string(e)
1301 : + ", "
1302 : + strerror(e));
1303 : // LCOV_EXCL_STOP
1304 : }
1305 1 : return n;
1306 : }
1307 :
1308 :
1309 :
1310 :
1311 :
1312 :
1313 : /** \brief Output a timespec to a basic_ostream.
1314 : *
1315 : * This function allows one to print out a timespec. By default the function
1316 : * prints the timespec as a floating point.
1317 : *
1318 : * To wrinte a date and time string instead, use the timespec_ex::to_string()
1319 : * function as in:
1320 : *
1321 : * \code
1322 : * out << t.to_string("%Y/%m/%d %H:%M:%S.%N");
1323 : * \endcode
1324 : *
1325 : * Just keep in mind that to_string() generates a local timestamp. The
1326 : * timespec_ex::from_string() reverses the value back to a timespec_ex.
1327 : *
1328 : * \todo
1329 : * Add a flag to determine whether the nanoseconds ending zeroes should
1330 : * be removed or not (warning: the timespec_ex::to_timestamp() cannot
1331 : * be used as is because this function receives a timespec not our
1332 : * timespec_ex structure).
1333 : *
1334 : * \param[in] out The output stream where the timespec gets written.
1335 : * \param[in] t The actual timespec that is to be printed.
1336 : *
1337 : * \return A reference to the basic_ostream object.
1338 : */
1339 : template<typename CharT, typename Traits>
1340 5 : std::basic_ostream<CharT, Traits> & operator << (std::basic_ostream<CharT, Traits> & out, timespec const & t)
1341 : {
1342 : // write to a string buffer first
1343 : //
1344 5 : std::basic_ostringstream<CharT, Traits, std::allocator<CharT> > s;
1345 :
1346 : // setup the string output like the out stream
1347 : //
1348 5 : s.flags(out.flags());
1349 5 : s.imbue(out.getloc());
1350 5 : s.precision(out.precision());
1351 5 : s << t.tv_sec << "." << std::setw(9) << std::setfill('0') << t.tv_nsec;
1352 :
1353 : // buffer is ready, display in output in one go
1354 : //
1355 10 : return out << s.str();
1356 5 : }
1357 :
1358 :
1359 : } // namespace snapdev
1360 : // vim: ts=4 sw=4 et
|