diff options
Diffstat (limited to 'src/base/date_time_scanner.cc')
-rw-r--r-- | src/base/date_time_scanner.cc | 308 |
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); + } +} |