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