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