mirror of https://github.com/tstack/lnav
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
10 KiB
C++
309 lines
10 KiB
C++
/**
|
|
* 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);
|
|
}
|
|
}
|