aboutsummaryrefslogtreecommitdiff
path: root/libbutl/timestamp.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbutl/timestamp.cxx')
-rw-r--r--libbutl/timestamp.cxx127
1 files changed, 83 insertions, 44 deletions
diff --git a/libbutl/timestamp.cxx b/libbutl/timestamp.cxx
index 9be2a82..260fbef 100644
--- a/libbutl/timestamp.cxx
+++ b/libbutl/timestamp.cxx
@@ -1,9 +1,7 @@
// file : libbutl/timestamp.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/timestamp.mxx>
-#endif
+#include <libbutl/timestamp.hxx>
#include <time.h> // localtime_{r,s}(), gmtime_{r,s}(), strptime(), timegm()
#include <errno.h> // EINVAL
@@ -25,22 +23,18 @@
#ifdef __GLIBCXX__
extern "C"
{
-#include "strptime.c"
+# include "strptime.c"
}
#else
-#include <locale.h> // LC_ALL
+# include <locale.h> // LC_ALL
#endif
#endif
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <chrono>
-
-#include <ctime> // tm, time_t, mktime(), strftime()[__GLIBCXX__]
+#include <ctime> // tm, time_t, mktime(), strftime()[libstdc++]
#include <cstdlib> // strtoull()
-#include <sstream>
+#include <sstream> // ostringstream, stringstream[VC]
#include <iomanip> // put_time(), setw(), dec, right
-#include <cstring> // strlen(), memcpy()
+#include <cstring> // strlen(), memcpy(), strchr()[VC]
#include <ostream>
#include <utility> // pair, make_pair()
#include <stdexcept> // runtime_error
@@ -49,30 +43,14 @@ extern "C"
//
#ifdef _WIN32
#ifndef __GLIBCXX__
-#include <locale>
-#include <clocale>
-#include <iomanip>
-#endif
+# include <ios>
+# include <locale>
+# include <clocale>
+# include <iomanip>
#endif
#endif
-// Other includes.
-
-#ifdef __cpp_modules_ts
-module butl.timestamp;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-#endif
-#endif
-
-import butl.utility;
-#else
-#include <libbutl/utility.mxx> // throw_generic_error()
-#endif
+#include <libbutl/utility.hxx> // throw_generic_error()
using namespace std;
@@ -180,24 +158,85 @@ strptime (const char* input, const char* format, tm* time)
{
// VC std::get_time()-based implementation.
//
- istringstream is (input);
+ // Note that the major difference in semantics of strptime() and
+ // std::get_time() is that the former always fails if the format string is
+ // not fully processed, while the latter can succeed in such a case,
+ // specifically if the end of the stream is reached after a conversion
+ // specifier was successfully applied. See this post for some background:
+ //
+ // https://stackoverflow.com/questions/67060956/what-is-the-correct-behavior-of-stdget-time-for-short-input
+ //
+ // The consequence of this fact is that there is no easy way to detect if
+ // the format was fully processed when the end of input is reached. It seems
+ // that the only way to resolve this ambiguity is to append some end marker
+ // to both the input and format and re-parse. We can dedicate some character
+ // that is unlikely to be used in the time format/input (for example '\x1')
+ // to serve as an end marker.
+ //
+ // Alternatively, we can abandon the use of std::get_time() altogether and,
+ // for example, use a FreeBSD-based strptime() implementation. This feels a
+ // bit too radical at the moment, though.
+ //
+ const char em ('\x1');
+
+ if (strchr (input, em) != nullptr || strchr (format, em) != nullptr)
+ return nullptr;
+
+ stringstream ss (input); // Input/output stream.
// The original strptime() function behaves according to the process' C
// locale (set with std::setlocale()), which can differ from the process C++
// locale (set with std::locale::global()).
//
- is.imbue (locale (setlocale (LC_ALL, nullptr)));
+ ss.imbue (locale (setlocale (LC_ALL, nullptr)));
- if (!(is >> get_time (time, format)))
+ // Bail out on the parsing error.
+ //
+ if (!(ss >> get_time (time, format)))
return nullptr;
- else
- // tellg() behaves as UnformattedInputFunction, so returns failure status
- // if eofbit is set.
- //
- return const_cast<char*> (
- input + (is.eof ()
- ? strlen (input)
- : static_cast<size_t> (is.tellg ())));
+
+ // If the end of input is not reached then the format string is fully
+ // processed.
+ //
+ if (!ss.eof ())
+ return const_cast<char*> (input + static_cast<size_t> (ss.tellg ()));
+
+ // Since eof is reached, we cannot say if the format string was fully
+ // processed or not. For example:
+ //
+ // %b %Y - format
+ // Feb 2016 - eofbit is set with a format fully processed
+ // Feb - eofbit is set with a format partially processed
+ //
+ // So append the end marker character to both input and format and re-parse.
+ //
+ ss.clear (); // Clear eof.
+ ss.seekp (0, ios_base::end); // Position to the end for writing.
+ ss.put (em); // Append the end marker.
+ ss.seekg (0); // Rewind for reading.
+
+ string fm (format);
+ fm += em; // Append the end marker.
+
+ // Fail if the input is "shorter" than the format. For example:
+ //
+ // %b %Y\x1 - format
+ // Feb\x1 - stream
+ //
+ // Note that we can reuse the time object for re-parsing, since on success
+ // its fields will be overwritten with the same values.
+ //
+ if (!(ss >> get_time (time, fm.c_str ())))
+ return nullptr;
+
+ // We would fail earlier otherwise.
+ //
+ assert (ss.eof () || ss.get () == stringstream::traits_type::eof ());
+
+ // tellg() behaves as UnformattedInputFunction, so returns failure status if
+ // eofbit is set.
+ //
+ return const_cast<char*> (input + strlen (input));
}
#endif