Line data Source code
1 : /** \file timeUtils.hpp
2 : * \brief Utilities for working with time
3 : *
4 : * \author Jared R. Males (jaredmales@gmail.com)
5 : *
6 : * \ingroup utils_files
7 : *
8 : */
9 :
10 : //***********************************************************************//
11 : // Copyright 2015-2020 Jared R. Males (jaredmales@gmail.com)
12 : //
13 : // This file is part of mxlib.
14 : //
15 : // mxlib is free software: you can redistribute it and/or modify
16 : // it under the terms of the GNU General Public License as published by
17 : // the Free Software Foundation, either version 3 of the License, or
18 : // (at your option) any later version.
19 : //
20 : // mxlib is distributed in the hope that it will be useful,
21 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
22 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 : // GNU General Public License for more details.
24 : //
25 : // You should have received a copy of the GNU General Public License
26 : // along with mxlib. If not, see <http://www.gnu.org/licenses/>.
27 : //***********************************************************************//
28 :
29 : #ifndef timeUtils_hpp
30 : #define timeUtils_hpp
31 :
32 : #include <time.h>
33 : #include <sys/time.h>
34 : #include <cmath>
35 :
36 : #include <thread>
37 : #include <chrono>
38 :
39 : #include <iostream>
40 :
41 : #include "../ioutils/stringUtils.hpp"
42 : #include "../astro/sofa.hpp"
43 :
44 : namespace mx
45 : {
46 : namespace sys
47 : {
48 :
49 : namespace timeUtilsDetail
50 : {
51 :
52 : /** \cond */
53 : /// Function signature for the broken-down UTC time operation used by time utilities.
54 : using gmtimeRT = tm *(*)( const time_t *, tm * );
55 :
56 : /// Function signature for the SOFA TAI-minus-UTC operation used by time utilities.
57 : using iauDatT = int ( * )( int, int, int, double, double * );
58 :
59 : /// Function signature for the SOFA calendar-to-Julian-date operation used by time utilities.
60 : using iauCal2jdT = int ( * )( int, int, int, double *, double * );
61 :
62 : /// Resettable external operations used to exercise time-conversion failures deterministically.
63 : struct operations
64 : {
65 : gmtimeRT gmtimeR; ///< Broken-down UTC time operation.
66 : iauDatT iauDat; ///< TAI-minus-UTC operation.
67 : iauCal2jdT iauCal2jd; ///< Calendar-to-Julian-date operation.
68 : };
69 :
70 : /// Access the process-wide time utility operation table.
71 : operations &operationsInstance();
72 :
73 : /// Restore the time utility operation table to its production functions.
74 : void resetOperations();
75 : /** \endcond */
76 :
77 : } // namespace timeUtilsDetail
78 :
79 : /// Get the current system time in seconds.
80 : /** Uses timespec, so nanosecond resolution is possible.
81 : *
82 : * \tparam typeT is the type to return the time in [default=double].
83 : * must have cast from integer types, and + and / operators defined.
84 : * \tparam clk_id is the sys/time.h clock identifier [default=CLOCK_REALTIME]
85 : *
86 : * \retval typeT containing the current time in seconds
87 : *
88 : * \ingroup timeutils
89 : */
90 : template <typename typeT = double, clockid_t clk_id = CLOCK_REALTIME>
91 1 : typeT get_curr_time( timespec &tsp /**<[out] a timespec to populate with the current time */ )
92 : {
93 1 : clock_gettime( clk_id, &tsp );
94 :
95 1 : return ( (typeT)tsp.tv_sec ) + ( (typeT)tsp.tv_nsec ) / 1e9;
96 : }
97 :
98 : // Specialization for most common use case.
99 : template <>
100 : double get_curr_time<double, CLOCK_REALTIME>( timespec &tsp );
101 :
102 : /// Get the current system time in seconds.
103 : /** Uses timespec, so nanosecond resolution is possible. This version creates a timespec internally.
104 : *
105 : * \overload
106 : *
107 : * \tparam typeT is the type to return the time in [default=double].
108 : * must have cast from integer types, and + and / operators defined.
109 : * \tparam clk_id is the sys/time.h clock identifier [default=CLOCK_REALTIME]
110 : *
111 : * \retval typeT containing the current time in seconds
112 : *
113 : * \ingroup timeutils
114 : */
115 : template <typename typeT = double, clockid_t clk_id = CLOCK_REALTIME>
116 : typeT get_curr_time()
117 : {
118 : struct timespec tsp;
119 : return get_curr_time<typeT, clk_id>( tsp );
120 : }
121 :
122 : // Specialization for most common use case.
123 : template <>
124 : double get_curr_time<double, CLOCK_REALTIME>();
125 :
126 : /// Sleep for a specified period in seconds.
127 : /**
128 : * \ingroup timeutils_sleep
129 : */
130 : void sleep( unsigned sec /**< [in] the number of seconds to sleep. */ );
131 :
132 : /// Sleep for a specified period in milliseconds.
133 : /**
134 : * \ingroup timeutils_sleep
135 : */
136 : void milliSleep( unsigned msec /**< [in] the number of milliseconds to sleep. */ );
137 :
138 : /// Sleep for a specified period in microseconds.
139 : /**
140 : * \ingroup timeutils_sleep
141 : */
142 : void microSleep( unsigned usec /**< [in] the number of microseconds to sleep. */ );
143 :
144 : /// Sleep for a specified period in nanoseconds.
145 : /**
146 : * \ingroup timeutils_sleep
147 : */
148 : void nanoSleep( unsigned nsec /**< [in] the number of microseconds to sleep. */ );
149 :
150 : /// Adds a time offset to an existing timespec
151 : /** The offset is specified in nanoseconds, which can be greater than 1e9.
152 : *
153 : * \ingroup timeutils
154 : */
155 : void timespecAddNsec( timespec &ts, ///< [in.out] the time to add to
156 : unsigned nsec ///< [in] the number of nanoseconds to add to ts.
157 : );
158 :
159 : /** Parse a string of format hh:mm:ss.s
160 : * Breaks a time string into constituent parts. Handles -h by distributing the sign to m and s.
161 : *
162 : * \tparam floatT is a floating point type
163 : *
164 : * \ingroup timeutils
165 : */
166 : template <typename floatT>
167 8 : void parse_hms( floatT &h, ///< [out] the hour component coverted to floatT
168 : floatT &m, ///< [out] the minute component converted to floatT
169 : floatT &s, ///< [out] the second component converted to floatT
170 : const std::string &hmsstr ///< [in] a string of format hh:mm:ss.s where ss.s can be of any precision
171 : )
172 : {
173 : int st, en;
174 :
175 8 : int sgn = 1;
176 :
177 8 : st = 0;
178 8 : en = hmsstr.find( ':', st );
179 :
180 8 : h = ioutils::stoT<floatT>( hmsstr.substr( st, en - st ).c_str() );
181 :
182 : // Check for negative
183 8 : if( std::signbit( h ) )
184 1 : sgn = -1;
185 :
186 8 : st = en + 1;
187 :
188 8 : en = hmsstr.find( ':', st );
189 :
190 8 : m = sgn * ioutils::stoT<floatT>( hmsstr.substr( st, en - st ) );
191 :
192 8 : st = en + 1;
193 :
194 16 : s = sgn * ioutils::stoT<floatT>( hmsstr.substr( st, hmsstr.length() - st ).c_str() );
195 8 : }
196 :
197 : /// Converts a Gregorian calendar date into modified Julian date (MJD).
198 : /** Uses the SOFA function iauCal2jd. This is not a template in floating point
199 : * because SOFA is always double precision.
200 : *
201 : * \retval double containing the MJD
202 : * \retval <0 on error (-1 = bad year, -2 = bad month, -3 = bad day)
203 : *
204 : * \ingroup timeutils
205 : */
206 : double Cal2mjd( int yr, ///< [in] Gregorian calendar year
207 : int mon, ///< [in] Gregorian calendar month
208 : int day, ///< [in] Gregorian calendar day
209 : int hr, ///< [in] Gregorian calendar hour
210 : int min, ///< [in] Gregorian calendar minute
211 : double sec ///< [in] Gregorian calendar second
212 : );
213 :
214 : /// Parse an ISO8601 date of the form "YYYY-MM-DDTHH:MM:SS.S" into the individual components.
215 : /** Parsing is currently only for the exact form above, which is the form in a FITS file.
216 : * See https://en.wikipedia.org/?title=ISO_8601.
217 : *
218 : * \returns 0 on success
219 : * \returns -4 if fdate is not long enough
220 : *
221 : * \ingroup timeutils
222 : */
223 : int ISO8601dateBreakdown( int &yr, ///< [out] Gregorian calendar year
224 : int &mon, ///< [out] Gregorian calendar month
225 : int &day, ///< [out] Gregorian calendar day
226 : int &hr, ///< [out] Gregorian calendar hour
227 : int &min, ///< [out] Gregorian calendar minute
228 : double &sec, ///< [out] Gregorian calendar second
229 : const std::string &fdate ///< [in] is a standard ISO8601 date string
230 : );
231 :
232 : /// Parse an ISO8601 date of the form "YYYY-MM-DDTHH:MM:SS.S" and return the modified Julian date (MJD)
233 : /** Parsing is currently only for the exact form above, which is the form in a FITS file.
234 : * See https://en.wikipedia.org/?title=ISO_8601.
235 : * After parsing calls Cal2mjd.
236 : *
237 : * \ingroup timeutils
238 : */
239 : double ISO8601date2mjd( const std::string &fdate /**<[in] a standard ISO8601 date string*/ );
240 :
241 : /// Get a date-time string in ISO 8601 format
242 : /** For recognized time types, returns a string in the ISO 8601 format:
243 : * YYYY-MM-DDYHH:MM:SS.SS, with optional timezone designation such as Z or +00:00.
244 : *
245 : * \tparam timeT is the time type
246 : *
247 : * \retval std::string containing the format date/time
248 : * \retval empty string if the input cannot be converted to broken-down UTC time
249 : *
250 : * \ingroup timeutils
251 : */
252 : template <typename timeT>
253 : std::string ISO8601DateTimeStr( const timeT &timeIn, ///< [in] the input time
254 : int timeZone = 0 ///< [in] [optional] specifies whether to include a timezone
255 : ///< designation. 0=> none, 1=> letter, 2=>offset.
256 : );
257 :
258 : /// Get a date-time string in ISO 8601 format for time_t
259 : /** Returns a string in the ISO 8601 format:
260 : * YYYY-MM-DDYHH:MM:SS, with optional timezone designation such as Z or +00:00.
261 : *
262 : * \retval std::string containing the format date/time
263 : * \retval empty string if the input cannot be converted to broken-down UTC time
264 : *
265 : * \ingroup timeutils
266 : */
267 : template <>
268 : std::string ISO8601DateTimeStr<time_t>( const time_t &timeIn, ///< [in] the input time
269 : int timeZone ///< [in] [optional] specifies whether to include a timezone
270 : ///< designation. 0=> none, 1=> letter, 2=>offset.
271 : );
272 :
273 : /// Get a date-time string in ISO 8601 format for timespec
274 : /** Returns a string in the ISO 8601 format:
275 : * YYYY-MM-DDYHH:MM:SS.SSSSSSSSS, with optional timezone designation such as Z or +00:00.
276 : *
277 : * \retval std::string containing the format date/time
278 : * \retval empty string if the input cannot be converted to broken-down UTC time
279 : *
280 : * \ingroup timeutils
281 : */
282 : template <>
283 : std::string ISO8601DateTimeStr<timespec>( const timespec &timeIn, ///< [in] the input time
284 : int timeZone ///< [in] [optional] specifies whether to include a timezone
285 : ///< designation. 0=> none, 1=> letter, 2=>offset.
286 : );
287 :
288 : /// Get a date-time string in ISO 8601 format for the current UTC time
289 : /** Returns a string in the ISO 8601 format:
290 : * YYYY-MM-DDYHH:MM:SS.SS, with optional timezone designation such as Z or +00:00.
291 : *
292 : * \overload
293 : *
294 : * \retval std::string containing the format date/time
295 : * \retval empty string if the current time cannot be converted to broken-down UTC time
296 : *
297 : * \ingroup timeutils
298 : */
299 :
300 : std::string ISO8601DateTimeStr(int timeZone = 0 /**< [in] [optional] specifies whether to include a timezone designation. 0=> none, 1=> letter, 2=>offset.*/);
301 :
302 : /// Get a date-time string in ISO 8601 format for an MJD
303 : /** Returns a string in the ISO 8601 format:
304 : * YYYY-MM-DDYHH:MM:SS.SSSSSSSSS, with optional timezone designation such as Z or +00:00.
305 : *
306 : * \retval std::string containing the format date/time
307 : *
308 : * \ingroup timeutils
309 : */
310 : std::string ISO8601DateTimeStrMJD(
311 : const double &timeIn, ///< [in] the input time
312 : int timeZone = 0 ///< [in] specifies whether to include a timezone designation. 0=> none, 1=> letter, 2=>offset.
313 : );
314 : /// Get a timestamp string in the form YYYYMMDDHHMMSS.SSSSSSSSS
315 : /** Assumes the input timespec is in UTC.
316 : *
317 : * \returns 0 on success
318 : * \returns -1 on error.
319 : *
320 : * \ingroup timeutils
321 : */
322 : int timeStamp( std::string &tstamp, ///< [out] the string to hold the formatted time
323 : timespec &ts ///< [in] the timespec from which to produce the timestamp string
324 : );
325 :
326 : /// Convert a UTC timespec to TAI modified Julian date
327 : /** Converts a timespec assumed to be in <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">Coordinated
328 : * Universal Time (UTC)</a> to a <a href="https://en.wikipedia.org/wiki/Julian_day">
329 : * Modified Julian Date (MJD)</a> in
330 : * <a href="https://en.wikipedia.org/wiki/International_Atomic_Time">International Atomic Time (TAI)</a>.
331 : *
332 : * \retval 1 SOFA dubious year [see SOFA documentation for iauDat]
333 : * \retval 0 success
334 : * \retval -1 SOFA bad year [see SOFA documentation for iauDat and iauCal2jd]
335 : * \retval -2 SOFA bad month [see SOFA documentation for iauDat and iauCal2jd]
336 : * \retval -3 SOFA bad day [see SOFA documentation for iauDat and iauCal2jd]
337 : * \retval -4 SOFA bad fractional day [see SOFA documentation for iauDat and iauCal2jd]
338 : * \retval -5 SOFA internal error [see SOFA documentation for iauDat and iauCal2jd]
339 : * \retval -10 gmtime_r returned error, check errno
340 : *
341 : * \ingroup timeutils
342 : */
343 : int timespecUTC2TAIMJD( double &djm, ///< [out] the modified Julian day number
344 : double &djmf, ///< [out] the fraction of the day
345 : const timespec &tsp, ///< [in] contains the UTC time
346 : tm *tm0 ///< [out] [optional] will be filled with the broken down UTC time
347 : );
348 :
349 : /// Calculate the mean time of two times given by timespecs
350 : /**
351 : * \returns the mean value of the inputs.
352 : */
353 : timespec meanTimespec( timespec ts1, ///< [in] one of the times to average
354 : timespec ts2 ///< [in] the other time to average
355 : );
356 :
357 : namespace tscomp
358 : {
359 :
360 : /// Timespec comparison operator \< (see caveats)
361 : /** Caveats:
362 : * - If the inputs are in UTC (or similar scale) this does not account for leap seconds
363 : * - Assumes that the `tv_nsec` field does not exceed 999999999 nanoseconds
364 : *
365 : * \returns true if tsL is earlier than tsR
366 : * \returns false otherwise
367 : *
368 : * \ingroup timeutils_tscomp
369 : */
370 : bool operator<( timespec const &tsL, ///< [in] the left hand side of the comparison
371 : timespec const &tsR ///< [in] the right hand side of the comparison
372 : );
373 :
374 : /// Timespec comparison operator \> (see caveats)
375 : /** Caveats:
376 : * - If the inputs are in UTC (or similar scale) this does not account for leap seconds
377 : * - Assumes that the `tv_nsec` field does not exceed 999999999 nanoseconds
378 : *
379 : * \returns true if tsL is later than tsR
380 : * \returns false otherwise
381 : *
382 : * \ingroup timeutils_tscomp
383 : */
384 : bool operator>( timespec const &tsL, ///< [in] the left hand side of the comparison
385 : timespec const &tsR ///< [in] the right hand side of the comparison
386 : );
387 :
388 : /// Timespec comparison operator == (see caveats)
389 : /** Caveats:
390 : * - If the inputs are in UTC (or similar scale) this does not account for leap seconds
391 : * - Assumes that the `tv_nsec` field does not exceed 999999999 nanoseconds
392 : *
393 : * \returns true if tsL is exactly the same as tsR
394 : * \returns false otherwise
395 : *
396 : * \ingroup timeutils_tscomp
397 : */
398 : bool operator==( timespec const &tsL, ///< [in] the left hand side of the comparison
399 : timespec const &tsR ///< [in] the right hand side of the comparison
400 : );
401 :
402 : /// Timespec comparison operator \<= (see caveats)
403 : /** Caveats:
404 : * - If the inputs are in UTC (or similar scale) this does not account for leap seconds
405 : * - Assumes that the `tv_nsec` field does not exceed 999999999 nanoseconds.
406 : *
407 : * \returns true if tsL is earlier than or exactly equal to tsR
408 : * \returns false otherwise
409 : *
410 : * \ingroup timeutils_tscomp
411 : */
412 : bool operator<=( timespec const &tsL, ///< [in] the left hand side of the comparison
413 : timespec const &tsR ///< [in] the right hand side of the comparison
414 : );
415 :
416 : /// Timespec comparison operator \>= (see caveats)
417 : /** Caveats:
418 : * - If the inputs are in UTC (or similar scale) this does not account for leap seconds
419 : * - Assumes that the `tv_nsec` field does not exceed 999999999 nanoseconds
420 : *
421 : * \returns true if tsL is exactly equal to or is later than tsR
422 : * \returns false otherwise
423 : *
424 : * \ingroup timeutils_tscomp
425 : */
426 : bool operator>=( timespec const &tsL, ///< [in] the left hand side of the comparison
427 : timespec const &tsR ///< [in] the right hand side of the comparison
428 : );
429 :
430 : } // namespace tscomp
431 :
432 : namespace tsop
433 : {
434 :
435 : /// Add an amount of time specified in seconds to a timespec
436 : /**
437 : * \returns the resulting timespec after addition
438 : */
439 : template <typename arithT>
440 2 : timespec operator+( timespec ts, ///< [in] the timespec to add to
441 : arithT add ///< [in] the seconds to add
442 : )
443 : {
444 2 : ts.tv_sec += floor( add );
445 :
446 2 : ts.tv_nsec += ( add - floor( add ) ) * 1e9;
447 :
448 2 : if( ts.tv_nsec >= 1e9 )
449 : {
450 1 : ts.tv_sec += 1;
451 1 : ts.tv_nsec -= 1e9;
452 : }
453 :
454 2 : return ts;
455 : }
456 :
457 : /// Subtract an amount of time specified in seconds from a timespec
458 : /**
459 : * \returns the resulting timespec after subtraction
460 : */
461 : template <typename arithT>
462 2 : timespec operator-( timespec ts, ///< [in] the timespec to subtract from
463 : arithT sub ///< [in] the seconds to subtract
464 : )
465 : {
466 2 : ts.tv_sec -= floor( sub );
467 :
468 2 : ts.tv_nsec -= ( sub - floor( sub ) ) * 1e9;
469 :
470 2 : if( ts.tv_nsec < 0 )
471 : {
472 1 : ts.tv_sec -= 1;
473 1 : ts.tv_nsec += 1e9;
474 : }
475 :
476 2 : return ts;
477 : }
478 :
479 : } // namespace tsop
480 :
481 : } // namespace sys
482 : } // namespace mx
483 :
484 : #endif // timeUtils_hpp
|