API
 
Loading...
Searching...
No Matches
fileTimes.hpp
Go to the documentation of this file.
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
20namespace MagAOX
21{
22namespace file
23{
24
25#ifdef XWCTEST_NAMESPACE
26namespace 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 */
43template <class verboseT = XWC_DEFAULT_VERBOSITY>
44mx::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 tstamp = std::format( "{:04}{:02}{:02}{:02}{:02}{:02}{:09}",
71 uttime.tm_year + 1900,
72 uttime.tm_mon + 1,
73 uttime.tm_mday,
74 uttime.tm_hour,
75 uttime.tm_min,
76 uttime.tm_sec,
77 ts_nsec );
78 }
79 catch( const std::bad_alloc &e )
80 {
81 std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "from std::format") );
82 }
83 catch( const std::format_error &e )
84 {
85 return mx::error_report<verboseT>( mx::error_t::std_format_error,
86 std::string( "from std::format: " ) + e.what() );
87 }
88 catch( const std::exception &e )
89 {
90 return mx::error_report<verboseT>( mx::error_t::std_exception, std::string( "from std::format: " ) + e.what() );
91 }
92
93 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 */
114template <typename verboseT = XWC_DEFAULT_VERBOSITY>
115mx::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 memset( &uttime, 0, sizeof( uttime ) );
122
123 errno = 0;
124 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 if( errno != 0 )
133 {
134 return mx::error_report<verboseT>( mx::errno2error_t( errno ),
135 "error getting UT time (gmtime_r returned 0)" );
136 }
137 else
138 {
139 return mx::error_report<verboseT>( mx::error_t::error, "error getting UT time (gmtime_r returned 0)" );
140 }
141 }
142
143 try
144 {
145 mx_error_return( timestamp<verboseT>( tstamp, uttime, ts_nsec ) );
146 }
147 catch( ... )
148 {
149 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 */
171template <class verboseT = XWC_DEFAULT_VERBOSITY>
172mx::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 memset( &uttime, 0, sizeof( uttime ) );
179
180 try
181 {
182 mx_error_return( timestamp<verboseT>( tstamp, uttime, ts_sec, ts_nsec ) );
183 }
184 catch( ... )
185 {
186 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 */
208template <class verboseT = XWC_DEFAULT_VERBOSITY>
209mx::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 if(ts_sec == 0 && ts_nsec == 0)
216 {
217 return mx::error_report<verboseT>( mx::error_t::invalidarg, "all 0 time");
218 }
219
220 tm uttime;
221 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 mx_error_check( timestamp<verboseT>( tstamp, uttime, ts_sec, ts_nsec ) );
244
245 relPath = std::format( "{:04}_{:02}_{:02}", uttime.tm_year + 1900, uttime.tm_mon + 1, uttime.tm_mday );
246
247 return mx::error_t::noerror;
248 }
249 catch( const std::bad_alloc &e )
250 {
251 std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "getting relPath") );
252 }
253 catch( const mx::exception<verboseT> & e ) // This is from previous bad_alloc
254 {
255 std::throw_with_nested( mx::exception<verboseT>(e.code(), "getting relPath") );
256 }
257 catch( const std::format_error &e )
258 {
259 return mx::error_report<verboseT>( mx::error_t::std_format_error,
260 std::string( "from std::format: " ) + e.what() );
261 }
262 catch( const std::exception &e )
263 {
264 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 */
286template <class verboseT = XWC_DEFAULT_VERBOSITY>
287mx::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 std::string tstamp, tmprelpath;
296
297 try
298 {
299 mx_error_check( fileTimeRelPath<verboseT>( tstamp, tmprelpath, ts_sec, ts_nsec ) );
300 }
301 catch( ... ) // This is from previous bad_alloc
302 {
303 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 relPath = devName + '/' + tmprelpath;
321 fileName = devName + '_' + tstamp + '.' + ext;
322
323 return mx::error_t::noerror;
324 }
325 catch( const std::bad_alloc &e )
326 {
327 std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "std::string assembling paths" ) );
328 }
329 catch( const std::exception &e )
330 {
331 return mx::error_report<verboseT>( mx::error_t::std_exception,
332 std::string( "std::string assembling paths" ) + e.what() );
333 }
334}
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 */
351template <class verboseT = XWC_DEFAULT_VERBOSITY>
352mx::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 if( tstamp.length() != 23 )
383 {
384 return mx::error_report<verboseT>( mx::error_t::invalidarg, "timestamp does not have 23 characters" );
385 }
386
387 YYYY = tstamp.substr( 0, 4 );
388 MM = tstamp.substr( 4, 2 );
389 DD = tstamp.substr( 6, 2 );
390 hh = tstamp.substr( 8, 2 );
391 mm = tstamp.substr( 10, 2 );
392 ss = tstamp.substr( 12, 2 );
393 nn = tstamp.substr( 14, 9 );
394
395 return mx::error_t::noerror;
396 }
397 catch( const std::bad_alloc &e )
398 {
399 std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc));
400 }
401 catch( const std::out_of_range &e )
402 {
403 return mx::error_report<verboseT>( mx::error_t::std_out_of_range,
404 std::string( "parsing timestamp" ) + e.what() );
405 }
406 catch( const std::exception &e )
407 {
408 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 */
429template <class verboseT = XWC_DEFAULT_VERBOSITY>
430mx::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 size_t est = fname.rfind( '.' ); // rfind does not throw
456 if( est == std::string::npos )
457 {
458 est = fname.size(); // no extension
459 }
460
461 size_t dst;
462 size_t dend;
463
464 dend = fname.rfind( '_', est ); // rfind does not throw
465
466 if( dend == std::string::npos ) // no device name, just a timestamp
467 {
468
469 dst = fname.rfind( '/', est );
470
471 if( dst == std::string::npos ) // no path
472 {
473 dst = 0;
474 }
475 else
476 {
477 ++dst; // move past the '/'
478 }
479
480 dend = est;
481
482 devName = "";
483 }
484 else
485 {
486
487 dst = fname.rfind( '/', dend );
488
489 if( dst == std::string::npos ) // no path
490 {
491 dst = 0;
492 }
493 else
494 {
495 ++dst; // move past the '/'
496 }
497
498 if( dst >= dend ) // This is '/_YYYY....'
499 {
500 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 devName = fname.substr( dst, dend - dst );
512 }
513
514 dst = dend + 1; // now move to beginning of timestamp
515 dend = est; // and one-past-the-end of the timestamp
516 }
517
518 // Here dst...dend should be just the timestamp
519 if( dend - dst != 23 )
520 {
521 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 mx_error_return( parseTimestamp<verboseT>( YYYY, MM, DD, hh, mm, ss, nn, fname.substr( dst, dend - dst ) ) );
532 }
533 catch( const std::bad_alloc &e )
534 {
535 std::throw_with_nested( mx::exception<verboseT>(mx::error_t::std_bad_alloc, "parsing filepath") );
536 }
537 catch( const mx::exception<verboseT> &e ) // from a previous bad_alloc
538 {
539 std::throw_with_nested( mx::exception<verboseT>(e.code(), "parsing filepath") );
540 }
541 catch( const std::out_of_range &e )
542 {
543 return mx::error_report<verboseT>( mx::error_t::std_out_of_range, e.what() );
544 }
545 catch( const std::exception &e )
546 {
547 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
#define XWCTEST_NAMESPACE
mx::error_t parseTimestamp(std::string &YYYY, std::string &MM, std::string &DD, std::string &hh, std::string &mm, std::string &ss, std::string &nn, const std::string &tstamp)
Parse a standard XWCTk timestamp string.
mx::error_t timestamp(std::string &tstamp, const tm &uttime, long ts_nsec)
Get the filename timestamp from the breakdown for a time.
Definition fileTimes.hpp:44
mx::error_t parseFilePath(std::string &devName, std::string &YYYY, std::string &MM, std::string &DD, std::string &hh, std::string &mm, std::string &ss, std::string &nn, const std::string &fname)
Parse a standard XWCTk timestamp filepath.
mx::error_t fileTimeRelPath(std::string &tstamp, std::string &relPath, time_t ts_sec, long ts_nsec)
Get the timestamp and the relative path based on a time.
Definition dm.hpp:28