A library for working with phylogenetic and population genetic data.
v0.27.0
date_time.cpp
Go to the documentation of this file.
1 /*
2  Genesis - A toolkit for working with phylogenetic data.
3  Copyright (C) 2014-2020 Lucas Czech and HITS gGmbH
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 
18  Contact:
19  Lucas Czech <lucas.czech@h-its.org>
20  Exelixis Lab, Heidelberg Institute for Theoretical Studies
21  Schloss-Wolfsbrunnenweg 35, D-69118 Heidelberg, Germany
22 */
23 
32 
35 
36 #include <array>
37 #include <cerrno>
38 #include <cstdio>
39 #include <cstdlib>
40 #include <ctime>
41 #include <iomanip>
42 #include <iostream>
43 #include <locale>
44 #include <mutex>
45 #include <sstream>
46 #include <stdexcept>
47 
48 namespace genesis {
49 namespace utils {
50 
51 // =================================================================================================
52 // Thread Safety
53 // =================================================================================================
54 
62 static std::mutex tm_mutex_;
63 
64 // =================================================================================================
65 // Convenience Functions
66 // =================================================================================================
67 
68 std::string current_date()
69 {
70  // std::localtime is not threadsafe.
71  std::lock_guard<std::mutex> const tm_lock( tm_mutex_ );
72 
73  std::time_t now = std::time( nullptr );
74  std::tm* ltm = std::localtime( &now );
75 
76  if( !ltm ) {
77  throw std::runtime_error( "Cannot get current date." );
78  }
79 
80  char out[12];
81  std::sprintf(
82  out, "%u-%02u-%02u",
83  ltm->tm_year + 1900, ltm->tm_mon + 1, ltm->tm_mday
84  );
85  return out;
86 }
87 
88 std::string current_time()
89 {
90  // std::localtime is not threadsafe.
91  std::lock_guard<std::mutex> const tm_lock( tm_mutex_ );
92 
93  std::time_t now = std::time( nullptr );
94  std::tm* ltm = std::localtime( &now );
95 
96  if( !ltm ) {
97  throw std::runtime_error( "Cannot get current time." );
98  }
99 
100  char out[10];
101  std::sprintf(
102  out, "%02u:%02u:%02u",
103  ltm->tm_hour, ltm->tm_min, ltm->tm_sec
104  );
105  return out;
106 }
107 
108 std::time_t tm_to_time( std::tm time, bool use_local_time )
109 {
110  // We take the @p time object by value, as we need a non-const copy anyway to call the function:
111  // std::mktime changes and fixes values in the time object.
112 
113  std::time_t ret;
114  if( use_local_time ) {
115  ret = std::mktime( &time );
116  } else {
117 
118  // Set the time zone to UTC if needed, and store the previous value.
119  // Unfortunately, some of the functions are not in the std namespace... Ugly C++ standard!
120  char* tz;
121  tz = ::getenv("TZ");
122  ::setenv("TZ", "", 1);
123  ::tzset();
124 
125  // Convert.
126  ret = std::mktime( &time );
127 
128  // Reverse the time zone if needed.
129  if( tz ) {
130  ::setenv("TZ", tz, 1);
131  } else {
132  ::unsetenv("TZ");
133  }
134  ::tzset();
135  }
136  if( ret == -1 ) {
137  throw std::invalid_argument( "Cannot convert std::tm object to std::time." );
138  }
139  return ret;
140 }
141 
142 std::vector<std::time_t> tm_to_time( std::vector<std::tm> const& times, bool use_local_time )
143 {
144  return tm_to_time( times.begin(), times.end(), use_local_time, times.size() );
145 }
146 
147 std::tm time_to_tm( std::time_t const& time, bool use_local_time )
148 {
149  // std::localtime and std::gmtime are not threadsafe.
150  std::lock_guard<std::mutex> const tm_lock( tm_mutex_ );
151 
152  std::tm* ret;
153  if( use_local_time ) {
154  ret = std::localtime( &time );
155  } else {
156  ret = std::gmtime( &time );
157  }
158  if( ret == nullptr ) {
159  if( errno == EOVERFLOW ) {
160  throw std::invalid_argument(
161  "Cannot convert std::time object to std::tm, because the argument is too large."
162  );
163  } else {
164  throw std::invalid_argument( "Cannot convert std::time object to std::tm." );
165  }
166  }
167  return *ret;
168 }
169 
170 std::vector<std::tm> time_to_tm( std::vector<std::time_t> const& times, bool use_local_time )
171 {
172  return time_to_tm( times.begin(), times.end(), use_local_time, times.size() );
173 }
174 
175 // =================================================================================================
176 // Date/Time Conversion from std::tm
177 // =================================================================================================
178 
179 std::string tm_to_string( std::tm const& time, std::string const& format, std::string const& locale )
180 {
181  // gcc claims that version 4.8.1 was feature-complete for C+=11, see
182  // https://gcc.gnu.org/projects/cxx-status.html#cxx11. That fact that std::put_time and
183  // std::get_time are only available starting with gcc 5 determines that this is not true.
184  // Hence, to also support gcc < 5, we have to work around this limitation.
185 
186  // Prepare a locale and a stream
187  auto const loc = std::locale( locale.c_str() );
188  std::ostringstream oss{};
189  oss.imbue( loc );
190 
191  // Explicitly create a time facet that we can use to put the time to the stream.
192  std::time_put<char> const& tmput = std::use_facet<std::time_put<char>>( loc );
193  tmput.put( oss, oss, ' ', &time, &format[0], &format[0] + format.size() );
194  return oss.str();
195 
196  // Simple version that does not work with gcc < 5
197  // std::ostringstream oss{};
198  // oss.imbue(std::locale( locale.c_str() ));
199  // oss << std::put_time( &time, format.c_str() );
200  // return oss.str();
201 }
202 
203 std::string tm_to_string( std::tm const& time, std::string const& format )
204 {
205  return tm_to_string( time, format, "C" );
206 }
207 
208 std::string tm_to_string( std::tm const& time )
209 {
210  return tm_to_string( time, "%Y-%m-%dT%H:%M:%S", "C" );
211 }
212 
213 std::string tm_date_to_string( std::tm const& time )
214 {
215  return tm_to_string( time, "%Y-%m-%d", "C" );
216 }
217 
218 std::string tm_time_to_string( std::tm const& time )
219 {
220  return tm_to_string( time, "%H:%M:%S", "C" );
221 }
222 
223 // =================================================================================================
224 // Date/Time Conversion to std::tm
225 // =================================================================================================
226 
227 // Typical locales that we expect to see in scientific data.
228 static const std::array<std::string, 3> locales_ = {{ "C", "en_US.UTF-8", "" }};
229 
230 // Typical formats that we expect to see in scientific data.
231 static const std::array<std::string, 9> formats_ = {{
232  "%Y-%m-%d", "%Y%m%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%Y%m%dT%H%M%S",
233  "%Y%m%d %H%M%S", "%Y%m%d%H%M%S", "%H:%M:%S", "%H%M%S"
234 }};
235 
242 bool convert_to_tm_( std::string const& str, std::string const& format, std::string const& locale, std::tm& t )
243 {
244  // gcc claims that version 4.8.1 was feature-complete for C+=11, see
245  // https://gcc.gnu.org/projects/cxx-status.html#cxx11. That fact that std::put_time and
246  // std::get_time are only available starting with gcc 5 determines that this is not true.
247  // Hence, to also support gcc < 5, we have to work around this limitation.
248  // Still, it does not work with gcc 4.8, unfortunately.
249 
250  #if !( defined(__GNUC__) && (__GNUC___ < 5) && !defined(__clang__) && !defined(__INTEL_COMPILER) )
251 
252  // Init the tm object to all zeros, see https://en.cppreference.com/w/cpp/io/manip/get_time
253  // We are re-using the object when called from looping functions, so this is necessary.
254  t.tm_sec = 0;
255  t.tm_min = 0;
256  t.tm_hour = 0;
257  t.tm_mday = 0;
258  t.tm_mon = 0;
259  t.tm_year = 0;
260  t.tm_wday = 0;
261  t.tm_yday = 0;
262  t.tm_isdst = 0;
263 
264  // Prepare a locale and a stream
265  auto loc = std::locale( locale.c_str() );
266  std::istringstream iss( trim( str ));
267  iss.imbue( loc );
268 
269  // Explicitly create a time facet and other helper objects
270  // that we can use to get the time from the stream.
271  std::time_get<char> const& tmget = std::use_facet<std::time_get<char>>( loc );
272  std::ios::iostate state = std::ios_base::goodbit;
273 
274  // Run the conversion, using all provided information.
275  tmget.get(
276  iss, std::time_get<char>::iter_type(), iss, state, &t, &format[0], &format[0] + format.size()
277  );
278 
279  // Return whether that worked or failed, and whether we also reached the end of the stream.
280  // If we did not reach EOF, there is more data in the stream, which means, we only partially
281  // matched the string, so that it is not an actual fit.
282  // We do this by a hard comparison against the eof bit mask. We cannot use the iss.eof()
283  // function here, because the iss state is not set by our code above.
284  return ! iss.fail() && state == std::ios_base::eofbit;
285 
286  #else
287 
288  (void) str;
289  (void) format;
290  (void) locale;
291  (void) t;
292 
293  throw std::runtime_error(
294  "You compiled with " + Options::get().compiler_family() + " " + Options::get().compiler_version() +
295  ", which does not support time conversion functions std::get_time and std::time_get::get. " +
296  "Please upgrade to a newer compiler."
297  );
298 
299  #endif
300 
301  // Simple version that does not work with gcc < 5
302  // // Run the conversion, using all provided information.
303  // std::istringstream iss( trim( str ));
304  // iss.imbue( std::locale( locale.c_str() ));
305  // iss >> std::get_time( &t, format.c_str() );
306  //
307  // // Return whether that worked or failed, and whether we also reached the end of the stream.
308  // // If we did not reach EOF, there is more data in the stream, which means, we only partially
309  // // matched the string, so that it is not an actual fit.
310  // return ! iss.fail() && iss.eof();
311 }
312 
313 std::tm convert_to_tm( std::string const& str, std::string const& format, std::string const& locale )
314 {
315  std::tm t;
316  if( !convert_to_tm_( str, format, locale, t )) {
317  throw std::invalid_argument(
318  "Cannot convert string '" + str + "' to tm date/time object."
319  );
320  }
321  return t;
322 }
323 
324 std::tm convert_to_tm( std::string const& str, std::string const& format )
325 {
326  std::tm t;
327  for( auto const& locale : locales_ ) {
328  if( convert_to_tm_( str, format, locale, t )) {
329  return t;
330  }
331  }
332 
333  throw std::invalid_argument(
334  "Cannot convert string '" + str + "' to tm date/time object with given format."
335  );
336 }
337 
338 std::tm convert_to_tm( std::string const& str )
339 {
340  std::tm t;
341 
342  // The formats that we try here are not dependent on the locale. If we introduce additional
343  // formats in the future, it might be necessary to also loop over those, for example the
344  // user-preferred locale, to make sure that local date/time formats can be parsed.
345  for( auto const& format : formats_ ) {
346  if( convert_to_tm_( str, format, "C", t )) {
347  return t;
348  }
349  }
350 
351  throw std::invalid_argument(
352  "Cannot convert string '" + str + "' to tm date/time object with guessed formats."
353  );
354 }
355 
356 bool is_convertible_to_tm( std::string const& str, std::string const& format, std::string const& locale )
357 {
358  std::tm t;
359  return convert_to_tm_( str, format, locale, t );
360 }
361 
362 bool is_convertible_to_tm( std::string const& str, std::string const& format )
363 {
364  // If one of the locales works, the string is convertible.
365  std::tm t;
366  for( auto const& locale : locales_ ) {
367  if( convert_to_tm_( str, format, locale, t )) {
368  return true;
369  }
370  }
371  return false;
372 }
373 
374 bool is_convertible_to_tm( std::string const& str )
375 {
376  // If one of the formats works, the string is convertible.
377  std::tm t;
378  for( auto const& format : formats_ ) {
379  if( convert_to_tm_( str, format, "C", t )) {
380  return true;
381  }
382  }
383  return false;
384 }
385 
386 } // namespace utils
387 } // namespace genesis
genesis::utils::convert_to_tm
std::tm convert_to_tm(std::string const &str, std::string const &format, std::string const &locale)
Convert a std::string to a std::tm date/time object, if possible. Throw otherwise.
Definition: date_time.cpp:313
genesis::utils::current_date
std::string current_date()
Returns the current date as a string in the format "2014-12-31".
Definition: date_time.cpp:68
genesis::utils::convert_to_tm_
bool convert_to_tm_(std::string const &str, std::string const &format, std::string const &locale, std::tm &t)
Local helper function that does the heavy load of time conversion.
Definition: date_time.cpp:242
date_time.hpp
Provides functions for date and time access.
genesis::utils::trim
std::string trim(std::string const &s, std::string const &delimiters)
Return a copy of the input string, with trimmed white spaces.
Definition: string.cpp:602
genesis::utils::current_time
std::string current_time()
Returns the current time as a string in the format "13:37:42".
Definition: date_time.cpp:88
genesis::utils::tm_date_to_string
std::string tm_date_to_string(std::tm const &time)
Print the given std::tm object as a std::string containing only the date, using the ISO 8601 extended...
Definition: date_time.cpp:213
genesis::utils::locales_
static const std::array< std::string, 3 > locales_
Definition: date_time.cpp:228
string.hpp
Provides some commonly used string utility functions.
genesis::utils::time_to_tm
std::tm time_to_tm(std::time_t const &time, bool use_local_time)
Convert std::time_t object to a std::tm object.
Definition: date_time.cpp:147
genesis
Container namespace for all symbols of genesis in order to keep them separate when used as a library.
Definition: placement/formats/edge_color.cpp:42
genesis::utils::tm_mutex_
static std::mutex tm_mutex_
The std::localtime and std::gmtime functions are not thread safe, due to their shared internal state....
Definition: date_time.cpp:62
options.hpp
genesis::utils::tm_to_time
std::time_t tm_to_time(std::tm time, bool use_local_time)
Convert std::tm object to a std::time_t object.
Definition: date_time.cpp:108
genesis::utils::tm_time_to_string
std::string tm_time_to_string(std::tm const &time)
Print the given std::tm object as a std::string containing only the time, using the ISO 8601 extended...
Definition: date_time.cpp:218
genesis::utils::formats_
static const std::array< std::string, 9 > formats_
Definition: date_time.cpp:231
genesis::utils::tm_to_string
std::string tm_to_string(std::tm const &time, std::string const &format, std::string const &locale)
Print the given std::tm object as a std::string, using the format and locale.
Definition: date_time.cpp:179
genesis::utils::is_convertible_to_tm
bool is_convertible_to_tm(std::string const &str, std::string const &format, std::string const &locale)
Return whether a std::string is convertible to a std::tm date/time object, that is,...
Definition: date_time.cpp:356
genesis::utils::Options::get
static Options & get()
Returns a single instance of this class.
Definition: options.hpp:60