Fix compile warning
[libi2ncommon] / src / timefunc.cpp
CommitLineData
0e23f538
TJ
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on this file might be covered by the GNU General Public License.
19*/
1b5dfd98
TJ
20/** @file
21 * @brief time related functions.
22 *
23 * @copyright Copyright © 2001-2008 by Intra2net AG
1b5dfd98
TJ
24 *
25 */
26
9f39641d 27#include <cstdio>
d70f7269 28#include <errno.h>
e93545dd
GE
29#include <string>
30#include <sstream>
31#include <iostream>
32#include <iomanip>
f1499910
GE
33#include <bitset>
34#include <stdexcept>
1b5dfd98
TJ
35#include <iterator>
36#include <algorithm>
e93545dd
GE
37
38#include <time.h>
96d0be2e 39#include <unistd.h>
5efd35b1 40#include <string.h>
e93545dd
GE
41#include <sys/timeb.h>
42
43#include <timefunc.hxx>
44#include <i18n.h>
45
96d0be2e
TJ
46
47// define missing POSIX.1b constants...
48
49#ifndef CLOCK_REALTIME
50#define CLOCK_REALTIME 0
51#endif
52#ifndef CLOCK_MONOTONIC
53#define CLOCK_MONOTONIC 1
54#endif
55
56
57
e93545dd
GE
58using namespace std;
59
60double prec_time(void)
61{
62 struct timeb tb;
63 double ret;
64
65 ftime(&tb);
66
67 ret=tb.time+(static_cast<float>(tb.millitm)/1000);
68
69 return ret;
70}
71
72// converts ISO-DATE: 2003-06-13
dad9e26f 73time_t date_to_seconds(const std::string &date)
e93545dd 74{
dad9e26f
GE
75 time_t rtn = 0;
76 int year = -1, month = -1, day = -1;
e93545dd
GE
77
78 string::size_type pos = date.find("-");
79 if (pos == string::npos)
80 return rtn;
81
82 istringstream in(string(date,0,pos));
83 in >> year;
84 year -= 1900;
85
86 string dstr(date, pos+1);
87 if ((pos = dstr.find("-")) == string::npos)
88 return rtn;
89
90 in.clear();
91 in.str(string(dstr, 0, pos));
92 in >> month;
93 month -= 1;
94
95 in.clear();
96 in.str(string(dstr, pos+1));
97 in >> day;
98
99 if (year < 0 || month == -1 || day == -1)
100 return rtn;
4cf3676b 101
e93545dd 102 struct tm tm_struct;
4cf3676b 103 memset(&tm_struct, 0, sizeof(struct tm));
e93545dd
GE
104 tm_struct.tm_year = year;
105 tm_struct.tm_mon = month;
106 tm_struct.tm_mday = day;
107 tm_struct.tm_isdst = -1;
108
109 rtn = mktime (&tm_struct);
110 return rtn;
111}
112
113string make_nice_time(int seconds)
114{
115 ostringstream out;
116
117 int days=seconds/86400;
118 seconds%=86400;
119
c0368918
GE
120 int hours,minutes;
121 split_daysec(seconds,&hours,&minutes,&seconds);
122
c7bd7a65
CH
123 if (days>0)
124 out << days << " " << i18n_plural("day", "days", days) << ", ";
e93545dd
GE
125
126 out << setfill('0');
127 out << setw(2) << hours << ':' << setw(2) << minutes << ':' << setw(2) << seconds;
128
129 return out.str();
130}
131
7839bd53 132string format_full_time(time_t seconds)
e93545dd
GE
133{
134 char buf[50];
135 memset (buf, 0, 50);
532d6b3a
TJ
136 struct tm ta;
137 if (localtime_r((time_t *)&seconds, &ta) == NULL)
138 memset (&ta, 0, sizeof(struct tm));
e93545dd 139
532d6b3a 140 strftime (buf, 49, "%d.%m.%Y %H:%M", &ta);
e93545dd
GE
141 return string(buf);
142}
f1499910 143
25f3d405
CH
144string format_date(time_t seconds)
145{
146 char buf[50];
147 memset (buf, 0, 50);
148 struct tm ta;
149 if (localtime_r((time_t *)&seconds, &ta) == NULL)
150 memset (&ta, 0, sizeof(struct tm));
151
152 strftime (buf, 49, "%d.%m.%Y", &ta);
153 return string(buf);
154}
155
87869870
GE
156void seconds_to_hour_minute(int seconds, int *hour, int *minute)
157{
158 if (hour != NULL) {
159 *hour = 0;
160 while (seconds >= 3600) {
161 seconds-=3600;
162 (*hour)++;
163 }
164 }
165
166 if (minute != NULL) {
167 *minute = 0;
168 while (seconds >= 60) {
169 seconds-=60;
170 (*minute)++;
171 }
172 }
173}
174
c0368918
GE
175/**
176 * Split seconds into hours, minutes and seconds
177 * @param [in] daysec Seconds since start of day
178 * @param [out] outhours hours
179 * @param [out] outminutes minutes
180 * @param [out] outseconds seconds
181 */
182void split_daysec(int daysec, int *outhours, int *outminutes, int *outseconds)
183{
184 int hours=daysec/3600;
185 daysec%=3600;
186
187 int minutes=daysec/60;
188 daysec%=60;
189
190 if (outhours)
191 *outhours=hours;
192
193 if (outminutes)
194 *outminutes=minutes;
195
196 if (outseconds)
197 *outseconds=daysec;
198}
199
200std::string output_hour_minute(int hour, int minute, bool h_for_00, int seconds)
2c66f490
GE
201{
202 ostringstream out;
203
204 if (hour >= 0 && hour < 10)
205 out << '0';
206 out << hour;
207
c0368918 208 if (!h_for_00 || minute != 0 || seconds > 0)
2c66f490
GE
209 {
210 out << ':';
fe223928 211 if (minute >= 0 && minute < 10)
2c66f490
GE
212 out << '0';
213 out << minute;
214 }
215 else
216 out << 'h';
217
c0368918
GE
218 if (seconds > 0)
219 {
220 out << ':';
221 if (seconds > 0 && seconds < 10)
222 out << '0';
223 out << seconds;
224 }
225
2c66f490
GE
226 return out.str();
227}
4e157d1d
TJ
228
229string get_month_name(unsigned char month)
230{
231 string rtn;
232 switch(month) {
233 case 1:
234 rtn = i18n("January");
235 break;
236 case 2:
237 rtn = i18n("February");
238 break;
239 case 3:
240 rtn = i18n("March");
241 break;
242 case 4:
243 rtn = i18n("April");
244 break;
245 case 5:
246 rtn = i18n("May");
247 break;
248 case 6:
249 rtn = i18n("June");
250 break;
251 case 7:
252 rtn = i18n("July");
253 break;
254 case 8:
255 rtn = i18n("August");
256 break;
257 case 9:
258 rtn = i18n("September");
259 break;
260 case 10:
261 rtn = i18n("October");
262 break;
263 case 11:
264 rtn = i18n("November");
265 break;
266 case 12:
267 rtn = i18n("December");
268 break;
269 default:
270 {
271 ostringstream out;
272 out << i18n("Illegal month:") << " " << month;
273 rtn = out.str();
274 }
275 }
276
277 return rtn;
278}
1b5dfd98
TJ
279
280
281/*
282** implementaion of Interval
283*/
284
d181c3bc
TJ
285
286/**
287 * @brief clears the interval (make it empty).
288 */
289void Interval::clear()
290{
291 m_lower_bound = m_upper_bound = 0;
292} // eo Interval::clear()
293
294
1b5dfd98
TJ
295/**
296 * @brief tests if there is some overlapping with another interval
297 * @param other the other interval
298 * @return @a true if the two intervals have a non empty intersection.
299 */
300bool Interval::intersects(const Interval& other) const
301{
302 return
303 // // other start within this:
304 (other.m_lower_bound >= m_lower_bound and other.m_lower_bound < m_upper_bound )
305 // // other end within this:
306 or (other.m_upper_bound > m_lower_bound and other.m_upper_bound <= m_upper_bound )
307 // // other contains this
308 or (other.m_lower_bound <= m_lower_bound and other.m_upper_bound >= m_upper_bound )
309 ;
310} // eo Interval::intersects(const Interval&)
311
312
313/**
314 * @brief tests if the current interval (fully) contains another one.
315 * @param other the other interval.
316 * @return @a true if the current interval fully contains the other interval.
317 */
318bool Interval::contains(const Interval& other) const
319{
320 return (other.m_lower_bound >= m_lower_bound)
321 and (other.m_upper_bound <= m_upper_bound)
322 ;
323} // eo Interval::contains(const Interval& other) const
324
325
326/*
327** implementation of Intervals:
328*/
329
330
331Intervals::Intervals()
332{
333} // eo Intervals::Intervals
334
335
d181c3bc
TJ
336void Intervals::clear()
337{
338 m_intervals.clear();
339} // eo Intervals::clear()
340
1b5dfd98
TJ
341/**
342 * @brief tests if one of the intervals of the list intersects with the given interval.
343 * @param other the interval to check for intersection.
344 * @return @a true if there is an intersection.
345 */
346bool Intervals::intersects(const Interval& other) const
347{
348 for(const_iterator it= begin();
349 it != end();
350 ++it)
351 {
352 if ( it->intersects(other) )
353 {
354 return true;
355 }
356 }
357 return false;
358} // eo Intervals::intersects(const Interval&) const
359
360
361/**
362 * @brief tests if we have at least one intersection with another Intervals instance.
363 * @param other the other instance.
364 * @return @a true if there is an intersection.
365 */
366bool Intervals::intersects(const Intervals& other) const
367{
368 for(const_iterator it= begin();
369 it != end();
370 ++it)
371 {
372 if ( other.intersects( *it ) )
373 {
374 return true;
375 }
376 }
377 return false;
378} // eo Intervals::intersects(const Intervals&) const
379
380
381/**
382 * @brief adds a new interval to the list.
383 * @param new_frame the new interval.
384 *
385 * Adds the interval to the list and joins overlapping intervals.
ebc3b584
TJ
386 *
387 * @internal complexity O(n).
1b5dfd98
TJ
388 */
389void Intervals::add(const Interval& new_frame)
390{
391 if (not new_frame.is_valid() or new_frame.empty())
392 {
393 // well... we will not insert invalid or empty frames!
394 return;
395 }
396 for (IntervalList::iterator it= m_intervals.begin();
397 it != m_intervals.end();
398 ++it)
399 {
400 Interval& current_frame = *it;
401 if ( new_frame.m_lower_bound > current_frame.m_upper_bound )
402 {
403 // new_frame begins later than current end; go on:
404 continue;
405 }
406 // at this point: the begin of the new frame is less then the current end.
407 // now let's determine how we can insert the new frame:
408
409 if ( new_frame.m_upper_bound < current_frame.m_lower_bound )
410 {
411 // new disjoint frame; insert it before the current frame:
412 m_intervals.insert( it, new_frame );
413 // and we are done.
414 return;
415 }
416 // at this point: the end of the new frame is >= current begin.
417 if ( new_frame.m_upper_bound <= current_frame.m_upper_bound )
418 {
419 // the end of the new frame is within our current frame; we need to combine
420 if (new_frame.m_lower_bound < current_frame.m_lower_bound)
421 {
422 // the new interval starts earlier; we need to adjust our current frame:
423 current_frame.m_lower_bound = new_frame.m_lower_bound;
80f30818 424 current_frame.m_changed = true;
1b5dfd98
TJ
425 }
426 // NOTE no "else" part needed since in that case our current frame already
427 // contains the new one!
428
429 // we are done:
430 return;
431 }
432 // at this point: end of new frame > end of current frame
433 // so we need to extend the current frame; at least the end.
434 // But we need to deal with intersects of following frames... *sigh*
435
436 // first the simple part: let's see if we need to move the start:
437 if ( new_frame.m_lower_bound < current_frame.m_lower_bound)
438 {
439 // yes, we need to move the start:
440 current_frame.m_lower_bound = new_frame.m_lower_bound;
80f30818 441 current_frame.m_changed= true;
1b5dfd98
TJ
442 }
443
444 // now let's extend the end:
445 current_frame.m_upper_bound = new_frame.m_upper_bound;
80f30818 446 current_frame.m_changed = true;
1b5dfd98
TJ
447
448 // well... let's walk through the following frames; looking for more joins...:
449 IntervalList::iterator it2 = it;
450 while( ++(it2=it) != m_intervals.end()
451 and current_frame.m_upper_bound >= it2->m_lower_bound
452 )
453 {
454 Interval next_frame= *it2;
455 if ( current_frame.m_upper_bound < next_frame.m_upper_bound )
456 {
457 // in this case our end is within the next frame.
458 // adjust our end.
459 current_frame.m_upper_bound = next_frame.m_upper_bound;
460 }
461 // and remove the next frame since the current frame contains it (now):
462 m_intervals.erase(it2);
463 }
464 // we are done!
465 return;
466 }
467 // at this point: new frame starts later than the last frame ends
468 // append the new frame:
469 m_intervals.push_back( new_frame );
470} // eo Intervals::add(const Interval&)
471
472
473/**
474 * @brief subtracts a time interval from the list.
475 * @param del_frame the time interval to subtract.
476 *
477 * removes the time interval from the list; cut off parts from or remove existing
478 * intervals if they overlap.
ebc3b584
TJ
479 *
480 * @internal complexity O(n).
1b5dfd98
TJ
481 */
482void Intervals::sub(const Interval& del_frame)
483{
484 if (not del_frame.is_valid() or del_frame.empty() )
485 {
486 return;
487 }
488 for (IntervalList::iterator it= m_intervals.begin();
489 it != m_intervals.end();
490 )
491 {
492 Interval& current_frame = *it;
493 if ( del_frame.m_lower_bound >= current_frame.m_upper_bound )
494 {
495 // del_frame begins later than current end; go on:
496 ++it;
497 continue;
498 }
499 // at this point: the begin of the del frame is less then the current end.
500 if ( del_frame.m_upper_bound < current_frame.m_lower_bound )
501 {
502 // end is before our start; nothing to do.
503 return;
504 }
505 // at this point: the end of the del frame is >= current begin.
506 if ( del_frame.m_upper_bound < current_frame.m_upper_bound )
507 {
508 // del frame end point is within our interval.
509 if ( del_frame.m_lower_bound > current_frame.m_lower_bound)
510 {
511 // the del frame is within our interval... we need to split:
512 m_intervals.insert(it, Interval( current_frame.m_lower_bound, del_frame.m_lower_bound ) );
513 }
514 // adjust start of current frame:
80f30818
TJ
515 if (current_frame.m_lower_bound < del_frame.m_upper_bound)
516 {
517 current_frame.m_lower_bound= del_frame.m_upper_bound;
518 current_frame.m_changed= true;
519 }
1b5dfd98
TJ
520 // and we are done!
521 return;
522 }
523 // at this point the end of the del frame is >= current end
524 if ( del_frame.m_lower_bound > current_frame.m_lower_bound )
525 {
526 // a part of the current interval needs to be preserved..
527 // move the end.
528 current_frame.m_upper_bound= del_frame.m_lower_bound;
80f30818 529 current_frame.m_changed= true;
1b5dfd98
TJ
530 // and continue with the next interval:
531 ++it;
532 continue;
533 }
534 // at this point; the whole frame needs to be deleted..
535 if ( it == m_intervals.begin())
536 {
537 m_intervals.erase(it);
538 it= m_intervals.begin();
539 }
540 else
541 {
542 IntervalList::iterator it2= it++;
543 m_intervals.erase(it2);
544 }
545 }
546} // eo Intervals::sub(const Interval&)
547
548
549/**
550 * @brief returns if we contain an interval.
551 * @param other the interval to check.
552 * @return @a true if we cover the given interval, too.
553 */
554bool Intervals::contains(const Interval& other) const
555{
556 for(const_iterator it= begin();
557 it != end();
558 ++it)
559 {
560 if ( it->contains( other ))
561 {
562 return true;
563 }
564 }
565 return false;
566} // eo Intervals::contains(const Interval&) const
567
568
569/**
e156de7c
TJ
570 * @brief returns if we contain an exact interval.
571 * @param other the interval to check.
572 * @return @a true if we axactly contains the given interval.
573 *
574 * @note thsi differs from contain in the way, that we return only @a true
575 * iff we have the given interval in our list; not only cover it.
576 */
577bool Intervals::contains_exact(const Interval& other) const
578{
579 for(const_iterator it= begin();
580 it != end();
581 ++it)
582 {
583 if ( *it == other)
584 {
585 return true;
586 }
587 }
588 return false;
589} // eo Intervals::contains_exact(const Interval&)const
590
591
592/**
1b5dfd98
TJ
593 * @brief returns if we contain another interval combination.
594 * @param other the intervals to check.
595 * @return @a true if we cover the given intervals, too.
596 *
597 * @internal we rely on the fact that the lists are sorted and contain
598 * disjoint intervals.
ebc3b584
TJ
599 *
600 * So this method has a complexity of O(n).
1b5dfd98
TJ
601 */
602bool Intervals::contains(const Intervals& other) const
603{
604 const_iterator my_it= begin();
605 const_iterator other_it= other.begin();
606 while( my_it != end() and other_it!= other.end() )
607 {
608 // seek the first interval which contains the lower bound of the current other interval
609 while (my_it != end()
610 and my_it->m_lower_bound > other_it->m_lower_bound
611 and other_it->m_lower_bound >= my_it->m_upper_bound
612 )
613 {
614 ++my_it;
615 }
616 if (my_it == end())
617 {
618 break;
619 }
620 if (not my_it->contains( *other_it ))
621 {
622 // if we don't contain the current other; we're done:
623 return false;
624 }
625 //else check the next other interval:
626 ++other_it;
627 }
628 return (other_it == other.end());
ebc3b584 629} // eo Intervals::contains(const Intervals&) const
1b5dfd98
TJ
630
631
ebc3b584
TJ
632/**
633 * @brief combines to interval combinates for equality
634 * @param other the other instance.
635 * @return @a true if the other is equal to the current.
636 *
637 * @internal since the lists are sorted, we compare the interval lists.
638 * Thus we have a complexity of O(n).
639 */
1b5dfd98
TJ
640bool Intervals::operator==(const Intervals& other) const
641{
642 // since we keep sorted lists: just compare the lists :-)
643 return m_intervals == other.m_intervals;
644} // eo Intervals::operator==(const Intervals&)
645
646
647Intervals& Intervals::operator+=(const Interval& other)
648{
649 add(other);
650 return *this;
651} // eo operator+=(const Interval&)
652
653
654Intervals& Intervals::operator-=(const Interval& other)
655{
656 sub(other);
657 return *this;
658} // eo operator-=(const Interval&)
659
660
ebc3b584
TJ
661/**
662 * @brief adds the intervals of a second instance to us.
663 * @param other the other instance.
664 * @return self reference (allow chaining).
665 *
666 * @internal since we do simple loops over the other and our intervals
667 * we have a complexity of O(n^2).
668 *
669 * @todo optimize if complexity becomes a problem.
670 */
1b5dfd98
TJ
671Intervals& Intervals::operator+=(const Intervals& other)
672{
673 for(const_iterator it= other.begin();
674 it != other.end();
675 ++it)
676 {
677 add( *it );
678 }
679 return *this;
680} // eo operator+=(const Intervals&)
681
682
ebc3b584
TJ
683/**
684 * @brief subtracts the intervals of a second instance from us.
685 * @param other the other instance.
686 * @return self reference (allow chaining).
687 *
688 * @internal since we do simple loops over the other and our intervals
689 * we have a complexity of O(n^2).
690 *
691 * @todo optimize if complexity becomes a problem.
692 */
1b5dfd98
TJ
693Intervals& Intervals::operator-=(const Intervals& other)
694{
695 if (&other == this)
696 {
697 m_intervals.clear();
698 }
699 else
700 {
701 for(const_iterator it= other.begin();
702 it != other.end();
703 ++it)
704 {
705 sub( *it );
706 }
707 }
708 return *this;
709} // eo operator-=(const Intervals&)
96d0be2e
TJ
710
711
712
713/*
714** clock funcs:
715*/
716
717
718/**
719 * @brief fetches the value from the monotonic clock source.
720 * @param[out] seconds the seconds.
721 * @param[out] nano_seconds the nano seconds.
722 * @return @a true if the clock was successfully read.
723 */
724bool monotonic_clock_gettime(long int& seconds, long int& nano_seconds)
725{
726 struct timespec tp[1];
4b1afed1 727 int res= clock_gettime (CLOCK_MONOTONIC, tp);
96d0be2e
TJ
728 if (0 == res)
729 {
730 seconds= tp->tv_sec;
731 nano_seconds= tp->tv_nsec;
732 }
733 return (res==0);
734} // eo monotonic_clock_gettime(long int&,long int&)
735
736
737/**
738 * @brief fetches the value from the monotonic clock source.
b7e17426
GE
739 * @return the time since system start in nanoseconds, 0 if read was unsuccessful
740 */
741long long monotonic_clock_gettime_nano()
742{
743 long int seconds;
744 long int nano_seconds;
745 long long nano=0;
746
747 if (monotonic_clock_gettime(seconds,nano_seconds))
748 {
749 nano=seconds;
750 nano*=1000000000LL;
751 nano+=nano_seconds;
752 }
753
754 return nano;
755}
756
757/**
758 * @brief fetches the value from the monotonic clock source.
96d0be2e
TJ
759 * @param[out] seconds the seconds.
760 * @param[out] nano_seconds the nano seconds.
761 * @return @a true if the clock was successfully read.
762 */
763bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
764{
765 struct timespec tp[1];
4b1afed1 766 int res= clock_gettime(CLOCK_REALTIME, tp);
96d0be2e
TJ
767 if (0 == res)
768 {
769 seconds= tp->tv_sec;
770 nano_seconds= tp->tv_nsec;
771 }
772 return (res==0);
773} // eo realtime_clock_gettime(long int&,long int&)
774
775
2795e39c
PG
776/*
777 * There is a discrepancy of one input character
778 * due to the lack of sign handling in strptime(3):
779 *
780 * - strftime(3) needs the year specified as %5Y to account for the
781 * leading dash;
782 * - strptime(3) will not parse the leading dash with that format
783 * but apart from that it works well.
784 */
d70f7269 785
214a748a
PG
786namespace iso8601 {
787
788 /*
789 * max: -YYYYYYYYYY-MM-DDThh:mm:ssZ+zzzz ⇒ 32
790 * That is assuming the year in broken down time is an int we
791 * need to reserve ten decimal places.
792 */
793// static_assert (sizeof (((struct tm *)NULL)->tm_year) == 4);
794 static const size_t bufsize = 33;
795
796 enum kind {
797 d = 0,
798 t = 1,
799 tz = 2,
800 dt = 3,
801 dtz = 4,
802 ISO8601_SIZE = 5,
803 };
804
805 /*
806 * Unfortunately the glibc strptime(3) on the Intranator trips over
807 * the length specifier in field descriptors so we can’t reuse the
808 * formatters here. This problem is fixed in newer glibc. For the time
809 * being we keep two tables of formatters and choose the appropriate
810 * at runtime.
811 */
d70f7269 812
214a748a
PG
813 static const char *const formatter [ISO8601_SIZE] =
814 { /* [iso8601::d ] = */ "%4Y-%m-%d",
815 /* [iso8601::t ] = */ "%T",
816 /* [iso8601::tz ] = */ "%TZ%z",
817 /* [iso8601::dt ] = */ "%4Y-%m-%dT%T",
818 /* [iso8601::dtz] = */ "%4Y-%m-%dT%TZ%z",
819 };
820
821 static const char *const scanner [ISO8601_SIZE] =
822 { /* [iso8601::d ] = */ "%Y-%m-%d",
823 /* [iso8601::t ] = */ "%T",
824 /* [iso8601::tz ] = */ "%TZ%z",
825 /* [iso8601::dt ] = */ "%Y-%m-%dT%T",
826 /* [iso8601::dtz] = */ "%Y-%m-%dT%TZ%z",
827 };
828
829 static inline const char *
830 pick_fmt (const bool date, const bool time, const bool tz, const bool scan=false)
831 {
832 const char *const *table = scan ? iso8601::scanner : iso8601::formatter;
833 enum iso8601::kind format = iso8601::dtz;
834
835 if (date) {
836 if (time) {
837 if (tz) {
838 format = iso8601::dtz;
839 } else {
840 format = iso8601::dt;
841 }
d70f7269 842 } else {
214a748a 843 format = iso8601::d;
d70f7269 844 }
214a748a
PG
845 } else if (time && tz) {
846 format = iso8601::tz;
d70f7269 847 } else {
214a748a 848 format = iso8601::t; /* default to %T */
d70f7269 849 }
214a748a
PG
850
851 return table [format];
d70f7269
PG
852 }
853
214a748a 854} /* [iso8601] */
2795e39c 855
2795e39c 856
214a748a 857namespace {
2795e39c
PG
858
859 static inline int flip_tm_year (const int y)
860 { return (y + 1900) * -1 - 1900; }
861} /* [namespace] */
862
863/**
864 * @brief Format a time structure according to ISO-8601, e. g.
865 * “2018-01-09T10:40:00Z+0100”; see \c strftime(3) for
866 * the details.
867 *
868 * @param tm Time to format as broken-down \c struct tm.
869 * @param date Include the day part ([-]YYYY-MM-DD).
870 * @param time Include the time part (hh:mm:ss).
fe377737 871 * @param tz Include the timezone ([±]ZZZZ); only needed if
2795e39c
PG
872 * \c time is requested as well.
873 *
874 * @return The formatted timestamp.
875 */
876std::string format_iso8601 (const struct tm &tm, const bool date,
877 const bool time, const bool tz)
878{
879 struct tm tmp;
214a748a 880 char buf [iso8601::bufsize] = { 0 };
2795e39c 881 char *start = &buf [0];
214a748a 882 const char *format = iso8601::pick_fmt (date, time, tz);
2795e39c
PG
883
884 memcpy (&tmp, &tm, sizeof (tmp));
885
886 if (tmp.tm_year < -1900) { /* negative year */
887 *start = '-';
888 start++;
889 tmp.tm_year = flip_tm_year (tmp.tm_year);
890 }
891
892 /*
fe377737 893 * The sign is *always* handled above so the formatted string here
2795e39c
PG
894 * is always one character shorter.
895 * */
214a748a 896 const size_t n = strftime (start, iso8601::bufsize-1, format, &tmp);
d70f7269 897
2795e39c 898 buf [n+1] = '\0';
d70f7269
PG
899
900 return std::string (buf);
901}
902
d70f7269
PG
903typedef struct tm * (*time_breakdown_fn) (const time_t *, struct tm *);
904
905/**
906 * @brief Format a UNIX timestamp according to ISO-8601. Converts
907 * to broken down time first.
908 *
909 * @param t Time to format as broken-down \c struct tm.
910 * @param date Include the day part ([-]YYYY-MM-DD).
911 * @param time Include the time part (hh:mm:ss).
912 * @param tz Include the timezone ([±]ZZZZ); only heeded if
913 * \c time is requested as well.
914 *
915 * @return The formatted timestamp.
916 */
917std::string format_iso8601 (time_t t, const bool utc, const bool date,
918 const bool time, const bool tz)
919{
920 time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
921 struct tm tm;
922
923 errno = 0;
924 if (breakdown (&t, &tm) == NULL) {
925 return std::string ("error analyzing timestamp: ") + strerror (errno);
926 }
927
928 return format_iso8601 (tm, date, time, tz);
929}
930
2795e39c
PG
931/**
932 * @brief Read a ISO-8601 formatted date stamp into broken down time.
933 *
934 * @param s String containing the timestamp.
935 *
936 * @return \c boost::none if the input string was \c NULL or malformed,
937 * an optional \c struct tm with the extracted values otherwise.
938 */
939boost::optional<struct tm>
940scan_iso8601 (const char *s,
941 const bool date, const bool time, const bool tz) NOEXCEPT
942{
943 struct tm tm;
214a748a 944 const char *format = iso8601::pick_fmt (date, time, tz, true);
2795e39c
PG
945 const char *start = s;
946 bool negyear = false;
947
948 if (s == NULL) {
949 return boost::none;
950 }
951
952 switch (s [0]) {
953 case '\0': {
954 return boost::none;
955 break;
956 }
957 /*
958 * Contrary to what the man page indicates, strptime(3) is *not*
959 * the inverse operation of strftime(3)! The later correctly formats
960 * negative year numbers with the %F modifier wheres the former trips
961 * over the sign character.
962 */
963 case '-': {
964 negyear = true;
965 start++;
966 break;
967 }
968 default: {
969 break;
970 }
971 }
972
973 memset (&tm, 0, sizeof (tm));
974
975 if (strptime (start, format, &tm) == NULL) {
976 return boost::none;
977 }
978
979 if (negyear) {
980 tm.tm_year = flip_tm_year (tm.tm_year);
981 }
982
983 return tm;
984}
985
9f39641d
PG
986/**
987 * @brief Format a \c struct timespec in the schema established by
988 * time(1): “3m14.159s”.
989 *
990 * @param ts The time spec to format.
991 *
992 * @return \c boost:none in case of error during formatting, an optional
993 * \c std::string otherwise.
994 */
995boost::optional<std::string>
996format_min_sec_msec (const struct timespec &ts)
997{
998 char ms [4] = { '\0', '\0', '\0', '\0' };
999
fe377737 1000 if (snprintf (ms, 4, "%.3ld", ts.tv_nsec / 1000000) < 0) {
9f39641d
PG
1001 return boost::none;
1002 }
1003
1004 const time_t min = ts.tv_sec / 60;
1005 const time_t sec = ts.tv_sec - min * 60;
1006
1007 return I2n::to_string (min) + "m"
1008 + I2n::to_string (sec) + "."
1009 + ms + "s"
1010 ;
1011}
d70f7269 1012
8b5814e2
PG
1013namespace I2n {
1014
1015namespace clock {
1016
1017 namespace {
1018
1019 static inline clockid_t
1020 clockid_of_flags (const enum type::id id,
e36ca33c 1021 const enum type::variant var) NOEXCEPT
8b5814e2
PG
1022 {
1023 clockid_t cid = CLOCK_MONOTONIC_COARSE;
1024
1025 switch (id) {
1026
1027 default:
1028 case type::mono: {
1029 switch (var) {
1030 default: {
1031 break;
1032 }
1033 case type::raw: {
1034 cid = CLOCK_MONOTONIC_RAW;
1035 break;
1036 }
1037 case type::exact: {
1038 cid = CLOCK_MONOTONIC;
1039 break;
1040 }
1041 }
1042 break;
1043 }
1044
1045 case type::real: {
1046 if (var == type::exact) {
1047 cid = CLOCK_REALTIME;
1048 } else {
1049 cid = CLOCK_REALTIME_COARSE;
1050 }
1051 break;
1052 }
1053
1054 case type::boot: {
1055 if (var & type::exact) {
1056 cid = CLOCK_BOOTTIME;
1057 }
1058 break;
1059 }
1060
1061 case type::cpu: {
1062 if (var == type::thread) {
1063 cid = CLOCK_THREAD_CPUTIME_ID;
1064 } else {
1065 cid = CLOCK_PROCESS_CPUTIME_ID;
1066 }
1067 break;
1068 }
1069 } /* [switch id] */
1070
1071 return cid;
1072 }
1073
1074 static const struct timespec zero_time = { 0, 0 };
1075
8b5814e2
PG
1076 } /* [namespace] */
1077
e36ca33c
PG
1078 Time::Time (const enum type::id id,
1079 const enum type::variant var) NOEXCEPT
8b5814e2
PG
1080 : value (zero_time)
1081 , id (id)
1082 , variant (var)
1083 , err (0)
1084 { }
1085
cf960f73
PG
1086 /*
1087 * Ctor from *struct tm*. On 32 bit systems the conversion to *time_t* will
1088 * fail with years outside the range from epoch to 2038.
1089 */
2795e39c
PG
1090 Time::Time (const struct tm &tm,
1091 const enum type::id id,
cf960f73 1092 const enum type::variant var)
2795e39c
PG
1093 {
1094 struct tm tmp_tm; /* dummy for mktime(3) */
1095 Time tmp_time;
1096
1097 memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
cf960f73
PG
1098
1099 errno = 0;
1100 const time_t t = mktime (&tmp_tm);
1101 if (t == - 1) { /* Glibc does not set errno on out-of-range here! */
1102 const char *datestr = asctime (&tm);
1103 throw conversion_error (errno,
1104 std::string ("mktime: from struct tm {")
1105 + std::string (datestr, 0, strlen(datestr)-1)
1106 + "}");
1107 }
1108
1109 tmp_time = Time (t, 0l, id, var);
2795e39c
PG
1110
1111 this->swap (tmp_time);
1112 }
1113
8b5814e2 1114 int64_t
e36ca33c
PG
1115 Time::as_nanosec (void) const NOEXCEPT
1116 {
1117 return int64_t (this->value.tv_sec) * TIME_CONST_FACTOR_NANO
1118 + this->value.tv_nsec;
1119 }
8b5814e2
PG
1120
1121 long
e36ca33c 1122 Time::as_nanosec_L (void) const NOEXCEPT /* likely to overflow */
8b5814e2
PG
1123 { return static_cast<long>(this->as_nanosec ()); }
1124
e36ca33c
PG
1125 Time &
1126 Time::operator= (Time t2) NOEXCEPT
1127 {
1128 this->swap (t2);
1129
1130 return *this;
1131 }
1132
1133 Time &
1134 Time::operator= (struct timespec ts) NOEXCEPT
1135 {
1136 std::swap (this->value, ts);
1137 this->id = clock::type::mono;
1138 this->variant = clock::type::dflt;
1139 this->err = 0;
1140
1141 return *this;
1142 }
1143
8b5814e2 1144 void
e36ca33c 1145 Time::unset (void) NOEXCEPT
8b5814e2
PG
1146 { this->value = zero_time; }
1147
1148 bool
e36ca33c 1149 Time::set (void) NOEXCEPT
8b5814e2
PG
1150 {
1151 struct timespec now;
1152
1153 errno = 0;
1154 if (clock_gettime (clockid_of_flags (this->id, this->variant), &now)
1155 == -1)
1156 {
1157 this->err = errno;
1158 this->unset ();
1159
1160 return false;
1161 }
1162 this->err = 0;
1163 this->value = now;
1164
1165 return true;
1166 }
1167
e36ca33c
PG
1168 Time &
1169 Time::add (const time_t sec, const long nsec) NOEXCEPT
1170 {
1171 this->value.tv_sec += sec;
1172 this->value.tv_nsec += nsec;
1173
1174 this->carry_nsec ();
1175
1176 return *this;
1177 }
1178
1179 Time &
1180 Time::subtract (const time_t sec, const long nsec) NOEXCEPT
1181 {
1182 this->value.tv_sec -= sec;
1183 this->value.tv_nsec -= nsec;
1184
1185 this->carry_nsec ();
1186
1187 return *this;
1188 }
1189
1190 Time &
c42fd3b3 1191 Time::scale (const int64_t factor) NOEXCEPT
e36ca33c
PG
1192 {
1193 this->value.tv_sec *= factor;
1194 this->value.tv_nsec *= factor;
1195
1196 this->carry_nsec ();
1197
1198 return *this;
1199 }
1200
c42fd3b3
PG
1201 /*
1202 * Below division code purposely does not attempt to handle divide-
1203 * by-zero just as any other C++ division function does. It is up to
1204 * the caller to ensure that the divisor is not zero.
1205 */
1206 Time &
1207 Time::divide (const int64_t divisor) NOEXCEPT
1208 {
1209 const long sec = static_cast<long> (this->value.tv_sec );
1210 int64_t nsec = static_cast<int64_t> (this->value.tv_nsec);
1211 const ldiv_t div = ldiv (sec, divisor);
1212
1213 if (div.rem != 0) {
1214 nsec += div.rem * TIME_CONST_FACTOR_NANO;
1215 }
1216
1217 nsec /= divisor;
1218
1219 this->value.tv_sec = static_cast<time_t> (div.quot);
1220 this->value.tv_nsec = static_cast<long> (nsec);
1221
1222 this->carry_nsec ();
1223
1224 return *this;
1225 }
1226
72acd54c
PG
1227 boost::optional<std::string>
1228 Time::format_iso8601 (const bool utc,
1229 const bool date,
1230 const bool time,
1231 const bool tz) const
1232 {
1233 time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
1234 struct tm tm;
1235
1236 if (breakdown (&this->value.tv_sec, &tm) == NULL) {
1237 return boost::none;
1238 }
1239
1240 return ::format_iso8601 (tm, date, time, tz);
1241 }
1242
9f39641d 1243 std::string
72acd54c
PG
1244 Time::make_nice_time (void) const
1245 {
1246 /* XXX the cast below results in loss of precision with 64 bit time_t! */
1247 return ::make_nice_time (static_cast<int> (this->value.tv_sec));
1248 }
1249
9f39641d 1250 std::string
72acd54c
PG
1251 Time::format_full_time (void) const
1252 { return ::format_full_time (this->value.tv_sec); }
1253
9f39641d 1254 std::string
72acd54c
PG
1255 Time::format_date (void) const
1256 { return ::format_date (this->value.tv_sec); }
1257
8b5814e2 1258 boost::optional<Time>
e36ca33c 1259 now (const enum type::id id, const enum type::variant var) NOEXCEPT
8b5814e2 1260 {
e36ca33c 1261 Time ret (id, var);
8b5814e2
PG
1262
1263 if (!ret.set ()) {
1264 return boost::none;
1265 }
1266
1267 return ret;
1268 }
1269
e36ca33c
PG
1270 Time
1271 zero (const enum type::id id, const enum type::variant var) NOEXCEPT
1272 { return Time (id, var); }
1273
1274 int
1275 compare (const Time &t1, const Time &t2) NOEXCEPT
1276 {
1277 if (t1.value.tv_sec < t2.value.tv_sec) {
1278 return -1;
1279 }
1280
1281 if (t1.value.tv_sec > t2.value.tv_sec) {
1282 return 1;
1283 }
1284
1285 if (t1.value.tv_nsec < t2.value.tv_nsec) {
1286 return -1;
1287 }
1288
1289 if (t1.value.tv_nsec > t2.value.tv_nsec) {
1290 return 1;
1291 }
1292
1293 return 0;
1294 }
1295
2795e39c
PG
1296 boost::optional<Time>
1297 time_of_iso8601 (const std::string &s,
1298 const bool date,
1299 const bool time,
1300 const bool tz,
1301 const enum type::id id,
1302 const enum type::variant var) NOEXCEPT
1303 {
1304 boost::optional<struct tm> tm = scan_iso8601 (s, date, time, tz);
1305
1306 if (!tm) {
1307 return boost::none;
1308 }
1309
cf960f73
PG
1310 try {
1311 return Time (*tm, id, var);
1312 }
1313 catch (conversion_error &_unused) { }
1314
1315 return boost::none;
2795e39c
PG
1316 }
1317
8b5814e2
PG
1318} /* [namespace clock] */
1319
1320} /* [namespace I2n] */