ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_EpochTime.H
Go to the documentation of this file.
1 #ifndef ERF_EPOCH_TIME_H_
2 #define ERF_EPOCH_TIME_H_
3 
4 #include <string>
5 #include <sstream>
6 #include <iomanip>
7 #include <ctime>
8 #include <cctype>
9 #include <cmath>
10 #include <cstdio>
11 
12 #ifdef _WIN32
13 #define timegm _mkgmtime
14 #endif
15 
16 /**
17  * Parse an integer of fixed width from a string.
18  * @param[in,out] dt String to parse from; advanced by width on success.
19  * @param[in] width Number of characters to parse.
20  * @param[out] value Parsed integer value.
21  * @return True if parsing was successful, false otherwise.
22  */
23 AMREX_GPU_HOST AMREX_FORCE_INLINE
24 bool
25 parse_fixed_width_int (const char*& dt, int width, int& value)
26 {
27  value = 0;
28 
29  for (int idx = 0; idx < width; ++idx) {
30  unsigned char ch = static_cast<unsigned char>(dt[idx]);
31  if (ch == '\0' || !std::isdigit(ch)) {
32  return false;
33  }
34  value = 10 * value + (dt[idx] - '0');
35  }
36 
37  dt += width;
38  return true;
39 }
40 
41 /**
42  * Convert a formatted date-time string to epoch time.
43  * @param[in] dateTime String containing the date and time.
44  * @param[in] dateTimeFormat Format string describing the layout of dateTime.
45  * @return Epoch time as std::time_t, or -1 on failure.
46  */
47 AMREX_GPU_HOST AMREX_FORCE_INLINE
48 std::time_t
49 getEpochTime (const std::string& dateTime, const std::string& dateTimeFormat)
50 {
51  std::tm tmTime{};
52  const char* dt = dateTime.c_str();
53  const char* fmt = dateTimeFormat.c_str();
54 
55  // Walk both the format string and the date string together
56  while (*fmt && *dt) {
57  if (*fmt == '%') {
58  ++fmt;
59  int parsed_value = 0;
60  switch (*fmt) {
61  case 'Y': // 4-digit year
62  if (!parse_fixed_width_int(dt, 4, parsed_value)) return -1;
63  tmTime.tm_year = parsed_value;
64  tmTime.tm_year -= 1900;
65  break;
66  case 'm': // 2-digit month
67  if (!parse_fixed_width_int(dt, 2, parsed_value)) return -1;
68  tmTime.tm_mon = parsed_value;
69  tmTime.tm_mon -= 1; // struct tm expects 0-11
70  break;
71  case 'd': // 2-digit day
72  if (!parse_fixed_width_int(dt, 2, parsed_value)) return -1;
73  tmTime.tm_mday = parsed_value;
74  break;
75  case 'H': // 2-digit hour
76  if (!parse_fixed_width_int(dt, 2, parsed_value)) return -1;
77  tmTime.tm_hour = parsed_value;
78  break;
79  case 'M': // 2-digit minute
80  if (!parse_fixed_width_int(dt, 2, parsed_value)) return -1;
81  tmTime.tm_min = parsed_value;
82  break;
83  case 'S': // 2-digit second
84  if (!parse_fixed_width_int(dt, 2, parsed_value)) return -1;
85  tmTime.tm_sec = parsed_value;
86  break;
87  default:
88  // Unsupported format specifier
89  return -1;
90  }
91  } else {
92  // Literal character, must match
93  if (*fmt != *dt) {
94  return -1; // mismatch
95  }
96  ++dt;
97  }
98  ++fmt;
99  }
100 
101  if (*fmt != '\0' || *dt != '\0') {
102  return -1;
103  }
104 
105  if (tmTime.tm_mon < 0 || tmTime.tm_mon > 11 ||
106  tmTime.tm_mday < 1 || tmTime.tm_mday > 31 ||
107  tmTime.tm_hour < 0 || tmTime.tm_hour > 23 ||
108  tmTime.tm_min < 0 || tmTime.tm_min > 59 ||
109  tmTime.tm_sec < 0 || tmTime.tm_sec > 60) {
110  return -1;
111  }
112 
113  return timegm(&tmTime);
114 }
115 
116 /**
117  * Convert epoch time to a formatted timestamp string.
118  * @param[in] epoch_real Epoch time in seconds.
119  * @param[in] datetime_format Format string for the resulting timestamp.
120  * @param[in] add_long_frac Whether to append fractional seconds to the output.
121  * @return Formatted timestamp string.
122  */
123 AMREX_FORCE_INLINE
124 std::string
125 getTimestamp (const double epoch_real, const std::string& datetime_format, bool add_long_frac=true)
126 {
127  std::time_t epoch_nearest_sec;
128  long long frac_usec = 0;
129 
130  if (add_long_frac) {
131  //
132  // NOTE: when we print the fraction we must derive it and the whole seconds from
133  // the *same* rounded value. If we instead truncated the seconds and then
134  // printed the leftover fraction to six places, a fraction just below one
135  // would print as "1.000000" next to the un-incremented second and the
136  // timestamp would come out a full second early.
137  //
138  // We round to the microsecond we are about to print, then split, rounding the
139  // seconds toward -infinity so that the fraction is never negative -- which
140  // also gives the right answer for times before the epoch.
141  //
142  constexpr long long usec_per_sec = 1000000;
143  const long long total_usec = std::llround(epoch_real * 1.e6);
144  long long whole_sec = total_usec / usec_per_sec;
145  frac_usec = total_usec % usec_per_sec;
146  if (frac_usec < 0) { whole_sec -= 1; frac_usec += usec_per_sec; }
147  epoch_nearest_sec = static_cast<std::time_t>(whole_sec);
148  } else {
149  epoch_nearest_sec = static_cast<std::time_t>(epoch_real);
150  }
151 
152  std::tm *time_info = std::gmtime(&epoch_nearest_sec);
153 
154  char buffer[80];
155  std::strftime(buffer, sizeof(buffer), datetime_format.c_str(), time_info);
156  std::string str_nearest_sec(buffer);
157 
158  if (add_long_frac) {
159  snprintf(buffer, 80, ".%06lld", frac_usec);
160  return str_nearest_sec + std::string(buffer);
161  } else {
162  return str_nearest_sec;
163  }
164 }
165 
166 
167 #endif
AMREX_GPU_HOST AMREX_FORCE_INLINE std::time_t getEpochTime(const std::string &dateTime, const std::string &dateTimeFormat)
Definition: ERF_EpochTime.H:49
AMREX_GPU_HOST AMREX_FORCE_INLINE bool parse_fixed_width_int(const char *&dt, int width, int &value)
Definition: ERF_EpochTime.H:25
AMREX_FORCE_INLINE std::string getTimestamp(const double epoch_real, const std::string &datetime_format, bool add_long_frac=true)
Definition: ERF_EpochTime.H:125
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE int idx(int i, int j, int k, int nx, int ny)
Definition: ERF_InitForEnsemble.cpp:365