Line data Source code
1 : /** \file fileTimes.hpp
2 : * \brief Utilities for working with file timestamps
3 : * \ingroup file_files
4 : */
5 :
6 : #ifndef file_fileTimes_hpp
7 : #define file_fileTimes_hpp
8 :
9 : #include <time.h>
10 : #include <cstring>
11 : #include <iostream>
12 : #include <format>
13 :
14 : #include <mx/mxlib.hpp>
15 :
16 : #include "../common/defaults.hpp"
17 :
18 : #include "../common/exceptions.hpp"
19 :
20 : namespace MagAOX
21 : {
22 : namespace file
23 : {
24 :
25 : #ifdef XWCTEST_NAMESPACE
26 : namespace XWCTEST_NAMESPACE
27 : {
28 : #endif
29 :
30 : /// Get the filename timestamp from the breakdown for a time.
31 : /** Fills in the \p tstamp string with the timestamp encoded as
32 : * \verbatim
33 : * YYYYMMDDHHMMSSNNNNNNNNN
34 : * \endverbatim
35 : *
36 : * \returns mx::error_t::noerror on success
37 : * \returns mx::error_t::format_error if std::format throws and exception
38 : * \returns mx::error_t::std_exception if any other exception other than bad_alloc is caught
39 : *
40 : * \returns throws nested mx::exception if std::bad_alloc is caught from std::format
41 : *
42 : */
43 : template <class verboseT = XWC_DEFAULT_VERBOSITY>
44 352 : mx::error_t timestamp( std::string &tstamp, /**< [out] the timestamp string*/
45 : const tm &uttime, /**< [in] the broken down time*/
46 : long ts_nsec /**< [in] the nanosecond*/
47 : )
48 : {
49 : try
50 : {
51 :
52 : // clang-format off
53 : #ifdef XWCTEST_TIMESTAMP_THROW_BAD_ALLOC
54 : throw std::bad_alloc(); // LCOV_EXCL_LINE
55 : #endif
56 : // clang-format on
57 :
58 : // clang-format off
59 : #ifdef XWCTEST_TIMESTAMP_THROW_FORMAT_ERROR
60 : throw std::format_error("testing format_error"); // LCOV_EXCL_LINE
61 : #endif
62 : // clang-format on
63 :
64 : // clang-format off
65 : #ifdef XWCTEST_TIMESTAMP_THROW_EXCEPTION
66 : throw std::exception(); // LCOV_EXCL_LINE
67 : #endif
68 : // clang-format on
69 :
70 343 : tstamp = std::format( "{:04}{:02}{:02}{:02}{:02}{:02}{:09}",
71 0 : uttime.tm_year + 1900,
72 0 : uttime.tm_mon + 1,
73 343 : uttime.tm_mday,
74 343 : uttime.tm_hour,
75 343 : uttime.tm_min,
76 343 : uttime.tm_sec,
77 : ts_nsec );
78 : }
79 12 : catch( const std::bad_alloc &e )
80 : {
81 12 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "from std::format") );
82 : }
83 6 : catch( const std::format_error &e )
84 : {
85 6 : return mx::error_report<verboseT>( mx::error_t::std_format_error,
86 15 : std::string( "from std::format: " ) + e.what() );
87 : }
88 6 : catch( const std::exception &e )
89 : {
90 9 : return mx::error_report<verboseT>( mx::error_t::std_exception, std::string( "from std::format: " ) + e.what() );
91 : }
92 :
93 343 : return mx::error_t::noerror;
94 : }
95 :
96 : /// Get the filename timestamp and the breakdown for a time.
97 : /** Fills in the \p tstamp string with the timestamp encoded as
98 : * \verbatim
99 : * YYYYMMDDHHMMSSNNNNNNNNN
100 : * \endverbatim
101 : * and the broken down `tm` structure \p uttime
102 : *
103 : * \returns mx::error_t::noerror on success
104 : * \returns mx::error_t::eoverflow if year is too big for gmtime_r
105 : * \returns mx::error_t::error if gmtime_r returns an error without setting errno
106 : * \returns mx::error_t::format_error if std::format throws and exception
107 : * \returns mx::error_t::std_exception if any other exception other than bad_alloc is caught in \ref
108 : * timestamp(std::string &, const tm&, long) "timestamp"
109 : *
110 : * \returns throws nested mx::exception if an exception is caught from \ref timestamp(std::string &, const tm&, long)
111 : * "timestamp" , which means std::bad_alloc was thrown
112 : *
113 : */
114 : template <typename verboseT = XWC_DEFAULT_VERBOSITY>
115 356 : mx::error_t timestamp( std::string &tstamp, /**< [out] the timestamp string*/
116 : tm &uttime, /**< [out] the broken down time*/
117 : time_t ts_sec, /**< [in] the unix time second*/
118 : long ts_nsec /**< [in] the nanosecond*/
119 : )
120 : {
121 356 : memset( &uttime, 0, sizeof( uttime ) );
122 :
123 356 : errno = 0;
124 356 : if( gmtime_r( &ts_sec, &uttime ) == 0 )
125 : {
126 : // clang-format off
127 : #ifdef XWCTEST_TIMESTAMP_GMTIME_OTHER
128 : errno = 0; // LCOV_EXCL_LINE
129 : #endif
130 : // clang-format on
131 :
132 4 : if( errno != 0 )
133 : {
134 9 : return mx::error_report<verboseT>( mx::errno2error_t( errno ),
135 3 : "error getting UT time (gmtime_r returned 0)" );
136 : }
137 : else
138 : {
139 2 : return mx::error_report<verboseT>( mx::error_t::error, "error getting UT time (gmtime_r returned 0)" );
140 : }
141 : }
142 :
143 : try
144 : {
145 352 : mx_error_return( timestamp<verboseT>( tstamp, uttime, ts_nsec ) );
146 : }
147 6 : catch( ... )
148 : {
149 6 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::exception) );
150 : }
151 : }
152 :
153 : /// Get the filename timestamp for a time.
154 : /** Fills in the \p tstamp string with the timestamp encoded as
155 : * \verbatim
156 : * YYYYMMDDHHMMSSNNNNNNNNN
157 : * \endverbatim
158 : *
159 : * \overload
160 : *
161 : * \returns mx::error_t::noerror on success
162 : * \returns mx::error_t::eoverflow if year is too big for gmtime_r
163 : * \returns mx::error_t::error if gmtime_r returns an error without setting errno
164 : * \returns mx::error_t::std_exception if an exception other than bad_alloc is caught in \ref timestamp(std::string &,
165 : * const tm&, long) "timestamp"
166 : *
167 : * \returns throws nested mx::exception if an exception is caught from \ref timestamp(std::string &, const tm&, long)
168 : * "timestamp" , which means std::bad_alloc was thrown
169 : *
170 : */
171 : template <class verboseT = XWC_DEFAULT_VERBOSITY>
172 4 : mx::error_t timestamp( std::string &tstamp, /**< [out] the timestamp string*/
173 : time_t ts_sec, /**< [in] the unix time second*/
174 : long ts_nsec /**< [in] the nanosecond*/
175 : )
176 : {
177 : tm uttime;
178 4 : memset( &uttime, 0, sizeof( uttime ) );
179 :
180 : try
181 : {
182 4 : mx_error_return( timestamp<verboseT>( tstamp, uttime, ts_sec, ts_nsec ) );
183 : }
184 2 : catch( ... )
185 : {
186 2 : std::throw_with_nested( mx::exception<verboseT>( mx::error_t::exception));
187 : }
188 : }
189 :
190 : /// Get the timestamp and the relative path based on a time
191 : /** Fills in the \p tstamp string with the timestamp encoded as
192 : * \verbatim
193 : * YYYYMMDDHHMMSSNNNNNNNNN
194 : * \endverbatim
195 : * and the \p relPath string with the format
196 : * \verbatim
197 : * yyyy_mm_dd
198 : * \endverbatim
199 : *
200 : * \returns 0 on success
201 : * \returns -1 if gmtime_r returns an error (from \ref timestamp)
202 : * \returns -2 if snprintf returns an error writing tstamp (from \ref timestamp)
203 : * \returns -3 if snprintf does not write enough characters to tstamp (from \ref timestamp)
204 : * \returns -4 if snprintf returns an error writing relPath
205 : * \returns -5 if snprintf does not write enough characters to relPath
206 : *
207 : */
208 : template <class verboseT = XWC_DEFAULT_VERBOSITY>
209 347 : mx::error_t fileTimeRelPath( std::string &tstamp, /**< [out] */
210 : std::string &relPath, /**< [out] */
211 : time_t ts_sec, /**< [in] the unix time second*/
212 : long ts_nsec /**< [in] the nanosecond*/
213 : )
214 : {
215 347 : if(ts_sec == 0 && ts_nsec == 0)
216 : {
217 2 : return mx::error_report<verboseT>( mx::error_t::invalidarg, "all 0 time");
218 : }
219 :
220 : tm uttime;
221 346 : memset( &uttime, 0, sizeof( uttime ) );
222 :
223 : try
224 : {
225 : // clang-format off
226 : #ifdef XWCTEST_FILETIMERELPATH_THROW_BAD_ALLOC
227 : throw std::bad_alloc(); // LCOV_EXCL_LINE
228 : #endif
229 : // clang-format on
230 :
231 : // clang-format off
232 : #ifdef XWCTEST_FILETIMERELPATH_THROW_FORMAT_ERROR
233 : throw std::format_error("testing format_error"); // LCOV_EXCL_LINE
234 : #endif
235 : // clang-format on
236 :
237 : // clang-format off
238 : #ifdef XWCTEST_FILETIMERELPATH_THROW_EXCEPTION
239 : throw std::exception(); // LCOV_EXCL_LINE
240 : #endif
241 : // clang-format on
242 :
243 343 : mx_error_check( timestamp<verboseT>( tstamp, uttime, ts_sec, ts_nsec ) );
244 :
245 339 : relPath = std::format( "{:04}_{:02}_{:02}", uttime.tm_year + 1900, uttime.tm_mon + 1, uttime.tm_mday );
246 :
247 339 : return mx::error_t::noerror;
248 : }
249 5 : catch( const std::bad_alloc &e )
250 : {
251 4 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "getting relPath") );
252 : }
253 2 : catch( const mx::exception<verboseT> & e ) // This is from previous bad_alloc
254 : {
255 4 : std::throw_with_nested( mx::exception<verboseT>(e.code(), "getting relPath") );
256 : }
257 2 : catch( const std::format_error &e )
258 : {
259 2 : return mx::error_report<verboseT>( mx::error_t::std_format_error,
260 5 : std::string( "from std::format: " ) + e.what() );
261 : }
262 2 : catch( const std::exception &e )
263 : {
264 2 : return mx::error_report<verboseT>( mx::error_t::std_exception, e.what() );
265 : }
266 :
267 : }
268 :
269 : /// Construct the filename and full relative path based on a time and a device name and extension
270 : /** Fills in the fileName string with the timestamp encoded as
271 : * \verbatim
272 : * devName_YYYYMMDDHHMMSSNNNNNNNNN.ext
273 : * \endverbatim
274 : * and the \p relPath string with the format
275 : * \verbatim
276 : * devName/YYYY_MM_DD
277 : * \endverbatim
278 : *
279 : * \overload
280 : *
281 : * \returns mx::error_t::noerror on success
282 : * \returns errors from fileTimeRelPath(std::string&,std::string&, time_t, long)
283 : * \returns mx::error_t::std_exception if an exception other than std::bad_alloc is caught
284 : * \throws nested mx::excpetion if std::bad_alloc is thrown
285 : */
286 : template <class verboseT = XWC_DEFAULT_VERBOSITY>
287 347 : mx::error_t fileTimeRelPath( std::string &fileName, /**< [out] the resulting file name*/
288 : std::string &relPath, /**< [out] the resulting relative path*/
289 : const std::string &devName, /**< [in] the device name part of the path. No '/'. */
290 : const std::string &ext, /**< [in] the extension part of the filename. No `.`. */
291 : time_t ts_sec, /**< [in] the unix time second*/
292 : long ts_nsec /**< [in] the nanosecond*/
293 : )
294 : {
295 347 : std::string tstamp, tmprelpath;
296 :
297 : try
298 : {
299 347 : mx_error_check( fileTimeRelPath<verboseT>( tstamp, tmprelpath, ts_sec, ts_nsec ) );
300 : }
301 4 : catch( ... ) // This is from previous bad_alloc
302 : {
303 4 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::exception));
304 : }
305 :
306 : try
307 : {
308 : // clang-format off
309 : #ifdef XWCTEST_FILETIMERELPATHSTRING_THROW_BAD_ALLOC
310 : throw std::bad_alloc(); // LCOV_EXCL_LINE
311 : #endif
312 : // clang-format on
313 :
314 : // clang-format off
315 : #ifdef XWCTEST_FILETIMERELPATHSTRING_THROW_EXCEPTION
316 : throw std::exception(); // LCOV_EXCL_LINE
317 : #endif
318 : // clang-format on
319 :
320 337 : relPath = devName + '/' + tmprelpath;
321 337 : fileName = devName + '_' + tstamp + '.' + ext;
322 :
323 337 : return mx::error_t::noerror;
324 : }
325 3 : catch( const std::bad_alloc &e )
326 : {
327 4 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "std::string assembling paths" ) );
328 : }
329 2 : catch( const std::exception &e )
330 : {
331 2 : return mx::error_report<verboseT>( mx::error_t::std_exception,
332 5 : std::string( "std::string assembling paths" ) + e.what() );
333 : }
334 350 : }
335 :
336 : /// Parse a standard XWCTk timestamp string
337 : /** Extracts the date components.
338 : *
339 : * The input must be exactly 23 characters long.
340 : *
341 : * No validity checks are done on the components.
342 : *
343 : * \returns mx::error_t::noerror on success
344 : * \returns mx::error_t::invalidarg if timestamp is not 23 characters long
345 : * \returns mx::error_t::std_out_of_range if std::out_of_range is thrown by std::substr
346 : * \returns mx::error_t::exception if any other exception other than bad_alloc is thrown by std::string::substr
347 : *
348 : * \throws nested mx::exception if std::bad_alloc is thrown
349 : *
350 : */
351 : template <class verboseT = XWC_DEFAULT_VERBOSITY>
352 111 : mx::error_t parseTimestamp( std::string &YYYY, /**< [out] the 4 digit year*/
353 : std::string &MM, /**< [out] the 2 digit month*/
354 : std::string &DD, /**< [out] the 2 digit day*/
355 : std::string &hh, /**< [out] the 2 digit hour*/
356 : std::string &mm, /**< [out] the 2 digit minute*/
357 : std::string &ss, /**< [out] the 2 digit second*/
358 : std::string &nn, /**< [out] the 9 digit nanosecond*/
359 : const std::string &tstamp /**< [in] the 23-digit timestamp */
360 : )
361 : {
362 : try
363 : {
364 : // clang-format off
365 : #ifdef XWCTEST_PARSETIMESTAMP_THROW_BAD_ALLOC
366 : throw std::bad_alloc(); // LCOV_EXCL_LINE
367 : #endif
368 : // clang-format on
369 :
370 : // clang-format off
371 : #ifdef XWCTEST_PARSETIMESTAMP_THROW_OUT_OF_RANGE
372 : throw std::out_of_range("testing out of range"); // LCOV_EXCL_LINE
373 : #endif
374 : // clang-format on
375 :
376 : // clang-format off
377 : #ifdef XWCTEST_PARSETIMESTAMP_THROW_EXCEPTION
378 : throw std::exception(); // LCOV_EXCL_LINE
379 : #endif
380 : // clang-format on
381 :
382 108 : if( tstamp.length() != 23 )
383 : {
384 4 : return mx::error_report<verboseT>( mx::error_t::invalidarg, "timestamp does not have 23 characters" );
385 : }
386 :
387 106 : YYYY = tstamp.substr( 0, 4 );
388 106 : MM = tstamp.substr( 4, 2 );
389 106 : DD = tstamp.substr( 6, 2 );
390 106 : hh = tstamp.substr( 8, 2 );
391 106 : mm = tstamp.substr( 10, 2 );
392 106 : ss = tstamp.substr( 12, 2 );
393 106 : nn = tstamp.substr( 14, 9 );
394 :
395 106 : return mx::error_t::noerror;
396 : }
397 4 : catch( const std::bad_alloc &e )
398 : {
399 2 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc));
400 : }
401 2 : catch( const std::out_of_range &e )
402 : {
403 2 : return mx::error_report<verboseT>( mx::error_t::std_out_of_range,
404 5 : std::string( "parsing timestamp" ) + e.what() );
405 : }
406 2 : catch( const std::exception &e )
407 : {
408 3 : return mx::error_report<verboseT>( mx::error_t::std_exception, std::string( "parsing timestamp" ) + e.what() );
409 : }
410 : }
411 :
412 : /// Parse a standard XWCTk timestamp filepath
413 : /** Extracts the device name and the date components.
414 : * The only restriction on the input \fname is that it be at least 23 characters long.
415 : * In this case it contains only the timestamp.
416 : *
417 : * No validity checks are done on the components (i.e. no check that the timestamp
418 : * is all numeric, no check on device name format).
419 : *
420 : * Examples of valid inputs are:
421 : * - `device_20241121063300000000000.txt`
422 : * - `/path/to/device_20241121063300000000000.txt`
423 : * - `20241121063300000000000`
424 : *
425 : * \returns 0 on success
426 : * \returns -1 on error
427 : *
428 : */
429 : template <class verboseT = XWC_DEFAULT_VERBOSITY>
430 116 : mx::error_t parseFilePath( std::string &devName, /**< [out] the device name */
431 : std::string &YYYY, /**< [out] the 4 digit year*/
432 : std::string &MM, /**< [out] the 2 digit month*/
433 : std::string &DD, /**< [out] the 2 digit day*/
434 : std::string &hh, /**< [out] the 2 digit hour*/
435 : std::string &mm, /**< [out] the 2 digit minute*/
436 : std::string &ss, /**< [out] the 2 digit second*/
437 : std::string &nn, /**< [out] the 9 digit nanosecond*/
438 : const std::string &fname /**< [in] the filename, which can include a path */
439 : )
440 : {
441 : try
442 : {
443 : // clang-format off
444 : #ifdef XWCTEST_PARSEFILEPATH_THROW_BAD_ALLOC
445 : throw std::bad_alloc(); // LCOV_EXCL_LINE
446 : #endif
447 : // clang-format on
448 :
449 : // clang-format off
450 : #ifdef XWCTEST_PARSEFILEPATH_THROW_EXCEPTION
451 : throw std::exception(); // LCOV_EXCL_LINE
452 : #endif
453 : // clang-format on
454 :
455 113 : size_t est = fname.rfind( '.' ); // rfind does not throw
456 113 : if( est == std::string::npos )
457 : {
458 6 : est = fname.size(); // no extension
459 : }
460 :
461 : size_t dst;
462 : size_t dend;
463 :
464 113 : dend = fname.rfind( '_', est ); // rfind does not throw
465 :
466 113 : if( dend == std::string::npos ) // no device name, just a timestamp
467 : {
468 :
469 4 : dst = fname.rfind( '/', est );
470 :
471 4 : if( dst == std::string::npos ) // no path
472 : {
473 2 : dst = 0;
474 : }
475 : else
476 : {
477 2 : ++dst; // move past the '/'
478 : }
479 :
480 4 : dend = est;
481 :
482 4 : devName = "";
483 : }
484 : else
485 : {
486 :
487 109 : dst = fname.rfind( '/', dend );
488 :
489 109 : if( dst == std::string::npos ) // no path
490 : {
491 99 : dst = 0;
492 : }
493 : else
494 : {
495 10 : ++dst; // move past the '/'
496 : }
497 :
498 109 : if( dst >= dend ) // This is '/_YYYY....'
499 : {
500 4 : devName = ""; // no device
501 : }
502 : else // finally, we have a device name
503 : {
504 :
505 : // clang-format off
506 : #ifdef XWCTEST_PARSEFILEPATH_THROW_OUT_OF_RANGE
507 : throw std::out_of_range("testing out of range"); // LCOV_EXCL_LINE
508 : #endif
509 : // clang-format on
510 :
511 103 : devName = fname.substr( dst, dend - dst );
512 : }
513 :
514 107 : dst = dend + 1; // now move to beginning of timestamp
515 107 : dend = est; // and one-past-the-end of the timestamp
516 : }
517 :
518 : // Here dst...dend should be just the timestamp
519 111 : if( dend - dst != 23 )
520 : {
521 4 : return mx::error_report<verboseT>( mx::error_t::invalidarg, "timestamp does not have 23 characters" );
522 : }
523 :
524 : // clang-format off
525 : #ifdef XWCTEST_PARSEFILEPATH_TOO_SHORT
526 : // this will generate a too-short error at this point (invalidarg)
527 : ++dst; // LCOV_EXCL_LINE
528 : #endif
529 : // clang-format on
530 :
531 110 : mx_error_return( parseTimestamp<verboseT>( YYYY, MM, DD, hh, mm, ss, nn, fname.substr( dst, dend - dst ) ) );
532 : }
533 8 : catch( const std::bad_alloc &e )
534 : {
535 8 : std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "parsing filepath") );
536 : }
537 2 : catch( const mx::exception<verboseT> &e ) // from a previous bad_alloc
538 : {
539 4 : std::throw_with_nested( mx::exception<verboseT>(e.code(), "parsing filepath") );
540 : }
541 4 : catch( const std::out_of_range &e )
542 : {
543 4 : return mx::error_report<verboseT>( mx::error_t::std_out_of_range, e.what() );
544 : }
545 2 : catch( const std::exception &e )
546 : {
547 2 : return mx::error_report<verboseT>( mx::error_t::std_exception, e.what() );
548 : }
549 :
550 : }
551 :
552 : #ifdef XWCTEST_NAMESPACE
553 : }
554 : #endif
555 :
556 : } // namespace file
557 : } // namespace MagAOX
558 :
559 : #endif // file_fileTimes_hpp
|