summaryrefslogtreecommitdiffstats
path: root/src/base/date_time_scanner.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/date_time_scanner.cc')
-rw-r--r--src/base/date_time_scanner.cc308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/base/date_time_scanner.cc b/src/base/date_time_scanner.cc
new file mode 100644
index 0000000..72b7e5d
--- /dev/null
+++ b/src/base/date_time_scanner.cc
@@ -0,0 +1,308 @@
+/**
+ * Copyright (c) 2020, Timothy Stack
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of Timothy Stack nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @file date_time_scanner.cc
+ */
+
+#include <chrono>
+
+#include "date_time_scanner.hh"
+
+#include "config.h"
+#include "ptimec.hh"
+#include "scn/scn.h"
+
+size_t
+date_time_scanner::ftime(char* dst,
+ size_t len,
+ const char* const time_fmt[],
+ const exttm& tm) const
+{
+ off_t off = 0;
+
+ if (time_fmt == nullptr) {
+ PTIMEC_FORMATS[this->dts_fmt_lock].pf_ffunc(dst, off, len, tm);
+ if (tm.et_flags & ETF_MILLIS_SET) {
+ dst[off++] = '.';
+ ftime_L(dst, off, len, tm);
+ } else if (tm.et_flags & ETF_MICROS_SET) {
+ dst[off++] = '.';
+ ftime_f(dst, off, len, tm);
+ } else if (tm.et_flags & ETF_NANOS_SET) {
+ dst[off++] = '.';
+ ftime_N(dst, off, len, tm);
+ }
+ dst[off] = '\0';
+ } else {
+ off = ftime_fmt(dst, len, time_fmt[this->dts_fmt_lock], tm);
+ }
+
+ return (size_t) off;
+}
+
+bool
+next_format(const char* const fmt[], int& index, int& locked_index)
+{
+ bool retval = true;
+
+ if (locked_index == -1) {
+ index += 1;
+ if (fmt[index] == nullptr) {
+ retval = false;
+ }
+ } else if (index == locked_index) {
+ retval = false;
+ } else {
+ index = locked_index;
+ }
+
+ return retval;
+}
+
+const char*
+date_time_scanner::scan(const char* time_dest,
+ size_t time_len,
+ const char* const time_fmt[],
+ struct exttm* tm_out,
+ struct timeval& tv_out,
+ bool convert_local)
+{
+ int curr_time_fmt = -1;
+ bool found = false;
+ const char* retval = nullptr;
+
+ if (!time_fmt) {
+ time_fmt = PTIMEC_FORMAT_STR;
+ }
+
+ while (next_format(time_fmt, curr_time_fmt, this->dts_fmt_lock)) {
+ *tm_out = this->dts_base_tm;
+ tm_out->et_flags = 0;
+ if (time_len > 1 && time_dest[0] == '+' && isdigit(time_dest[1])) {
+ retval = nullptr;
+ auto epoch_scan_res = scn::scan_value<int64_t>(
+ scn::string_view{time_dest, time_len});
+ if (epoch_scan_res) {
+ time_t gmt = epoch_scan_res.value();
+
+ if (convert_local && this->dts_local_time) {
+ localtime_r(&gmt, &tm_out->et_tm);
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm_out->et_tm.tm_zone = nullptr;
+#endif
+ tm_out->et_tm.tm_isdst = 0;
+ gmt = tm2sec(&tm_out->et_tm);
+ }
+ tv_out.tv_sec = gmt;
+ tv_out.tv_usec = 0;
+ tm_out->et_flags = ETF_DAY_SET | ETF_MONTH_SET | ETF_YEAR_SET
+ | ETF_MACHINE_ORIENTED | ETF_EPOCH_TIME;
+
+ this->dts_fmt_lock = curr_time_fmt;
+ this->dts_fmt_len = std::distance(epoch_scan_res.begin(),
+ epoch_scan_res.end());
+ retval = time_dest + this->dts_fmt_len;
+ found = true;
+ break;
+ }
+ } else if (time_fmt == PTIMEC_FORMAT_STR) {
+ ptime_func func = PTIMEC_FORMATS[curr_time_fmt].pf_func;
+ off_t off = 0;
+
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ if (!this->dts_keep_base_tz) {
+ tm_out->et_tm.tm_zone = nullptr;
+ }
+#endif
+ if (func(tm_out, time_dest, off, time_len)) {
+ retval = &time_dest[off];
+
+ if (tm_out->et_tm.tm_year < 70) {
+ tm_out->et_tm.tm_year = 80;
+ }
+ if (convert_local
+ && (this->dts_local_time
+ || tm_out->et_flags & ETF_EPOCH_TIME))
+ {
+ time_t gmt = tm2sec(&tm_out->et_tm);
+
+ this->to_localtime(gmt, *tm_out);
+ }
+ const auto& last_tm = this->dts_last_tm.et_tm;
+ if (last_tm.tm_year == tm_out->et_tm.tm_year
+ && last_tm.tm_mon == tm_out->et_tm.tm_mon
+ && last_tm.tm_mday == tm_out->et_tm.tm_mday
+ && last_tm.tm_hour == tm_out->et_tm.tm_hour
+ && last_tm.tm_min == tm_out->et_tm.tm_min)
+ {
+ const auto sec_diff = tm_out->et_tm.tm_sec - last_tm.tm_sec;
+
+ // log_debug("diff %d", sec_diff);
+ tv_out = this->dts_last_tv;
+ tv_out.tv_sec += sec_diff;
+ tm_out->et_tm.tm_wday = last_tm.tm_wday;
+ } else {
+ // log_debug("doing tm2sec");
+ tv_out.tv_sec = tm2sec(&tm_out->et_tm);
+ secs2wday(tv_out, &tm_out->et_tm);
+ }
+ tv_out.tv_usec = tm_out->et_nsec / 1000;
+
+ this->dts_fmt_lock = curr_time_fmt;
+ this->dts_fmt_len = retval - time_dest;
+
+ found = true;
+ break;
+ }
+ } else {
+ off_t off = 0;
+
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ if (!this->dts_keep_base_tz) {
+ tm_out->et_tm.tm_zone = nullptr;
+ }
+#endif
+ if (ptime_fmt(
+ time_fmt[curr_time_fmt], tm_out, time_dest, off, time_len)
+ && (time_dest[off] == '.' || time_dest[off] == ','
+ || off == (off_t) time_len))
+ {
+ retval = &time_dest[off];
+ if (tm_out->et_tm.tm_year < 70) {
+ tm_out->et_tm.tm_year = 80;
+ }
+ if (convert_local
+ && (this->dts_local_time
+ || tm_out->et_flags & ETF_EPOCH_TIME))
+ {
+ time_t gmt = tm2sec(&tm_out->et_tm);
+
+ this->to_localtime(gmt, *tm_out);
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm_out->et_tm.tm_zone = nullptr;
+#endif
+ tm_out->et_tm.tm_isdst = 0;
+ }
+
+ tv_out.tv_sec = tm2sec(&tm_out->et_tm);
+ tv_out.tv_usec = tm_out->et_nsec / 1000;
+ secs2wday(tv_out, &tm_out->et_tm);
+
+ this->dts_fmt_lock = curr_time_fmt;
+ this->dts_fmt_len = retval - time_dest;
+
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ retval = nullptr;
+ }
+
+ if (retval != nullptr) {
+ this->dts_last_tm = *tm_out;
+ this->dts_last_tv = tv_out;
+ }
+
+ if (retval != nullptr && static_cast<size_t>(retval - time_dest) < time_len)
+ {
+ /* Try to pull out the milli/micro-second value. */
+ if (retval[0] == '.' || retval[0] == ',') {
+ off_t off = (retval - time_dest) + 1;
+
+ if (ptime_N(tm_out, time_dest, off, time_len)) {
+ tv_out.tv_usec
+ = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::nanoseconds{tm_out->et_nsec})
+ .count();
+ this->dts_fmt_len += 10;
+ tm_out->et_flags |= ETF_NANOS_SET;
+ retval += 10;
+ } else if (ptime_f(tm_out, time_dest, off, time_len)) {
+ tv_out.tv_usec
+ = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::nanoseconds{tm_out->et_nsec})
+ .count();
+ this->dts_fmt_len += 7;
+ tm_out->et_flags |= ETF_MICROS_SET;
+ retval += 7;
+ } else if (ptime_L(tm_out, time_dest, off, time_len)) {
+ tv_out.tv_usec
+ = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::nanoseconds{tm_out->et_nsec})
+ .count();
+ this->dts_fmt_len += 4;
+ tm_out->et_flags |= ETF_MILLIS_SET;
+ retval += 4;
+ }
+ }
+ }
+
+ return retval;
+}
+
+void
+date_time_scanner::set_base_time(time_t base_time, const tm& local_tm)
+{
+ this->dts_base_time = base_time;
+ this->dts_base_tm.et_tm = local_tm;
+ this->dts_last_tm = exttm{};
+ this->dts_last_tv = timeval{};
+}
+
+void
+date_time_scanner::to_localtime(time_t t, exttm& tm_out)
+{
+ if (t < (24 * 60 * 60)) {
+ // Don't convert and risk going past the epoch.
+ return;
+ }
+
+ if (t < this->dts_local_offset_valid || t >= this->dts_local_offset_expiry)
+ {
+ time_t new_gmt;
+
+ localtime_r(&t, &tm_out.et_tm);
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm_out.et_tm.tm_zone = nullptr;
+#endif
+ tm_out.et_tm.tm_isdst = 0;
+
+ new_gmt = tm2sec(&tm_out.et_tm);
+ this->dts_local_offset_cache = t - new_gmt;
+ this->dts_local_offset_valid = t;
+ this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1);
+ this->dts_local_offset_expiry
+ -= this->dts_local_offset_expiry % EXPIRE_TIME;
+ } else {
+ time_t adjust_gmt = t - this->dts_local_offset_cache;
+ gmtime_r(&adjust_gmt, &tm_out.et_tm);
+ }
+}