take precautions for 32bit time_t/long
[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 <errno.h>
28 #include <string>
29 #include <sstream>
30 #include <iostream>
31 #include <iomanip>
32 #include <bitset>
33 #include <stdexcept>
34 #include <iterator>
35 #include <algorithm>
36
37 #include <time.h>
38 #include <unistd.h>
39 #include <string.h>
40 #include <sys/timeb.h>
41
42 #include <timefunc.hxx>
43 #include <i18n.h>
44
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
57 using namespace std;
58
59 double 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
72 time_t date_to_seconds(const std::string &date)
73 {
74     time_t rtn = 0;
75     int year = -1, month = -1, day = -1;
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;
100
101     struct tm tm_struct;
102     memset(&tm_struct, 0, sizeof(struct tm));
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
112 string make_nice_time(int seconds)
113 {
114     ostringstream out;
115
116     int days=seconds/86400;
117     seconds%=86400;
118
119     int hours,minutes;
120     split_daysec(seconds,&hours,&minutes,&seconds);
121     
122     if (days>0)
123         out << days << " " << i18n_plural("day", "days", days) << ", ";
124
125     out << setfill('0');
126     out << setw(2) << hours << ':' << setw(2) << minutes << ':' << setw(2) << seconds;
127
128     return out.str();
129 }
130
131 string format_full_time(time_t seconds)
132 {
133     char buf[50];
134     memset (buf, 0, 50);
135     struct tm ta;
136     if (localtime_r((time_t *)&seconds, &ta) == NULL)
137         memset (&ta, 0, sizeof(struct tm));
138
139     strftime (buf, 49, "%d.%m.%Y %H:%M", &ta);
140     return string(buf);
141 }
142
143 string 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
155 void 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
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     */
181 void 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
199 std::string output_hour_minute(int hour, int minute, bool h_for_00, int seconds)
200 {
201     ostringstream out;
202     
203     if (hour >= 0 && hour < 10)
204         out << '0';
205     out << hour;
206     
207     if (!h_for_00 || minute != 0 || seconds > 0)
208     {
209         out << ':';
210         if (minute >= 0 && minute < 10)
211             out << '0';
212         out << minute;
213     }
214     else
215         out << 'h';
216
217     if (seconds > 0)
218     {
219         out << ':';
220         if (seconds > 0 && seconds < 10)
221             out << '0';
222         out << seconds;
223     }
224
225     return out.str();
226 }
227
228 string 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 }
278
279
280 /*
281 ** implementaion of Interval
282 */
283
284
285 /**
286  * @brief clears the interval (make it empty).
287  */
288 void Interval::clear()
289 {
290     m_lower_bound = m_upper_bound = 0;
291 } // eo Interval::clear()
292
293
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  */
299 bool 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  */
317 bool 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
330 Intervals::Intervals()
331 {
332 } // eo Intervals::Intervals
333
334
335 void Intervals::clear()
336 {
337     m_intervals.clear();
338 } // eo Intervals::clear()
339
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  */
345 bool 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  */
365 bool 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.
385  *
386  * @internal complexity O(n).
387  */
388 void 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;
423                 current_frame.m_changed = true;
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;
440             current_frame.m_changed= true;
441         }
442
443         // now let's extend the end:
444         current_frame.m_upper_bound = new_frame.m_upper_bound;
445         current_frame.m_changed = true;
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.
478  *
479  * @internal complexity O(n).
480  */
481 void 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:
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             }
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;
528             current_frame.m_changed= true;
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  */
553 bool 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 /**
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  */
576 bool 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 /**
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.
598  *
599  * So this method has a complexity of O(n).
600  */
601 bool 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());
628 } // eo Intervals::contains(const Intervals&) const
629
630
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  */
639 bool 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
646 Intervals& Intervals::operator+=(const Interval& other)
647 {
648     add(other);
649     return *this;
650 } // eo operator+=(const Interval&)
651
652
653 Intervals& Intervals::operator-=(const Interval& other)
654 {
655     sub(other);
656     return *this;
657 } // eo operator-=(const Interval&)
658
659
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  */
670 Intervals& 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
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  */
692 Intervals& 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&)
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  */
723 bool monotonic_clock_gettime(long int& seconds, long int& nano_seconds)
724 {
725     struct timespec tp[1];
726     int res= clock_gettime (CLOCK_MONOTONIC, tp);
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.
738  * @return the time since system start in nanoseconds, 0 if read was unsuccessful
739  */
740 long 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.
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  */
762 bool realtime_clock_gettime(long int& seconds, long int& nano_seconds)
763 {
764     struct timespec tp[1];
765     int res= clock_gettime(CLOCK_REALTIME, tp);
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
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  */
784
785 namespace 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      */
811
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                 }
841             } else {
842                 format = iso8601::d;
843             }
844         } else if (time && tz) {
845             format = iso8601::tz;
846         } else {
847             format = iso8601::t; /* default to %T */
848         }
849
850         return table [format];
851     }
852
853 } /* [iso8601] */
854
855
856 namespace {
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  */
875 std::string format_iso8601 (const struct tm &tm, const bool date,
876                             const bool time, const bool tz)
877 {
878     struct tm tmp;
879     char buf [iso8601::bufsize] = { 0 };
880     char *start = &buf [0];
881     const char *format = iso8601::pick_fmt (date, time, tz);
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      * */
895     const size_t n = strftime (start, iso8601::bufsize-1, format, &tmp);
896
897     buf [n+1] = '\0';
898
899     return std::string (buf);
900 }
901
902 typedef 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  */
916 std::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
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  */
938 boost::optional<struct tm>
939 scan_iso8601 (const char *s,
940               const bool date, const bool time, const bool tz) NOEXCEPT
941 {
942     struct tm tm;
943     const char *format = iso8601::pick_fmt (date, time, tz, true);
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
985
986 namespace I2n {
987
988 namespace clock {
989
990     namespace {
991
992         static inline clockid_t
993         clockid_of_flags (const enum type::id      id,
994                           const enum type::variant var) NOEXCEPT
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
1049     } /* [namespace] */
1050
1051     Time::Time (const enum type::id id,
1052                 const enum type::variant var) NOEXCEPT
1053         : value   (zero_time)
1054         , id      (id)
1055         , variant (var)
1056         , err     (0)
1057     { }
1058
1059     /*
1060      * Ctor from *struct tm*. On 32 bit systems the conversion to *time_t* will
1061      * fail with years outside the range from epoch to 2038.
1062      */
1063     Time::Time (const struct tm          &tm,
1064                 const enum type::id       id,
1065                 const enum type::variant  var)
1066     {
1067         struct tm tmp_tm; /* dummy for mktime(3) */
1068         Time   tmp_time;
1069
1070         memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
1071
1072         errno = 0;
1073         const time_t t = mktime (&tmp_tm);
1074         if (t == - 1) { /* Glibc does not set errno on out-of-range here! */
1075             const char *datestr = asctime (&tm);
1076             throw conversion_error (errno,
1077                                     std::string ("mktime: from struct tm {")
1078                                     + std::string (datestr, 0, strlen(datestr)-1)
1079                                     + "}");
1080         }
1081
1082         tmp_time = Time (t, 0l, id, var);
1083
1084         this->swap (tmp_time);
1085     }
1086
1087     int64_t
1088     Time::as_nanosec (void) const NOEXCEPT
1089     {
1090         return int64_t (this->value.tv_sec) * TIME_CONST_FACTOR_NANO
1091              + this->value.tv_nsec;
1092     }
1093
1094     long
1095     Time::as_nanosec_L (void) const NOEXCEPT /* likely to overflow */
1096     { return static_cast<long>(this->as_nanosec ()); }
1097
1098     Time &
1099     Time::operator= (Time t2) NOEXCEPT
1100     {
1101         this->swap (t2);
1102
1103         return *this;
1104     }
1105
1106     Time &
1107     Time::operator= (struct timespec ts) NOEXCEPT
1108     {
1109         std::swap (this->value, ts);
1110         this->id      = clock::type::mono;
1111         this->variant = clock::type::dflt;
1112         this->err     = 0;
1113
1114         return *this;
1115     }
1116
1117     void
1118     Time::unset (void) NOEXCEPT
1119     { this->value = zero_time; }
1120
1121     bool
1122     Time::set (void) NOEXCEPT
1123     {
1124         struct timespec now;
1125
1126         errno = 0;
1127         if (clock_gettime (clockid_of_flags (this->id, this->variant), &now)
1128             == -1)
1129         {
1130             this->err = errno;
1131             this->unset ();
1132
1133             return false;
1134         }
1135         this->err   = 0;
1136         this->value = now;
1137
1138         return true;
1139     }
1140
1141     Time &
1142     Time::add (const time_t sec, const long nsec) NOEXCEPT
1143     {
1144         this->value.tv_sec  += sec;
1145         this->value.tv_nsec += nsec;
1146
1147         this->carry_nsec ();
1148
1149         return *this;
1150     }
1151
1152     Time &
1153     Time::subtract (const time_t sec, const long nsec) NOEXCEPT
1154     {
1155         this->value.tv_sec  -= sec;
1156         this->value.tv_nsec -= nsec;
1157
1158         this->carry_nsec ();
1159
1160         return *this;
1161     }
1162
1163     Time &
1164     Time::scale (const int64_t factor) NOEXCEPT
1165     {
1166         this->value.tv_sec  *= factor;
1167         this->value.tv_nsec *= factor;
1168
1169         this->carry_nsec ();
1170
1171         return *this;
1172     }
1173
1174     /*
1175      * Below division code purposely does not attempt to handle divide-
1176      * by-zero just as any other C++ division function does. It is up to
1177      * the caller to ensure that the divisor is not zero.
1178      */
1179     Time &
1180     Time::divide (const int64_t divisor) NOEXCEPT
1181     {
1182         const long   sec  = static_cast<long>    (this->value.tv_sec );
1183         int64_t      nsec = static_cast<int64_t> (this->value.tv_nsec);
1184         const ldiv_t div  = ldiv (sec, divisor);
1185
1186         if (div.rem != 0) {
1187             nsec += div.rem * TIME_CONST_FACTOR_NANO;
1188         }
1189
1190         nsec /= divisor;
1191
1192         this->value.tv_sec  = static_cast<time_t> (div.quot);
1193         this->value.tv_nsec = static_cast<long>   (nsec);
1194
1195         this->carry_nsec ();
1196
1197         return *this;
1198     }
1199
1200     boost::optional<std::string>
1201     Time::format_iso8601 (const bool utc,
1202                           const bool date,
1203                           const bool time,
1204                           const bool tz) const
1205     {
1206         time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
1207         struct tm tm;
1208
1209         if (breakdown (&this->value.tv_sec, &tm) == NULL) {
1210             return boost::none;
1211         }
1212
1213         return ::format_iso8601 (tm, date, time, tz);
1214     }
1215
1216     boost::optional<std::string>
1217     Time::make_nice_time (void) const
1218     {
1219         /* XXX the cast below results in loss of precision with 64 bit time_t! */
1220         return ::make_nice_time (static_cast<int> (this->value.tv_sec));
1221     }
1222
1223     boost::optional<std::string>
1224     Time::format_full_time (void) const
1225     { return ::format_full_time (this->value.tv_sec); }
1226
1227     boost::optional<std::string>
1228     Time::format_date (void) const
1229     { return ::format_date (this->value.tv_sec); }
1230
1231     boost::optional<Time>
1232     now (const enum type::id id, const enum type::variant var) NOEXCEPT
1233     {
1234         Time ret (id, var);
1235
1236         if (!ret.set ()) {
1237             return boost::none;
1238         }
1239
1240         return ret;
1241     }
1242
1243     Time
1244     zero (const enum type::id id, const enum type::variant var) NOEXCEPT
1245     { return Time (id, var); }
1246
1247     int
1248     compare (const Time &t1, const Time &t2) NOEXCEPT
1249     {
1250         if (t1.value.tv_sec < t2.value.tv_sec) {
1251             return -1;
1252         }
1253
1254         if (t1.value.tv_sec > t2.value.tv_sec) {
1255             return 1;
1256         }
1257
1258         if (t1.value.tv_nsec < t2.value.tv_nsec) {
1259             return -1;
1260         }
1261
1262         if (t1.value.tv_nsec > t2.value.tv_nsec) {
1263             return 1;
1264         }
1265
1266         return 0;
1267     }
1268
1269     boost::optional<Time>
1270     time_of_iso8601 (const std::string        &s,
1271                      const bool                date,
1272                      const bool                time,
1273                      const bool                tz,
1274                      const enum type::id       id,
1275                      const enum type::variant  var) NOEXCEPT
1276     {
1277         boost::optional<struct tm> tm = scan_iso8601 (s, date, time, tz);
1278
1279         if (!tm) {
1280             return boost::none;
1281         }
1282
1283         try {
1284             return Time (*tm, id, var);
1285         }
1286         catch (conversion_error &_unused) { }
1287
1288         return boost::none;
1289     }
1290
1291 } /* [namespace clock] */
1292
1293 } /* [namespace I2n] */