0d2ee5d03392130562183ae3b5133ea0494b0f8c
[libi2ncommon] / src / timefunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /** @file
21  * @brief time related functions.
22  *
23  * @copyright Copyright © 2001-2008 by Intra2net AG
24  *
25  */
26
27 #include <cstdio>
28 #include <errno.h>
29 #include <string>
30 #include <sstream>
31 #include <iostream>
32 #include <iomanip>
33 #include <bitset>
34 #include <stdexcept>
35 #include <iterator>
36 #include <algorithm>
37
38 #include <time.h>
39 #include <unistd.h>
40 #include <string.h>
41 #include <sys/timeb.h>
42
43 #include <timefunc.hxx>
44 #include <i18n.h>
45
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
58 using namespace std;
59
60 double 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
73 time_t date_to_seconds(const std::string &date)
74 {
75     time_t rtn = 0;
76     int year = -1, month = -1, day = -1;
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;
101
102     struct tm tm_struct;
103     memset(&tm_struct, 0, sizeof(struct tm));
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
113 string make_nice_time(int seconds)
114 {
115     ostringstream out;
116
117     int days=seconds/86400;
118     seconds%=86400;
119
120     int hours,minutes;
121     split_daysec(seconds,&hours,&minutes,&seconds);
122     
123     if (days>0)
124         out << days << " " << i18n_plural("day", "days", days) << ", ";
125
126     out << setfill('0');
127     out << setw(2) << hours << ':' << setw(2) << minutes << ':' << setw(2) << seconds;
128
129     return out.str();
130 }
131
132 string format_full_time(time_t seconds)
133 {
134     char buf[50];
135     memset (buf, 0, 50);
136     struct tm ta;
137     if (localtime_r((time_t *)&seconds, &ta) == NULL)
138         memset (&ta, 0, sizeof(struct tm));
139
140     strftime (buf, 49, "%d.%m.%Y %H:%M", &ta);
141     return string(buf);
142 }
143
144 string 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
156 void 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
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     */
182 void 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
200 std::string output_hour_minute(int hour, int minute, bool h_for_00, int seconds)
201 {
202     ostringstream out;
203     
204     if (hour >= 0 && hour < 10)
205         out << '0';
206     out << hour;
207     
208     if (!h_for_00 || minute != 0 || seconds > 0)
209     {
210         out << ':';
211         if (minute >= 0 && minute < 10)
212             out << '0';
213         out << minute;
214     }
215     else
216         out << 'h';
217
218     if (seconds > 0)
219     {
220         out << ':';
221         if (seconds > 0 && seconds < 10)
222             out << '0';
223         out << seconds;
224     }
225
226     return out.str();
227 }
228
229 string 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 }
279
280
281 /*
282 ** implementaion of Interval
283 */
284
285
286 /**
287  * @brief clears the interval (make it empty).
288  */
289 void Interval::clear()
290 {
291     m_lower_bound = m_upper_bound = 0;
292 } // eo Interval::clear()
293
294
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  */
300 bool 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  */
318 bool 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
331 Intervals::Intervals()
332 {
333 } // eo Intervals::Intervals
334
335
336 void Intervals::clear()
337 {
338     m_intervals.clear();
339 } // eo Intervals::clear()
340
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  */
346 bool 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  */
366 bool 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.
386  *
387  * @internal complexity O(n).
388  */
389 void 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;
424                 current_frame.m_changed = true;
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;
441             current_frame.m_changed= true;
442         }
443
444         // now let's extend the end:
445         current_frame.m_upper_bound = new_frame.m_upper_bound;
446         current_frame.m_changed = true;
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.
479  *
480  * @internal complexity O(n).
481  */
482 void 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:
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             }
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;
529             current_frame.m_changed= true;
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  */
554 bool 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 /**
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  */
577 bool 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 /**
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.
599  *
600  * So this method has a complexity of O(n).
601  */
602 bool 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());
629 } // eo Intervals::contains(const Intervals&) const
630
631
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  */
640 bool 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
647 Intervals& Intervals::operator+=(const Interval& other)
648 {
649     add(other);
650     return *this;
651 } // eo operator+=(const Interval&)
652
653
654 Intervals& Intervals::operator-=(const Interval& other)
655 {
656     sub(other);
657     return *this;
658 } // eo operator-=(const Interval&)
659
660
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  */
671 Intervals& 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
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  */
693 Intervals& 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&)
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  */
724 bool monotonic_clock_gettime(long int& seconds, long int& nano_seconds)
725 {
726     struct timespec tp[1];
727     int res= clock_gettime (CLOCK_MONOTONIC, tp);
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.
739  * @return the time since system start in nanoseconds, 0 if read was unsuccessful
740  */
741 long 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.
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  */
763 bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
764 {
765     struct timespec tp[1];
766     int res= clock_gettime(CLOCK_REALTIME, tp);
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
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  */
785
786 namespace 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      */
812
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                 }
842             } else {
843                 format = iso8601::d;
844             }
845         } else if (time && tz) {
846             format = iso8601::tz;
847         } else {
848             format = iso8601::t; /* default to %T */
849         }
850
851         return table [format];
852     }
853
854 } /* [iso8601] */
855
856
857 namespace {
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).
871  * @param tz      Include the timezone ([±]ZZZZ); only heeded if
872  *                \c time is requested as well.
873  *
874  * @return        The formatted timestamp.
875  */
876 std::string format_iso8601 (const struct tm &tm, const bool date,
877                             const bool time, const bool tz)
878 {
879     struct tm tmp;
880     char buf [iso8601::bufsize] = { 0 };
881     char *start = &buf [0];
882     const char *format = iso8601::pick_fmt (date, time, tz);
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     /*
893      * The sign is *always* handled above so the formatted string her
894      * is always one character shorter.
895      * */
896     const size_t n = strftime (start, iso8601::bufsize-1, format, &tmp);
897
898     buf [n+1] = '\0';
899
900     return std::string (buf);
901 }
902
903 typedef 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  */
917 std::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
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  */
939 boost::optional<struct tm>
940 scan_iso8601 (const char *s,
941               const bool date, const bool time, const bool tz) NOEXCEPT
942 {
943     struct tm tm;
944     const char *format = iso8601::pick_fmt (date, time, tz, true);
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
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  */
995 boost::optional<std::string>
996 format_min_sec_msec (const struct timespec &ts)
997 {
998     char ms [4] = { '\0', '\0', '\0', '\0' };
999
1000     if (snprintf (ms, 4, "%0.3ld", ts.tv_nsec / 1000000) < 0) {
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 }
1012
1013 namespace I2n {
1014
1015 namespace clock {
1016
1017     namespace {
1018
1019         static inline clockid_t
1020         clockid_of_flags (const enum type::id      id,
1021                           const enum type::variant var) NOEXCEPT
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
1076     } /* [namespace] */
1077
1078     Time::Time (const enum type::id id,
1079                 const enum type::variant var) NOEXCEPT
1080         : value   (zero_time)
1081         , id      (id)
1082         , variant (var)
1083         , err     (0)
1084     { }
1085
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      */
1090     Time::Time (const struct tm          &tm,
1091                 const enum type::id       id,
1092                 const enum type::variant  var)
1093     {
1094         struct tm tmp_tm; /* dummy for mktime(3) */
1095         Time   tmp_time;
1096
1097         memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
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);
1110
1111         this->swap (tmp_time);
1112     }
1113
1114     int64_t
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     }
1120
1121     long
1122     Time::as_nanosec_L (void) const NOEXCEPT /* likely to overflow */
1123     { return static_cast<long>(this->as_nanosec ()); }
1124
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
1144     void
1145     Time::unset (void) NOEXCEPT
1146     { this->value = zero_time; }
1147
1148     bool
1149     Time::set (void) NOEXCEPT
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
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 &
1191     Time::scale (const int64_t factor) NOEXCEPT
1192     {
1193         this->value.tv_sec  *= factor;
1194         this->value.tv_nsec *= factor;
1195
1196         this->carry_nsec ();
1197
1198         return *this;
1199     }
1200
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
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
1243     std::string
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
1250     std::string
1251     Time::format_full_time (void) const
1252     { return ::format_full_time (this->value.tv_sec); }
1253
1254     std::string
1255     Time::format_date (void) const
1256     { return ::format_date (this->value.tv_sec); }
1257
1258     boost::optional<Time>
1259     now (const enum type::id id, const enum type::variant var) NOEXCEPT
1260     {
1261         Time ret (id, var);
1262
1263         if (!ret.set ()) {
1264             return boost::none;
1265         }
1266
1267         return ret;
1268     }
1269
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
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
1310         try {
1311             return Time (*tm, id, var);
1312         }
1313         catch (conversion_error &_unused) { }
1314
1315         return boost::none;
1316     }
1317
1318 } /* [namespace clock] */
1319
1320 } /* [namespace I2n] */