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 :
45 : namespace mx
46 : {
47 : namespace sys
48 : {
49 :
50 : /// Get the current system time in seconds.
51 : /** Uses timespec, so nanosecond resolution is possible.
52 : *
53 : * \tparam typeT is the type to return the time in [default=double].
54 : * must have cast from integer types, and + and / operators defined.
55 : * \tparam clk_id is the sys/time.h clock identifier [default=CLOCK_REALTIME]
56 : *
57 : * \retval typeT containing the current time in seconds
58 : *
59 : * Tests:
60 : * - Verify operation of get_curr_time. \ref tests_sys_timeUtils_get_curr_time "[test doc]"
61 : *
62 : * \ingroup timeutils
63 : */
64 : template <typename typeT = double, clockid_t clk_id = CLOCK_REALTIME>
65 : typeT get_curr_time( timespec &tsp /**<[out] a timespec to populate with the current time */ )
66 : {
67 : clock_gettime( clk_id, &tsp );
68 :
69 : return ( (typeT)tsp.tv_sec ) + ( (typeT)tsp.tv_nsec ) / 1e9;
70 : }
71 :
72 : // Specialization for most common use case.
73 : template <>
74 : double get_curr_time<double, CLOCK_REALTIME>( timespec &tsp );
75 :
76 : /// Get the current system time in seconds.
77 : /** Uses timespec, so nanosecond resolution is possible. This version creates a timespec internally.
78 : *
79 : * \overload
80 : *
81 : * \tparam typeT is the type to return the time in [default=double].
82 : * must have cast from integer types, and + and / operators defined.
83 : * \tparam clk_id is the sys/time.h clock identifier [default=CLOCK_REALTIME]
84 : *
85 : * \retval typeT containing the current time in seconds
86 : *
87 : * Tests:
88 : * - Verify operation of get_curr_time. \ref tests_sys_timeUtils_get_curr_time "[test doc]"
89 : *
90 : * \ingroup timeutils
91 : */
92 : template <typename typeT = double, clockid_t clk_id = CLOCK_REALTIME>
93 : typeT get_curr_time()
94 : {
95 : struct timespec tsp;
96 : return get_curr_time<typeT, clk_id>( tsp );
97 : }
98 :
99 : // Specialization for most common use case.
100 : template <>
101 : double get_curr_time<double, CLOCK_REALTIME>();
102 :
103 : /// Sleep for a specified period in seconds.
104 : /**
105 : * Tests:
106 : * - Verify operation of thread sleep functions. \ref tests_sys_timeUtils_sleep "[test doc]"
107 : *
108 : * \ingroup timeutils_sleep
109 : */
110 : void sleep( unsigned sec /**< [in] the number of seconds to sleep. */ );
111 :
112 : /// Sleep for a specified period in milliseconds.
113 : /**
114 : * Tests:
115 : * - Verify operation of thread sleep functions. \ref tests_sys_timeUtils_sleep "[test doc]"
116 : *
117 : * \ingroup timeutils_sleep
118 : */
119 : void milliSleep( unsigned msec /**< [in] the number of milliseconds to sleep. */ );
120 :
121 : /// Sleep for a specified period in microseconds.
122 : /**
123 : * Tests:
124 : * - Verify operation of thread sleep functions. \ref tests_sys_timeUtils_sleep "[test doc]"
125 : *
126 : * \ingroup timeutils_sleep
127 : */
128 : void microSleep( unsigned usec /**< [in] the number of microseconds to sleep. */ );
129 :
130 : /// Sleep for a specified period in nanoseconds.
131 : /**
132 : * Tests
133 : * - Verify operation of thread sleep functions. \ref tests_sys_timeUtils_sleep "[test doc]"
134 : *
135 : * \ingroup timeutils_sleep
136 : */
137 : void nanoSleep( unsigned nsec /**< [in] the number of microseconds to sleep. */ );
138 :
139 : /// Adds a time offset to an existing timespec
140 : /** The offset is specified in nanoseconds, which can be greater than 1e9.
141 : *
142 : * Tests:
143 : * - Verify operation of timespecAddNsec. \ref tests_sys_timeUtils_timespecAddNsec "[test doc]"
144 : *
145 : * \ingroup timeutils
146 : */
147 : void timespecAddNsec( timespec &ts, ///< [in.out] the time to add to
148 : unsigned nsec ///< [in] the number of nanoseconds to add to ts.
149 : );
150 :
151 : /** Parse a string of format hh:mm:ss.s
152 : * Breaks a time string into constituent parts. Handles -h by distributing the sign to m and s.
153 : *
154 : * \tparam floatT is a floating point type
155 : *
156 : * Tests
157 : * - Verify parsing of a formatted time string. \ref tests_sys_timeUtils_parse_hms "[test doc]"
158 : *
159 : * \ingroup timeutils
160 : */
161 : template <typename floatT>
162 4 : void parse_hms( floatT &h, ///< [out] the hour component coverted to floatT
163 : floatT &m, ///< [out] the minute component converted to floatT
164 : floatT &s, ///< [out] the second component converted to floatT
165 : const std::string &hmsstr ///< [in] a string of format hh:mm:ss.s where ss.s can be of any precision
166 : )
167 : {
168 : int st, en;
169 :
170 4 : int sgn = 1;
171 :
172 4 : st = 0;
173 4 : en = hmsstr.find( ':', st );
174 :
175 4 : h = ioutils::stoT<floatT>( hmsstr.substr( st, en - st ).c_str() );
176 :
177 : // Check for negative
178 4 : if( std::signbit( h ) )
179 1 : sgn = -1;
180 :
181 4 : st = en + 1;
182 :
183 4 : en = hmsstr.find( ':', st );
184 :
185 4 : m = sgn * ioutils::stoT<floatT>( hmsstr.substr( st, en - st ) );
186 :
187 4 : st = en + 1;
188 :
189 8 : s = sgn * ioutils::stoT<floatT>( hmsstr.substr( st, hmsstr.length() - st ).c_str() );
190 4 : }
191 :
192 : /// Converts a Gregorian calendar date into modified Julian date (MJD).
193 : /** Uses the SOFA function iauCal2jd. This is not a template in floating point
194 : * because SOFA is always double precision.
195 : *
196 : * \retval double containing the MJD
197 : * \retval <0 on error (-1 = bad year, -2 = bad month, -3 = bad day)
198 : *
199 : * Tests:
200 : * - Verify calculation of MJD. \ref tests_sys_timeUtils_Cal2mjd "[test doc]"
201 : *
202 : * \ingroup timeutils
203 : */
204 : double Cal2mjd( int yr, ///< [in] Gregorian calendar year
205 : int mon, ///< [in] Gregorian calendar month
206 : int day, ///< [in] Gregorian calendar day
207 : int hr, ///< [in] Gregorian calendar hour
208 : int min, ///< [in] Gregorian calendar minute
209 : double sec ///< [in] Gregorian calendar second
210 : );
211 :
212 : /// Parse an ISO8601 date of the form "YYYY-MM-DDTHH:MM:SS.S" into the individual components.
213 : /** Parsing is currently only for the exact form above, which is the form in a FITS file.
214 : * See https://en.wikipedia.org/?title=ISO_8601.
215 : *
216 : * \returns 0 on success
217 : * \returns -4 if fdate is not long enough
218 : *
219 : * Tests:
220 : * - Verify parsing of an ISO 8601 time string \ref tests_sys_timeUtils_ISO8601dateBreakdown "[test doc]"
221 : *
222 : * \ingroup timeutils
223 : */
224 : int ISO8601dateBreakdown( int &yr, ///< [out] Gregorian calendar year
225 : int &mon, ///< [out] Gregorian calendar month
226 : int &day, ///< [out] Gregorian calendar day
227 : int &hr, ///< [out] Gregorian calendar hour
228 : int &min, ///< [out] Gregorian calendar minute
229 : double &sec, ///< [out] Gregorian calendar second
230 : const std::string &fdate ///< [in] is a standard ISO8601 date string
231 : );
232 :
233 : /// Parse an ISO8601 date of the form "YYYY-MM-DDTHH:MM:SS.S" and return the modified Julian date (MJD)
234 : /** Parsing is currently only for the exact form above, which is the form in a FITS file.
235 : * See https://en.wikipedia.org/?title=ISO_8601.
236 : * After parsing calls Cal2mjd.
237 : *
238 : * Tests:
239 : * - Verify conversion of an ISO 8601 time string to MJD. \ref tests_sys_timeUtils_ISO8601date2mjd "[test doc]"
240 : *
241 : * \ingroup timeutils
242 : */
243 : double ISO8601date2mjd( const std::string &fdate /**<[in] a standard ISO8601 date string*/ );
244 :
245 : /// Get a date-time string in ISO 8601 format
246 : /** For recognized time types, returns a string in the ISO 8601 format:
247 : * YYYY-MM-DDYHH:MM:SS.SS, with optional timezone designation such as Z or +00:00.
248 : *
249 : * \tparam timeT is the time type
250 : *
251 : * \retval std::string containing the format date/time
252 : *
253 : * \ingroup timeutils
254 : */
255 : template <typename timeT>
256 : std::string ISO8601DateTimeStr( const timeT &timeIn, ///< [in] the input time
257 : int timeZone = 0 ///< [in] [optional] specifies whether to include a timezone
258 : ///< designation. 0=> none, 1=> letter, 2=>offset.
259 : );
260 :
261 : /// Get a date-time string in ISO 8601 format for time_t
262 : /** Returns a string in the ISO 8601 format:
263 : * YYYY-MM-DDYHH:MM:SS, with optional timezone designation such as Z or +00:00.
264 : *
265 : * \retval std::string containing the format date/time
266 : *
267 : * \ingroup timeutils
268 : */
269 : template <>
270 : std::string ISO8601DateTimeStr<time_t>( const time_t &timeIn, ///< [in] the input time
271 : int timeZone ///< [in] [optional] specifies whether to include a timezone
272 : ///< designation. 0=> none, 1=> letter, 2=>offset.
273 : );
274 :
275 : /// Get a date-time string in ISO 8601 format for timespec
276 : /** Returns a string in the ISO 8601 format:
277 : * YYYY-MM-DDYHH:MM:SS.SSSSSSSSS, with optional timezone designation such as Z or +00:00.
278 : *
279 : * \retval std::string containing the format date/time
280 : *
281 : * \ingroup timeutils
282 : */
283 : template <>
284 : std::string ISO8601DateTimeStr<timespec>( const timespec &timeIn, ///< [in] the input time
285 : int timeZone ///< [in] [optional] specifies whether to include a timezone
286 : ///< designation. 0=> none, 1=> letter, 2=>offset.
287 : );
288 :
289 : /// Get a date-time string in ISO 8601 format for the current UTC time
290 : /** Returns a string in the ISO 8601 format:
291 : * YYYY-MM-DDYHH:MM:SS.SS, with optional timezone designation such as Z or +00:00.
292 : *
293 : * \overload
294 : *
295 : * \retval std::string containing the format date/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 : timespec operator+( timespec ts, ///< [in] the timespec to add to
441 : arithT add ///< [in] the seconds to add
442 : )
443 : {
444 : ts.tv_sec += floor( add );
445 :
446 : ts.tv_nsec += ( add - floor( add ) ) * 1e9;
447 :
448 : if( ts.tv_nsec >= 1e9 )
449 : {
450 : ts.tv_sec += 1;
451 : ts.tv_nsec -= 1e9;
452 : }
453 :
454 : 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 : timespec operator-( timespec ts, ///< [in] the timespec to subtract from
463 : arithT sub ///< [in] the seconds to subtract
464 : )
465 : {
466 : ts.tv_sec -= floor( sub );
467 :
468 : ts.tv_nsec -= ( sub - floor( sub ) ) * 1e9;
469 :
470 : if( ts.tv_nsec < 0 )
471 : {
472 : ts.tv_sec -= 1;
473 : ts.tv_nsec += 1e9;
474 : }
475 :
476 : return ts;
477 : }
478 :
479 : } // namespace tsop
480 :
481 : } // namespace sys
482 : } // namespace mx
483 :
484 : #endif // timeUtils_hpp
|