handle strftime() more defensively
[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 needed 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 here
894      * is always one character shorter.
895      */
896     if (strftime (start, iso8601::bufsize-1, format, &tmp) == 0)
897     {
898         return std::string ();
899     }
900
901     buf [iso8601::bufsize-1] = '\0'; /* Just in case. */
902
903     return std::string (buf);
904 }
905
906 typedef struct tm * (*time_breakdown_fn) (const time_t *, struct tm *);
907
908 /**
909  * @brief         Format a UNIX timestamp according to ISO-8601. Converts
910  *                to broken down time first.
911  *
912  * @param t       Time to format as broken-down \c struct tm.
913  * @param date    Include the day part ([-]YYYY-MM-DD).
914  * @param time    Include the time part (hh:mm:ss).
915  * @param tz      Include the timezone ([±]ZZZZ); only heeded if
916  *                \c time is requested as well.
917  *
918  * @return        The formatted timestamp.
919  */
920 std::string format_iso8601 (time_t t, const bool utc, const bool date,
921                             const bool time, const bool tz)
922 {
923     time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
924     struct tm tm;
925
926     errno = 0;
927     if (breakdown (&t, &tm) == NULL) {
928         return std::string ("error analyzing timestamp: ") + strerror (errno);
929     }
930
931     return format_iso8601 (tm, date, time, tz);
932 }
933
934 /**
935  * @brief         Read a ISO-8601 formatted date stamp into broken down time.
936  *
937  * @param s       String containing the timestamp.
938  *
939  * @return        \c boost::none if the input string was \c NULL or malformed,
940  *                an optional \c struct tm with the extracted values otherwise.
941  */
942 boost::optional<struct tm>
943 scan_iso8601 (const char *s,
944               const bool date, const bool time, const bool tz) NOEXCEPT
945 {
946     struct tm tm;
947     const char *format = iso8601::pick_fmt (date, time, tz, true);
948     const char *start = s;
949     bool negyear = false;
950
951     if (s == NULL) {
952         return boost::none;
953     }
954
955     switch (s [0]) {
956         case '\0': {
957             return boost::none;
958             break;
959         }
960         /*
961          * Contrary to what the man page indicates, strptime(3) is *not*
962          * the inverse operation of strftime(3)! The later correctly formats
963          * negative year numbers with the %F modifier wheres the former trips
964          * over the sign character.
965          */
966         case '-': {
967             negyear = true;
968             start++;
969             break;
970         }
971         default: {
972             break;
973         }
974     }
975
976     memset (&tm, 0, sizeof (tm));
977
978     if (strptime (start, format, &tm) == NULL) {
979         return boost::none;
980     }
981
982     if (negyear) {
983         tm.tm_year = flip_tm_year (tm.tm_year);
984     }
985
986     return tm;
987 }
988
989 /**
990  * @brief         Format a \c struct timespec in the schema established by
991  *                time(1): “3m14.159s”.
992  *
993  * @param ts      The time spec to format.
994  *
995  * @return        \c boost:none in case of error during formatting, an optional
996  *                \c std::string otherwise.
997  */
998 boost::optional<std::string>
999 format_min_sec_msec (const struct timespec &ts)
1000 {
1001     char ms [4] = { '\0', '\0', '\0', '\0' };
1002
1003     if (snprintf (ms, 4, "%.3ld", ts.tv_nsec / 1000000) < 0) {
1004         return boost::none;
1005     }
1006
1007     const time_t min = ts.tv_sec / 60;
1008     const time_t sec = ts.tv_sec - min * 60;
1009
1010     return I2n::to_string (min) + "m"
1011          + I2n::to_string (sec) + "."
1012          + ms + "s"
1013          ;
1014 }
1015
1016 namespace I2n {
1017
1018 namespace clock {
1019
1020     namespace {
1021
1022         static inline clockid_t
1023         clockid_of_flags (const enum type::id      id,
1024                           const enum type::variant var) NOEXCEPT
1025         {
1026             clockid_t cid = CLOCK_MONOTONIC_COARSE;
1027
1028             switch (id) {
1029
1030                 default:
1031                 case type::mono: {
1032                     switch (var) {
1033                         default: {
1034                             break;
1035                         }
1036                         case type::raw: {
1037                             cid = CLOCK_MONOTONIC_RAW;
1038                             break;
1039                         }
1040                         case type::exact: {
1041                             cid = CLOCK_MONOTONIC;
1042                             break;
1043                         }
1044                     }
1045                     break;
1046                 }
1047
1048                 case type::real: {
1049                     if (var == type::exact) {
1050                         cid = CLOCK_REALTIME;
1051                     } else {
1052                         cid = CLOCK_REALTIME_COARSE;
1053                     }
1054                     break;
1055                 }
1056
1057                 case type::boot: {
1058                     if (var & type::exact) {
1059                         cid = CLOCK_BOOTTIME;
1060                     }
1061                     break;
1062                 }
1063
1064                 case type::cpu: {
1065                     if (var == type::thread) {
1066                         cid = CLOCK_THREAD_CPUTIME_ID;
1067                     } else {
1068                         cid = CLOCK_PROCESS_CPUTIME_ID;
1069                     }
1070                     break;
1071                 }
1072             } /* [switch id] */
1073
1074             return cid;
1075         }
1076
1077         static const struct timespec zero_time = { 0, 0 };
1078
1079     } /* [namespace] */
1080
1081     Time::Time (const enum type::id id,
1082                 const enum type::variant var) NOEXCEPT
1083         : value   (zero_time)
1084         , id      (id)
1085         , variant (var)
1086         , err     (0)
1087     { }
1088
1089     /*
1090      * Ctor from *struct tm*. On 32 bit systems the conversion to *time_t* will
1091      * fail with years outside the range from epoch to 2038.
1092      */
1093     Time::Time (const struct tm          &tm,
1094                 const enum type::id       id,
1095                 const enum type::variant  var)
1096     {
1097         struct tm tmp_tm; /* dummy for mktime(3) */
1098         Time   tmp_time;
1099
1100         memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
1101
1102         errno = 0;
1103         const time_t t = mktime (&tmp_tm);
1104         if (t == - 1) { /* Glibc does not set errno on out-of-range here! */
1105             const char *datestr = asctime (&tm);
1106             throw conversion_error (errno,
1107                                     std::string ("mktime: from struct tm {")
1108                                     + std::string (datestr, 0, strlen(datestr)-1)
1109                                     + "}");
1110         }
1111
1112         tmp_time = Time (t, 0l, id, var);
1113
1114         this->swap (tmp_time);
1115     }
1116
1117     int64_t
1118     Time::as_nanosec (void) const NOEXCEPT
1119     {
1120         return int64_t (this->value.tv_sec) * TIME_CONST_FACTOR_NANO
1121              + this->value.tv_nsec;
1122     }
1123
1124     long
1125     Time::as_nanosec_L (void) const NOEXCEPT /* likely to overflow */
1126     { return static_cast<long>(this->as_nanosec ()); }
1127
1128     Time &
1129     Time::operator= (Time t2) NOEXCEPT
1130     {
1131         this->swap (t2);
1132
1133         return *this;
1134     }
1135
1136     Time &
1137     Time::operator= (struct timespec ts) NOEXCEPT
1138     {
1139         std::swap (this->value, ts);
1140         this->id      = clock::type::mono;
1141         this->variant = clock::type::dflt;
1142         this->err     = 0;
1143
1144         return *this;
1145     }
1146
1147     void
1148     Time::unset (void) NOEXCEPT
1149     { this->value = zero_time; }
1150
1151     bool
1152     Time::set (void) NOEXCEPT
1153     {
1154         struct timespec now;
1155
1156         errno = 0;
1157         if (clock_gettime (clockid_of_flags (this->id, this->variant), &now)
1158             == -1)
1159         {
1160             this->err = errno;
1161             this->unset ();
1162
1163             return false;
1164         }
1165         this->err   = 0;
1166         this->value = now;
1167
1168         return true;
1169     }
1170
1171     Time &
1172     Time::add (const time_t sec, const long nsec) NOEXCEPT
1173     {
1174         this->value.tv_sec  += sec;
1175         this->value.tv_nsec += nsec;
1176
1177         this->carry_nsec ();
1178
1179         return *this;
1180     }
1181
1182     Time &
1183     Time::subtract (const time_t sec, const long nsec) NOEXCEPT
1184     {
1185         this->value.tv_sec  -= sec;
1186         this->value.tv_nsec -= nsec;
1187
1188         this->carry_nsec ();
1189
1190         return *this;
1191     }
1192
1193     Time &
1194     Time::scale (const int64_t factor) NOEXCEPT
1195     {
1196         this->value.tv_sec  *= factor;
1197         this->value.tv_nsec *= factor;
1198
1199         this->carry_nsec ();
1200
1201         return *this;
1202     }
1203
1204     /*
1205      * Below division code purposely does not attempt to handle divide-
1206      * by-zero just as any other C++ division function does. It is up to
1207      * the caller to ensure that the divisor is not zero.
1208      */
1209     Time &
1210     Time::divide (const int64_t divisor) NOEXCEPT
1211     {
1212         const long   sec  = static_cast<long>    (this->value.tv_sec );
1213         int64_t      nsec = static_cast<int64_t> (this->value.tv_nsec);
1214         const ldiv_t div  = ldiv (sec, divisor);
1215
1216         if (div.rem != 0) {
1217             nsec += div.rem * TIME_CONST_FACTOR_NANO;
1218         }
1219
1220         nsec /= divisor;
1221
1222         this->value.tv_sec  = static_cast<time_t> (div.quot);
1223         this->value.tv_nsec = static_cast<long>   (nsec);
1224
1225         this->carry_nsec ();
1226
1227         return *this;
1228     }
1229
1230     boost::optional<std::string>
1231     Time::format_iso8601 (const bool utc,
1232                           const bool date,
1233                           const bool time,
1234                           const bool tz) const
1235     {
1236         time_breakdown_fn breakdown = utc ? gmtime_r : localtime_r;
1237         struct tm tm;
1238
1239         if (breakdown (&this->value.tv_sec, &tm) == NULL) {
1240             return boost::none;
1241         }
1242
1243         return ::format_iso8601 (tm, date, time, tz);
1244     }
1245
1246     std::string
1247     Time::make_nice_time (void) const
1248     {
1249         /* XXX the cast below results in loss of precision with 64 bit time_t! */
1250         return ::make_nice_time (static_cast<int> (this->value.tv_sec));
1251     }
1252
1253     std::string
1254     Time::format_full_time (void) const
1255     { return ::format_full_time (this->value.tv_sec); }
1256
1257     std::string
1258     Time::format_date (void) const
1259     { return ::format_date (this->value.tv_sec); }
1260
1261     boost::optional<Time>
1262     now (const enum type::id id, const enum type::variant var) NOEXCEPT
1263     {
1264         Time ret (id, var);
1265
1266         if (!ret.set ()) {
1267             return boost::none;
1268         }
1269
1270         return ret;
1271     }
1272
1273     Time
1274     zero (const enum type::id id, const enum type::variant var) NOEXCEPT
1275     { return Time (id, var); }
1276
1277     int
1278     compare (const Time &t1, const Time &t2) NOEXCEPT
1279     {
1280         if (t1.value.tv_sec < t2.value.tv_sec) {
1281             return -1;
1282         }
1283
1284         if (t1.value.tv_sec > t2.value.tv_sec) {
1285             return 1;
1286         }
1287
1288         if (t1.value.tv_nsec < t2.value.tv_nsec) {
1289             return -1;
1290         }
1291
1292         if (t1.value.tv_nsec > t2.value.tv_nsec) {
1293             return 1;
1294         }
1295
1296         return 0;
1297     }
1298
1299     boost::optional<Time>
1300     time_of_iso8601 (const std::string        &s,
1301                      const bool                date,
1302                      const bool                time,
1303                      const bool                tz,
1304                      const enum type::id       id,
1305                      const enum type::variant  var) NOEXCEPT
1306     {
1307         boost::optional<struct tm> tm = scan_iso8601 (s, date, time, tz);
1308
1309         if (!tm) {
1310             return boost::none;
1311         }
1312
1313         try {
1314             return Time (*tm, id, var);
1315         }
1316         catch (conversion_error &_unused) { }
1317
1318         return boost::none;
1319     }
1320
1321 } /* [namespace clock] */
1322
1323 } /* [namespace I2n] */