[reltime] add support for weekdays and having timeslice() return NULL when a time is out-of-range

pull/857/head
Timothy Stack 4 years ago
parent 0b3819d16a
commit 92e20ffd51

@ -31,6 +31,7 @@
#include "config.h"
#include <chrono>
#include "time_util.hh"
static time_t BAD_DATE = -1;
@ -182,3 +183,15 @@ struct tm *secs2tm(time_t *tim_p, struct tm *res)
return (res);
}
struct timeval exttm::to_timeval() const
{
struct timeval retval;
retval.tv_sec = tm2sec(&this->et_tm);
retval.tv_usec =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::nanoseconds(this->et_nsec)).count();
return retval;
}

@ -73,14 +73,7 @@ struct exttm {
return memcmp(this, &other, sizeof(exttm)) == 0;
};
struct timeval to_timeval() const {
struct timeval retval;
retval.tv_sec = tm2sec(&this->et_tm);
retval.tv_usec = this->et_nsec * 1000;
return retval;
};
struct timeval to_timeval() const;
};
inline

@ -91,6 +91,10 @@ struct big_array {
return this->ba_ptr[index];
};
const T &operator[](size_t index) const {
return this->ba_ptr[index];
};
T &back() {
return this->ba_ptr[this->ba_size - 1];
}

@ -925,7 +925,7 @@ bool handle_paging_key(int ch)
logline *ll = lnav_data.ld_log_source.find_line(cl);
ll->to_exttm(tm);
do {
rt.add(tm);
tm = rt.adjust(tm);
new_vl = lnav_data.ld_log_source.find_from_time(tm);
if (new_vl == 0_vl || new_vl != vl || !rt.is_relative()) {

@ -80,6 +80,7 @@ CREATE TABLE lnav_example_log (
ex_procname text collate 'BINARY',
ex_duration integer,
log_time_msecs int hidden,
log_path text hidden collate naturalnocase,
log_text text hidden,
log_body text hidden
@ -90,7 +91,7 @@ CREATE VIEW lnav_top_view AS
SELECT name FROM lnav_view_stack ORDER BY rowid DESC LIMIT 1);
INSERT INTO lnav_example_log VALUES
(0, null, '2017-02-03T04:05:06.100', '2017-02-03T04:05:06.100', 0, 'info', 0, null, null, null, 'hw', 2, '/tmp/log', '2017-02-03T04:05:06.100 hw(2): Hello, World!', 'Hello, World!'),
(1, null, '2017-02-03T04:05:06.200', '2017-02-03T04:05:06.200', 100, 'error', 0, null, null, null, 'gw', 4, '/tmp/log', '2017-02-03T04:05:06.200 gw(4): Goodbye, World!', 'Goodbye, World!'),
(2, 'new', '2017-02-03T04:25:06.200', '2017-02-03T04:25:06.200', 1200000, 'warn', 0, null, null, null, 'gw', 1, '/tmp/log', '2017-02-03T04:25:06.200 gw(1): Goodbye, World!', 'Goodbye, World!'),
(3, 'new', '2017-02-03T04:55:06.200', '2017-02-03T04:55:06.200', 1800000, 'debug', 0, null, null, null, 'gw', 10, '/tmp/log', '2017-02-03T04:55:06.200 gw(10): Goodbye, World!', 'Goodbye, World!');
(0, null, '2017-02-03T04:05:06.100', '2017-02-03T04:05:06.100', 0, 'info', 0, null, null, null, 'hw', 2, 1486094706000, '/tmp/log', '2017-02-03T04:05:06.100 hw(2): Hello, World!', 'Hello, World!'),
(1, null, '2017-02-03T04:05:06.200', '2017-02-03T04:05:06.200', 100, 'error', 0, null, null, null, 'gw', 4, 1486094706000, '/tmp/log', '2017-02-03T04:05:06.200 gw(4): Goodbye, World!', 'Goodbye, World!'),
(2, 'new', '2017-02-03T04:25:06.200', '2017-02-03T04:25:06.200', 1200000, 'warn', 0, null, null, null, 'gw', 1, 1486095906000, '/tmp/log', '2017-02-03T04:25:06.200 gw(1): Goodbye, World!', 'Goodbye, World!'),
(3, 'new', '2017-02-03T04:55:06.200', '2017-02-03T04:55:06.200', 1800000, 'debug', 0, null, null, null, 'gw', 10, 1486097706000, '/tmp/log', '2017-02-03T04:55:06.200 gw(10): Goodbye, World!', 'Goodbye, World!');

@ -3251,7 +3251,7 @@ timediff(*time1*, *time2*)
timeslice(*time*, *slice*)
^^^^^^^^^^^^^^^^^^^^^^^^^^
Return the start of the slice of time that the given timestamp falls in.
Return the start of the slice of time that the given timestamp falls in. If the time falls outside of the slice, NULL is returned.
**Parameters**
* **time\*** --- The timestamp to get the time slice for.
@ -3269,8 +3269,20 @@ timeslice(*time*, *slice*)
.. code-block:: custsqlite
;SELECT timeslice(log_time_msecs, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY slice
2017-01-01 05:00:00.000
;SELECT timeslice(log_time_msecs, '5m') AS slice, count(1) FROM lnav_example_log GROUP BY slice
slice count(1)
2017-02-03 04:05:00.000 2
2017-02-03 04:25:00.000 1
2017-02-03 04:55:00.000 1
To group log messages by those before 4:30am and after:
.. code-block:: custsqlite
;SELECT timeslice(log_time_msecs, 'before 4:30am') AS slice, count(1) FROM lnav_example_log GROUP BY slice
slice count(1)
<NULL> 3
2017-02-03 04:30:00.000 1
**See Also**
:ref:`date`, :ref:`datetime`, :ref:`julianday`, :ref:`strftime`, :ref:`time`, :ref:`timediff`

@ -172,9 +172,9 @@ static Result<string, string> com_adjust_log_time(exec_context &ec, string cmdli
auto parse_res = relative_time::from_str(args[1]);
if (parse_res.isOk()) {
new_time = parse_res.unwrap().add(top_time).to_timeval();
new_time = parse_res.unwrap().adjust(top_time).to_timeval();
}
else if (dts.scan(args[1].c_str(), args[1].size(), NULL, &tm, new_time) != NULL) {
else if (dts.scan(args[1].c_str(), args[1].size(), nullptr, &tm, new_time) != nullptr) {
// nothing to do
} else {
return ec.make_error("could not parse timestamp -- {}", args[1]);
@ -316,7 +316,7 @@ static Result<string, string> com_goto(exec_context &ec, string cmdline, vector<
}
do {
struct exttm tm = rt.add(tv);
auto tm = rt.adjust(tv);
tv = tm.to_timeval();
new_vl = vis_line_t(ttt->row_for_time(tv));
@ -2708,7 +2708,7 @@ static Result<string, string> com_pt_time(exec_context &ec, string cmdline, vect
dts.set_base_time(now);
if (parse_res.isOk()) {
tm.et_tm = *gmtime(&now);
parse_res.unwrap().add(tm);
tm = parse_res.unwrap().adjust(tm);
new_time.tv_sec = timegm(&tm.et_tm);
}
else {
@ -3296,10 +3296,7 @@ static Result<string, string> com_hide_line(exec_context &ec, string cmdline, ve
cl = lnav_data.ld_log_source.at(vl);
ll = lnav_data.ld_log_source.find_line(cl);
ll->to_exttm(tm);
parse_res.unwrap().add(tm);
tv.tv_sec = timegm(&tm.et_tm);
tv.tv_usec = tm.et_nsec / 1000;
tv = parse_res.unwrap().adjust(tm).to_timeval();
tv_set = true;
}

@ -134,15 +134,14 @@ shared_ptr<logfile> logfile_sub_source::find(const char *fn,
return retval;
}
vis_line_t logfile_sub_source::find_from_time(const struct timeval &start)
vis_line_t logfile_sub_source::find_from_time(const struct timeval &start) const
{
vector<uint32_t>::iterator lb;
vis_line_t retval(-1);
lb = lower_bound(this->lss_filtered_index.begin(),
this->lss_filtered_index.end(),
start,
filtered_logline_cmp(*this));
auto lb = lower_bound(this->lss_filtered_index.begin(),
this->lss_filtered_index.end(),
start,
filtered_logline_cmp(*this));
if (lb != this->lss_filtered_index.end()) {
retval = vis_line_t(lb - this->lss_filtered_index.begin());
}

@ -491,7 +491,7 @@ public:
std::shared_ptr<logfile> find(const char *fn, content_line_t &line_base);
std::shared_ptr<logfile> find(content_line_t &line)
std::shared_ptr<logfile> find(content_line_t &line) const
{
std::shared_ptr<logfile> retval;
@ -509,7 +509,7 @@ public:
return retval;
};
logline *find_line(content_line_t line)
logline *find_line(content_line_t line) const
{
logline *retval = nullptr;
std::shared_ptr<logfile> lf = this->find(line);
@ -523,7 +523,7 @@ public:
return retval;
};
vis_line_t find_from_time(const struct timeval &start);
vis_line_t find_from_time(const struct timeval &start) const;
vis_line_t find_from_time(time_t start) {
struct timeval tv = { start, 0 };
@ -531,13 +531,8 @@ public:
return this->find_from_time(tv);
};
vis_line_t find_from_time(exttm &etm) {
struct timeval tv;
tv.tv_sec = timegm(&etm.et_tm);
tv.tv_usec = etm.et_nsec / 1000;
return this->find_from_time(tv);
vis_line_t find_from_time(const exttm &etm) const {
return this->find_from_time(etm.to_timeval());
};
nonstd::optional<vis_line_t> find_from_content(content_line_t cl) {
@ -893,7 +888,7 @@ private:
};
struct filtered_logline_cmp {
filtered_logline_cmp(logfile_sub_source & lc)
filtered_logline_cmp(const logfile_sub_source & lc)
: llss_controller(lc) { };
bool operator()(const uint32_t &lhs, const uint32_t &rhs) const
{
@ -916,7 +911,7 @@ private:
return (*ll_lhs) < rhs;
};
logfile_sub_source & llss_controller;
const logfile_sub_source & llss_controller;
};
/**

@ -31,12 +31,14 @@
#include <assert.h>
#include "base/time_util.hh"
#include "pcrepp/pcrepp.hh"
#include "relative_time.hh"
using namespace std;
using namespace std::chrono_literals;
static struct {
static const struct {
const char *name;
pcrepp pcre;
} MATCHERS[relative_time::RTT__MAX] = {
@ -49,6 +51,15 @@ static struct {
{ "time",
pcrepp("\\A(\\d{1,2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3,6}))?)?") },
{ "num", pcrepp("\\A((?:-|\\+)?\\d+)") },
{ "sun", pcrepp("\\Asun(days?)?\\b") },
{ "mon", pcrepp("\\Amon(days?)?\\b") },
{ "tue", pcrepp("\\Atue(s(days?)?)?\\b") },
{ "wed", pcrepp("\\Awed(nesdays?)?\\b") },
{ "thu", pcrepp("\\Athu(rsdays?)?\\b") },
{ "fri", pcrepp("\\Afri(days?)?\\b") },
{ "sat", pcrepp("\\Asat(urdays?)?\\b") },
{ "us", pcrepp("\\A(?:micros(?:econds?)?|us(?![a-zA-Z]))") },
{ "ms", pcrepp("\\A(?:millis(?:econds?)?|ms(?![a-zA-Z]))") },
{ "sec", pcrepp("\\As(?:ec(?:onds?)?)?(?![a-zA-Z])") },
@ -154,7 +165,7 @@ relative_time::from_str(const char *str, size_t len)
gettimeofday(&tv, nullptr);
localtime_r(&tv.tv_sec, &tm.et_tm);
tm.et_nsec = tv.tv_usec * 1000;
retval.add(tm);
tm = retval.adjust(tm);
retval.rt_field[RTF_YEARS] = tm.et_tm.tm_year;
retval.rt_field[RTF_MONTHS] = tm.et_tm.tm_mon;
@ -193,6 +204,7 @@ relative_time::from_str(const char *str, size_t len)
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_duration = 1min;
retval.rt_absolute_field_end = RTF__MAX;
number_set = false;
}
@ -208,6 +220,36 @@ relative_time::from_str(const char *str, size_t len)
else if (retval.rt_field[RTF_HOURS].value < 12) {
retval.rt_field[RTF_HOURS].value += 12;
}
if (base_token == RTT_AFTER) {
std::chrono::microseconds usecs = 0s;
uint64_t carry = 0;
if (retval.rt_field[RTF_MICROSECONDS].value > 0) {
usecs += std::chrono::microseconds(
1000000ULL - retval.rt_field[RTF_MICROSECONDS].value);
carry = 1;
}
if (carry || retval.rt_field[RTF_SECONDS].value > 0) {
usecs += std::chrono::seconds(
60 - carry - retval.rt_field[RTF_SECONDS].value);
carry = 1;
}
if (carry || retval.rt_field[RTF_MINUTES].value > 0) {
usecs += std::chrono::minutes(
60 - carry - retval.rt_field[RTF_MINUTES].value);
carry = 1;
}
usecs += std::chrono::hours(
24 - retval.rt_field[RTF_HOURS].value);
retval.rt_duration = usecs;
}
if (base_token == RTT_BEFORE) {
retval.rt_duration =
std::chrono::hours(retval.rt_field[RTF_HOURS].value) +
std::chrono::minutes(retval.rt_field[RTF_MINUTES].value) +
std::chrono::seconds(retval.rt_field[RTF_SECONDS].value) +
std::chrono::microseconds(retval.rt_field[RTF_MICROSECONDS].value);
}
break;
case RTT_A:
case RTT_AN:
@ -237,11 +279,15 @@ relative_time::from_str(const char *str, size_t len)
atoi(substr.c_str());
break;
}
} else {
retval.rt_field[RTF_MICROSECONDS].clear();
retval.rt_duration = 1s;
}
}
else {
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS].clear();
retval.rt_field[RTF_MICROSECONDS].clear();
retval.rt_duration = 1min;
}
retval.rt_absolute_field_end = RTF__MAX;
break;
@ -339,7 +385,6 @@ relative_time::from_str(const char *str, size_t len)
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_BEFORE:
case RTT_AGO:
if (retval.empty()) {
pe_out.pe_msg = "Expecting a time unit";
@ -357,6 +402,7 @@ relative_time::from_str(const char *str, size_t len)
retval.rt_absolute_field_end = last_field_type;
}
break;
case RTT_BEFORE:
case RTT_AFTER:
base_token = token;
break;
@ -389,6 +435,19 @@ relative_time::from_str(const char *str, size_t len)
}
break;
case RTT_SUNDAY:
case RTT_MONDAY:
case RTT_TUESDAY:
case RTT_WEDNESDAY:
case RTT_THURSDAY:
case RTT_FRIDAY:
case RTT_SATURDAY:
if (retval.rt_duration == 0s) {
retval.rt_duration = 24h;
}
retval.rt_included_days.insert(token);
break;
case RTT__MAX:
assert(false);
break;
@ -443,20 +502,50 @@ void relative_time::rollover()
}
}
void relative_time::from_timeval(const struct timeval& tv)
relative_time relative_time::from_timeval(const struct timeval& tv)
{
relative_time retval;
retval.clear();
retval.rt_field[RTF_SECONDS] = tv.tv_sec;
retval.rt_field[RTF_MICROSECONDS] = tv.tv_usec;
retval.rollover();
return retval;
}
relative_time relative_time::from_usecs(std::chrono::microseconds usecs)
{
this->clear();
this->rt_field[RTF_SECONDS] = tv.tv_sec;
this->rt_field[RTF_MICROSECONDS] = tv.tv_usec;
this->rollover();
relative_time retval;
retval.clear();
retval.rt_field[RTF_MICROSECONDS] = usecs.count();
retval.rollover();
return retval;
}
std::string relative_time::to_string()
std::string relative_time::to_string() const
{
static const char *DAYS[] = {
"sun",
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
};
char dst[128] = "";
char *pos = dst;
if (this->is_absolute()) {
for (const auto& day_token : this->rt_included_days) {
pos += snprintf(pos, sizeof(dst) - (pos - dst),
"%s ", DAYS[day_token - RTT_SUNDAY]);
}
pos += snprintf(pos, sizeof(dst) - (pos - dst),
"%s",
this->rt_next ? "next " :
@ -528,6 +617,210 @@ std::string relative_time::to_string()
return dst;
}
struct exttm relative_time::adjust(const exttm &tm) const
{
auto retval = tm;
if (this->rt_field[RTF_MICROSECONDS].is_set && this->is_absolute(RTF_MICROSECONDS)) {
retval.et_nsec = this->rt_field[RTF_MICROSECONDS].value * 1000;
}
else {
retval.et_nsec += this->rt_field[RTF_MICROSECONDS].value * 1000;
}
if (this->rt_field[RTF_SECONDS].is_set && this->is_absolute(RTF_SECONDS)) {
if (this->rt_next &&
this->rt_field[RTF_SECONDS].value <= tm.et_tm.tm_sec) {
retval.et_tm.tm_min += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_SECONDS].value >= tm.et_tm.tm_sec) {
retval.et_tm.tm_min -= 1;
}
retval.et_tm.tm_sec = this->rt_field[RTF_SECONDS].value;
}
else {
retval.et_tm.tm_sec += this->rt_field[RTF_SECONDS].value;
}
if (this->rt_field[RTF_MINUTES].is_set && this->is_absolute(RTF_MINUTES)) {
if (this->rt_next &&
this->rt_field[RTF_MINUTES].value <= tm.et_tm.tm_min) {
retval.et_tm.tm_hour += 1;
}
if (this->rt_previous && (this->rt_field[RTF_MINUTES].value == 0 ||
(this->rt_field[RTF_MINUTES].value >= tm.et_tm.tm_min))) {
retval.et_tm.tm_hour -= 1;
}
retval.et_tm.tm_min = this->rt_field[RTF_MINUTES].value;
}
else {
retval.et_tm.tm_min += this->rt_field[RTF_MINUTES].value;
}
if (this->rt_field[RTF_HOURS].is_set && this->is_absolute(RTF_HOURS)) {
if (this->rt_next &&
this->rt_field[RTF_HOURS].value <= tm.et_tm.tm_hour) {
retval.et_tm.tm_mday += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_HOURS].value >= tm.et_tm.tm_hour) {
retval.et_tm.tm_mday -= 1;
}
retval.et_tm.tm_hour = this->rt_field[RTF_HOURS].value;
}
else {
retval.et_tm.tm_hour += this->rt_field[RTF_HOURS].value;
}
if (this->rt_field[RTF_DAYS].is_set && this->is_absolute(RTF_DAYS)) {
if (this->rt_next &&
this->rt_field[RTF_DAYS].value <= tm.et_tm.tm_mday) {
retval.et_tm.tm_mon += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_DAYS].value >= tm.et_tm.tm_mday) {
retval.et_tm.tm_mon -= 1;
}
retval.et_tm.tm_mday = this->rt_field[RTF_DAYS].value;
}
else {
retval.et_tm.tm_mday += this->rt_field[RTF_DAYS].value;
}
if (this->rt_field[RTF_MONTHS].is_set && this->is_absolute(RTF_MONTHS)) {
if (this->rt_next &&
this->rt_field[RTF_MONTHS].value <= tm.et_tm.tm_mon) {
retval.et_tm.tm_year += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_MONTHS].value >= tm.et_tm.tm_mon) {
retval.et_tm.tm_year -= 1;
}
retval.et_tm.tm_mon = this->rt_field[RTF_MONTHS].value;
}
else {
retval.et_tm.tm_mon += this->rt_field[RTF_MONTHS].value;
}
if (this->rt_field[RTF_YEARS].is_set && this->is_absolute(RTF_YEARS)) {
retval.et_tm.tm_year = this->rt_field[RTF_YEARS].value;
}
else {
retval.et_tm.tm_year += this->rt_field[RTF_YEARS].value;
}
return retval;
}
nonstd::optional<exttm> relative_time::window_start(
const struct exttm &tm) const
{
auto retval = tm;
if (this->is_relative()) {
uint64_t us, remainder;
auto tv = tm.to_timeval();
us = (uint64_t) tv.tv_sec * 1000000ULL + (uint64_t) tv.tv_usec;
remainder = us % this->to_microseconds();
us -= remainder;
tv.tv_sec = us / 1000000ULL;
tv.tv_usec = us % 1000000ULL;
retval.et_tm = *gmtime(&tv.tv_sec);
retval.et_nsec = tv.tv_usec * 1000ULL;
return retval;
}
bool clear = false;
if (this->rt_field[RTF_YEARS].is_set) {
if (this->rt_field[RTF_YEARS].value > tm.et_tm.tm_year) {
return nonstd::nullopt;
}
retval.et_tm.tm_year = this->rt_field[RTF_YEARS].value - 1900;
clear = true;
}
if (this->rt_field[RTF_MONTHS].is_set) {
if (this->rt_field[RTF_MONTHS].value - 1 > tm.et_tm.tm_mon) {
return nonstd::nullopt;
}
retval.et_tm.tm_mon = this->rt_field[RTF_MONTHS].value - 1;
clear = true;
} else if (clear) {
retval.et_tm.tm_mon = 0;
}
if (this->rt_field[RTF_DAYS].is_set) {
if (this->rt_field[RTF_DAYS].value > tm.et_tm.tm_mday) {
return nonstd::nullopt;
}
retval.et_tm.tm_mday = this->rt_field[RTF_DAYS].value;
clear = true;
} else if (clear) {
retval.et_tm.tm_mday = 1;
}
if (!this->rt_included_days.empty()) {
auto iter = this->rt_included_days.find((token_t) (RTT_SUNDAY + tm.et_tm.tm_wday));
if (iter == this->rt_included_days.end()) {
return nonstd::nullopt;
}
clear = true;
}
if (this->rt_field[RTF_HOURS].is_set &&
this->rt_field[RTF_HOURS].value) {
if (this->rt_field[RTF_HOURS].value > tm.et_tm.tm_hour) {
return nonstd::nullopt;
}
retval.et_tm.tm_hour = this->rt_field[RTF_HOURS].value;
clear = true;
} else if (clear) {
retval.et_tm.tm_hour = 0;
}
if (this->rt_field[RTF_MINUTES].is_set) {
if (this->rt_field[RTF_MINUTES].value > tm.et_tm.tm_min) {
return nonstd::nullopt;
}
retval.et_tm.tm_min = this->rt_field[RTF_MINUTES].value;
clear = true;
} else if (clear) {
retval.et_tm.tm_min = 0;
}
if (this->rt_field[RTF_SECONDS].is_set) {
if (this->rt_field[RTF_SECONDS].value > tm.et_tm.tm_sec) {
return nonstd::nullopt;
}
retval.et_tm.tm_sec = this->rt_field[RTF_SECONDS].value;
clear = true;
} else if (clear) {
retval.et_tm.tm_sec = 0;
}
if (this->rt_field[RTF_MICROSECONDS].is_set) {
if (this->rt_field[RTF_MICROSECONDS].value > tm.et_nsec / 1000) {
return nonstd::nullopt;
}
retval.et_nsec = this->rt_field[RTF_MICROSECONDS].value * 1000ULL;
clear = true;
} else if (clear) {
retval.et_nsec = 0;
}
auto tv = tm.to_timeval();
auto start_time = retval.to_timeval();
auto end_time = relative_time::from_usecs(this->rt_duration)
.adjust(retval).to_timeval();
if (tv < start_time || end_time < tv) {
return nonstd::nullopt;
}
return retval;
}
size_t duration2str(int64_t millis, std::string &value_out)
{
/* 24h22m33s111 */

@ -35,7 +35,9 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <set>
#include <array>
#include <chrono>
#include <string>
#include "ptimec.hh"
@ -54,6 +56,15 @@ public:
RTT_AT,
RTT_TIME,
RTT_NUMBER,
RTT_SUNDAY,
RTT_MONDAY,
RTT_TUESDAY,
RTT_WEDNESDAY,
RTT_THURSDAY,
RTT_FRIDAY,
RTT_SATURDAY,
RTT_MICROS,
RTT_MILLIS,
RTT_SECONDS,
@ -106,6 +117,10 @@ public:
return from_str(str.c_str(), str.length());
}
static relative_time from_timeval(const struct timeval& tv);
static relative_time from_usecs(std::chrono::microseconds usecs);
relative_time() {
this->clear();
};
@ -148,7 +163,7 @@ public:
};
bool is_absolute() const {
return this->rt_absolute_field_end > 0;
return !this->rt_included_days.empty() || this->rt_absolute_field_end > 0;
};
bool is_absolute(rt_field_type rft) const {
@ -160,6 +175,9 @@ public:
}
bool empty() const {
if (!this->rt_included_days.empty()) {
return false;
}
for (auto rtf : this->rt_field) {
if (rtf.is_set) {
return false;
@ -168,111 +186,28 @@ public:
return true;
};
struct exttm add_now() {
struct exttm adjust_now() const {
struct exttm tm;
time_t now;
time(&now);
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&now);
this->add(tm);
return tm;
return this->adjust(tm);
};
struct exttm add(const struct timeval &tv) {
struct exttm adjust(const struct timeval &tv) const {
struct exttm tm;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&tv.tv_sec);
tm.et_nsec = tv.tv_usec * 1000;
this->add(tm);
return tm;
return this->adjust(tm);
}
void add(struct exttm &tm) {
if (this->rt_field[RTF_MICROSECONDS].is_set && this->is_absolute(RTF_MICROSECONDS)) {
tm.et_nsec = this->rt_field[RTF_MICROSECONDS].value * 1000;
}
else {
tm.et_nsec += this->rt_field[RTF_MICROSECONDS].value * 1000;
}
if (this->rt_field[RTF_SECONDS].is_set && this->is_absolute(RTF_SECONDS)) {
if (this->rt_next &&
this->rt_field[RTF_SECONDS].value <= tm.et_tm.tm_sec) {
tm.et_tm.tm_min += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_SECONDS].value >= tm.et_tm.tm_sec) {
tm.et_tm.tm_min -= 1;
}
tm.et_tm.tm_sec = this->rt_field[RTF_SECONDS].value;
}
else {
tm.et_tm.tm_sec += this->rt_field[RTF_SECONDS].value;
}
if (this->rt_field[RTF_MINUTES].is_set && this->is_absolute(RTF_MINUTES)) {
if (this->rt_next &&
this->rt_field[RTF_MINUTES].value <= tm.et_tm.tm_min) {
tm.et_tm.tm_hour += 1;
}
if (this->rt_previous && (this->rt_field[RTF_MINUTES].value == 0 ||
(this->rt_field[RTF_MINUTES].value >= tm.et_tm.tm_min))) {
tm.et_tm.tm_hour -= 1;
}
tm.et_tm.tm_min = this->rt_field[RTF_MINUTES].value;
}
else {
tm.et_tm.tm_min += this->rt_field[RTF_MINUTES].value;
}
if (this->rt_field[RTF_HOURS].is_set && this->is_absolute(RTF_HOURS)) {
if (this->rt_next &&
this->rt_field[RTF_HOURS].value <= tm.et_tm.tm_hour) {
tm.et_tm.tm_mday += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_HOURS].value >= tm.et_tm.tm_hour) {
tm.et_tm.tm_mday -= 1;
}
tm.et_tm.tm_hour = this->rt_field[RTF_HOURS].value;
}
else {
tm.et_tm.tm_hour += this->rt_field[RTF_HOURS].value;
}
if (this->rt_field[RTF_DAYS].is_set && this->is_absolute(RTF_DAYS)) {
if (this->rt_next &&
this->rt_field[RTF_DAYS].value <= tm.et_tm.tm_mday) {
tm.et_tm.tm_mon += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_DAYS].value >= tm.et_tm.tm_mday) {
tm.et_tm.tm_mon -= 1;
}
tm.et_tm.tm_mday = this->rt_field[RTF_DAYS].value;
}
else {
tm.et_tm.tm_mday += this->rt_field[RTF_DAYS].value;
}
if (this->rt_field[RTF_MONTHS].is_set && this->is_absolute(RTF_MONTHS)) {
if (this->rt_next &&
this->rt_field[RTF_MONTHS].value <= tm.et_tm.tm_mon) {
tm.et_tm.tm_year += 1;
}
if (this->rt_previous &&
this->rt_field[RTF_MONTHS].value >= tm.et_tm.tm_mon) {
tm.et_tm.tm_year -= 1;
}
tm.et_tm.tm_mon = this->rt_field[RTF_MONTHS].value;
}
else {
tm.et_tm.tm_mon += this->rt_field[RTF_MONTHS].value;
}
if (this->rt_field[RTF_YEARS].is_set && this->is_absolute(RTF_YEARS)) {
tm.et_tm.tm_year = this->rt_field[RTF_YEARS].value;
}
else {
tm.et_tm.tm_year += this->rt_field[RTF_YEARS].value;
}
};
struct exttm adjust(const struct exttm &tm) const;
nonstd::optional<exttm> window_start(const struct exttm &tm) const;
int64_t to_microseconds() const {
int64_t retval;
@ -283,20 +218,19 @@ public:
retval = (retval + this->rt_field[RTF_HOURS].value) * 60;
retval = (retval + this->rt_field[RTF_MINUTES].value) * 60;
retval = (retval + this->rt_field[RTF_SECONDS].value) * 1000 * 1000;
retval = (retval + this->rt_field[RTF_MICROSECONDS].value);
return retval;
};
void from_timeval(const struct timeval& tv);
void to_timeval(struct timeval &tv_out) {
void to_timeval(struct timeval &tv_out) const {
int64_t us = this->to_microseconds();
tv_out.tv_sec = us / (1000 * 1000);
tv_out.tv_usec = us % (1000 * 1000);
};
struct timeval to_timeval() {
struct timeval to_timeval() const {
int64_t us = this->to_microseconds();
struct timeval retval;
@ -305,7 +239,7 @@ public:
return retval;
};
std::string to_string();
std::string to_string() const;
void rollover();
@ -318,11 +252,18 @@ public:
_rt_field() : value(0), is_set(false) {
};
void clear() {
this->value = 0;
this->is_set = false;
}
int64_t value;
bool is_set;
};
std::array<_rt_field, RTF__MAX> rt_field;
std::set<token_t> rt_included_days;
std::chrono::microseconds rt_duration{0};
bool rt_next;
bool rt_previous;

@ -47,7 +47,7 @@
using namespace std;
static auto_buffer timeslice(sqlite3_value *time_in, nonstd::optional<const char *> slice_in_opt)
static nonstd::optional<auto_buffer> timeslice(sqlite3_value *time_in, nonstd::optional<const char *> slice_in_opt)
{
thread_local date_time_scanner dts;
thread_local struct {
@ -73,21 +73,17 @@ static auto_buffer timeslice(sqlite3_value *time_in, nonstd::optional<const char
slice_in);
}
if (cache.c_rel_time.is_absolute()) {
throw sqlite_func_error("absolute time slices are not valid");
}
cache.c_slice_str = slice_in.to_string();
}
int64_t us, remainder;
struct timeval tv;
struct exttm tm;
switch (sqlite3_value_type(time_in)) {
case SQLITE_BLOB:
case SQLITE3_TEXT: {
const char *time_in_str = reinterpret_cast<const char *>(sqlite3_value_text(
time_in));
struct exttm tm;
if (dts.scan(time_in_str, strlen(time_in_str), nullptr, &tm, tv, false) == nullptr) {
dts.unlock();
@ -96,26 +92,42 @@ static auto_buffer timeslice(sqlite3_value *time_in, nonstd::optional<const char
time_in_str);
}
}
us = tv.tv_sec * 1000000LL + tv.tv_usec;
break;
}
case SQLITE_INTEGER: {
auto msecs = sqlite3_value_int64(time_in);
auto msecs = std::chrono::milliseconds(sqlite3_value_int64(time_in));
us = msecs * 1000LL;
tv.tv_sec = std::chrono::duration_cast<std::chrono::seconds>(msecs)
.count();
tm.et_tm = *gmtime(&tv.tv_sec);
tm.et_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(
msecs % 1000).count();
break;
}
case SQLITE_FLOAT: {
auto secs = sqlite3_value_double(time_in);
double integ;
auto fract = modf(secs, &integ);
tv.tv_sec = integ;
tm.et_tm = *gmtime(&tv.tv_sec);
tm.et_nsec = floor(fract * 1000000000.0);
break;
}
case SQLITE_NULL: {
return nonstd::nullopt;
}
}
remainder = us % cache.c_rel_time.to_microseconds();
us -= remainder;
auto win_start_opt = cache.c_rel_time.window_start(tm);
tv.tv_sec = us / (1000LL * 1000LL);
tv.tv_usec = us % (1000LL * 1000LL);
if (!win_start_opt) {
return nonstd::nullopt;
}
auto win_start = *win_start_opt;
auto ts = auto_buffer::alloc(64);
auto actual_length = sql_strftime(ts.in(), ts.size(), tv);
auto actual_length = sql_strftime(ts.in(), ts.size(), win_start.to_timeval());
ts.shrink_to(actual_length);
return ts;
@ -129,14 +141,14 @@ nonstd::optional<double> sql_timediff(const char *time1, const char *time2)
auto parse_res1 = relative_time::from_str(time1, -1);
if (parse_res1.isOk()) {
tv1 = parse_res1.unwrap().add_now().to_timeval();
tv1 = parse_res1.unwrap().adjust_now().to_timeval();
} else if (!dts1.convert_to_timeval(time1, -1, nullptr, tv1)) {
return nonstd::nullopt;
}
auto parse_res2 = relative_time::from_str(time2, -1);
if (parse_res2.isOk()) {
tv2 = parse_res2.unwrap().add_now().to_timeval();
tv2 = parse_res2.unwrap().adjust_now().to_timeval();
} else if (!dts2.convert_to_timeval(time2, -1, nullptr, tv2)) {
return nonstd::nullopt;
}
@ -152,7 +164,8 @@ int time_extension_functions(struct FuncDef **basic_funcs,
static struct FuncDef time_funcs[] = {
sqlite_func_adapter<decltype(&timeslice), timeslice>::builder(
help_text("timeslice",
"Return the start of the slice of time that the given timestamp falls in.")
"Return the start of the slice of time that the given timestamp falls in. "
"If the time falls outside of the slice, NULL is returned.")
.sql_function()
.with_parameter({"time", "The timestamp to get the time slice for."})
.with_parameter({"slice", "The size of the time slices"})
@ -163,7 +176,11 @@ int time_extension_functions(struct FuncDef **basic_funcs,
})
.with_example({
"To group log messages into five minute buckets and count them",
"SELECT timeslice(log_time_msecs, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY slice"
"SELECT timeslice(log_time_msecs, '5m') AS slice, count(1) FROM lnav_example_log GROUP BY slice"
})
.with_example({
"To group log messages by those before 4:30am and after",
"SELECT timeslice(log_time_msecs, 'before 4:30am') AS slice, count(1) FROM lnav_example_log GROUP BY slice"
})
),

@ -214,6 +214,16 @@ inline void to_sqlite(sqlite3_context *ctx, double val)
#define JSON_SUBTYPE 74 /* Ascii for "J" */
template<typename T>
inline void to_sqlite(sqlite3_context *ctx, nonstd::optional<T> &val)
{
if (val.has_value()) {
to_sqlite(ctx, val.value());
} else {
sqlite3_result_null(ctx);
}
}
template<typename T>
inline void to_sqlite(sqlite3_context *ctx, const nonstd::optional<T> &val)
{

@ -629,10 +629,8 @@ struct json_path_handler : public json_path_handler_base {
}
yajlpp_generator gen(handle);
relative_time rt;
rt.from_timeval({ field.count(), 0 });
return gen(rt.to_string());
return gen(relative_time::from_timeval({field.count(), 0}).to_string());
};
this->jph_field_getter = [args...](void *root, nonstd::optional<std::string> name) {
return (void *) &json_path_handler::get_field(root, args...);

@ -2778,7 +2778,8 @@ Examples
Synopsis
timeslice(time, slice) -- Return the start of the slice of time that the
given timestamp falls in.
given timestamp falls in. If the time falls outside of the slice, NULL is
returned.
Parameters
time The timestamp to get the time slice for.
slice The size of the time slices
@ -2790,10 +2791,15 @@ Examples
#2 To group log messages into five minute buckets and count them:
;SELECT timeslice(log_time_msecs, '5m') AS slice, count(*) FROM lnav_example_log GROUP
;SELECT timeslice(log_time_msecs, '5m') AS slice, count(1) FROM lnav_example_log GROUP
BY slice
#3 To group log messages by those before 4:30am and after:
;SELECT timeslice(log_time_msecs, 'before 4:30am') AS slice, count(1) FROM
lnav_example_log GROUP BY slice
Synopsis
total(X) -- Returns the sum of the values in the group as a floating-point.

@ -91,6 +91,100 @@ TEST_CASE("reltime")
struct exttm tm, tm2;
time_t new_time;
{
auto rt_res = relative_time::from_str("sun after 1pm");
CHECK(rt_res.isOk());
auto rt = rt_res.unwrap();
time_t t_in = 1615727900;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&t_in);
auto win_opt = rt.window_start(tm);
auto win_tm = *win_opt;
CHECK(win_tm.et_tm.tm_year == 121);
CHECK(win_tm.et_tm.tm_mon == 2);
CHECK(win_tm.et_tm.tm_mday == 14);
CHECK(win_tm.et_tm.tm_hour == 13);
CHECK(win_tm.et_tm.tm_min == 0);
CHECK(win_tm.et_tm.tm_sec == 0);
}
{
auto rt_res = relative_time::from_str("0:05");
CHECK(rt_res.isOk());
auto rt = rt_res.unwrap();
time_t t_in = 5 * 60 + 15;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&t_in);
auto win_opt = rt.window_start(tm);
auto win_tm = *win_opt;
CHECK(win_tm.et_tm.tm_sec == 0);
CHECK(win_tm.et_tm.tm_min == 5);
CHECK(win_tm.et_tm.tm_hour == 0);
t_in = 4 * 60 + 15;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&t_in);
win_opt = rt.window_start(tm);
CHECK(!win_opt.has_value());
}
{
auto rt_res = relative_time::from_str("mon");
CHECK(rt_res.isOk());
auto rt = rt_res.unwrap();
time_t t_in = 1615841352;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&t_in);
auto win_opt = rt.window_start(tm);
auto win_tm = *win_opt;
CHECK(win_tm.et_tm.tm_year == 121);
CHECK(win_tm.et_tm.tm_mon == 2);
CHECK(win_tm.et_tm.tm_mday == 15);
CHECK(win_tm.et_tm.tm_hour == 0);
CHECK(win_tm.et_tm.tm_min == 0);
CHECK(win_tm.et_tm.tm_sec == 0);
}
{
auto rt_res = relative_time::from_str("tue");
CHECK(rt_res.isOk());
auto rt = rt_res.unwrap();
CHECK(rt.rt_included_days ==
std::set<relative_time::token_t>{relative_time::RTT_TUESDAY});
}
{
auto rt_res = relative_time::from_str("1m");
CHECK(rt_res.isOk());
auto rt = rt_res.unwrap();
time_t t_in = 30;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&t_in);
auto win_opt = rt.window_start(tm);
auto win_tm = *win_opt;
CHECK(win_tm.et_tm.tm_sec == 0);
CHECK(win_tm.et_tm.tm_min == 0);
CHECK(win_tm.et_tm.tm_hour == 0);
t_in = 90;
memset(&tm, 0, sizeof(tm));
tm.et_tm = *gmtime(&t_in);
win_opt = rt.window_start(tm);
win_tm = *win_opt;
CHECK(win_tm.et_tm.tm_sec == 0);
CHECK(win_tm.et_tm.tm_min == 1);
CHECK(win_tm.et_tm.tm_hour == 0);
}
relative_time rt;
for (int lpc = 0; TEST_DATA[lpc].reltime; lpc++) {
auto res = relative_time::from_str(TEST_DATA[lpc].reltime);
@ -144,7 +238,7 @@ TEST_CASE("reltime")
CHECK(rt.is_absolute());
tm = base_tm;
rt.add(tm);
tm = rt.adjust(tm);
new_time = timegm(&tm.et_tm);
tm.et_tm = *gmtime(&new_time);
@ -154,7 +248,7 @@ TEST_CASE("reltime")
rt = relative_time::from_str("5 minutes ago").unwrap();
tm = base_tm;
rt.add(tm);
tm = rt.adjust(tm);
new_time = timegm(&tm.et_tm);
@ -163,20 +257,20 @@ TEST_CASE("reltime")
rt = relative_time::from_str("today at 4pm").unwrap();
memset(&tm, 0, sizeof(tm));
memset(&tm2, 0, sizeof(tm2));
gettimeofday(&tv, NULL);
gettimeofday(&tv, nullptr);
localtime_r(&tv.tv_sec, &tm.et_tm);
localtime_r(&tv.tv_sec, &tm2.et_tm);
tm2.et_tm.tm_hour = 16;
tm2.et_tm.tm_min = 0;
tm2.et_tm.tm_sec = 0;
rt.add(tm);
tm = rt.adjust(tm);
tm.et_tm.tm_yday = 0;
tm2.et_tm.tm_yday = 0;
tm.et_tm.tm_wday = 0;
tm2.et_tm.tm_wday = 0;
#ifdef HAVE_STRUCT_TM_TM_ZONE
tm2.et_tm.tm_gmtoff = 0;
tm2.et_tm.tm_zone = NULL;
tm2.et_tm.tm_zone = nullptr;
#endif
CHECK(tm.et_tm.tm_year == tm2.et_tm.tm_year);
CHECK(tm.et_tm.tm_mon == tm2.et_tm.tm_mon);
@ -186,14 +280,14 @@ TEST_CASE("reltime")
CHECK(tm.et_tm.tm_sec == tm2.et_tm.tm_sec);
rt = relative_time::from_str("yesterday at 4pm").unwrap();
gettimeofday(&tv, NULL);
gettimeofday(&tv, nullptr);
localtime_r(&tv.tv_sec, &tm.et_tm);
localtime_r(&tv.tv_sec, &tm2.et_tm);
tm2.et_tm.tm_mday -= 1;
tm2.et_tm.tm_hour = 16;
tm2.et_tm.tm_min = 0;
tm2.et_tm.tm_sec = 0;
rt.add(tm);
tm = rt.adjust(tm);
tm.et_tm.tm_yday = 0;
tm2.et_tm.tm_yday = 0;
tm.et_tm.tm_wday = 0;
@ -210,21 +304,21 @@ TEST_CASE("reltime")
CHECK(tm.et_tm.tm_sec == tm2.et_tm.tm_sec);
rt = relative_time::from_str("2 days ago").unwrap();
gettimeofday(&tv, NULL);
gettimeofday(&tv, nullptr);
localtime_r(&tv.tv_sec, &tm.et_tm);
localtime_r(&tv.tv_sec, &tm2.et_tm);
tm2.et_tm.tm_mday -= 2;
tm2.et_tm.tm_hour = 0;
tm2.et_tm.tm_min = 0;
tm2.et_tm.tm_sec = 0;
rt.add(tm);
tm = rt.adjust(tm);
tm.et_tm.tm_yday = 0;
tm2.et_tm.tm_yday = 0;
tm.et_tm.tm_wday = 0;
tm2.et_tm.tm_wday = 0;
#ifdef HAVE_STRUCT_TM_TM_ZONE
tm2.et_tm.tm_gmtoff = 0;
tm2.et_tm.tm_zone = NULL;
tm2.et_tm.tm_zone = nullptr;
#endif
CHECK(tm.et_tm.tm_year == tm2.et_tm.tm_year);
CHECK(tm.et_tm.tm_mon == tm2.et_tm.tm_mon);

@ -1,5 +1,19 @@
#! /bin/bash
run_test ./drive_sql "select timeslice('2015-08-07 12:01:00', 'after 12pm')"
check_output "after 12pm" <<EOF
Row 0:
Column timeslice('2015-08-07 12:01:00', 'after 12pm'): 2015-08-07 12:00:00.000
EOF
run_test ./drive_sql "select timeslice('2015-08-07 11:59:00', 'after 12pm')"
check_output "not after 12pm" <<EOF
Row 0:
Column timeslice('2015-08-07 11:59:00', 'after 12pm'): (null)
EOF
run_test ./drive_sql "select timeslice()"
check_error_output "timeslice()" <<EOF
@ -21,8 +35,23 @@ EOF
run_test ./drive_sql "select timeslice('2015-08-07 12:01:00', '8 am')"
check_error_output "timeslice abs" <<EOF
error: sqlite3_exec failed -- absolute time slices are not valid
check_output "timeslice abs" <<EOF
Row 0:
Column timeslice('2015-08-07 12:01:00', '8 am'): (null)
EOF
run_test ./drive_sql "select timeslice('2015-08-07 08:00:33', '8 am')"
check_output "timeslice abs" <<EOF
Row 0:
Column timeslice('2015-08-07 08:00:33', '8 am'): 2015-08-07 08:00:00.000
EOF
run_test ./drive_sql "select timeslice('2015-08-07 08:01:33', '8 am')"
check_output "timeslice abs" <<EOF
Row 0:
Column timeslice('2015-08-07 08:01:33', '8 am'): (null)
EOF
run_test ./drive_sql "select timeslice(null, null)"
@ -32,6 +61,20 @@ Row 0:
Column timeslice(null, null): (null)
EOF
run_test ./drive_sql "select timeslice(null)"
check_output "timeslice(null)" <<EOF
Row 0:
Column timeslice(null): (null)
EOF
run_test ./drive_sql "select timeslice(1616300753.333, '100ms')"
check_output "100ms slice" <<EOF
Row 0:
Column timeslice(1616300753.333, '100ms'): 2021-03-21 04:25:53.300
EOF
run_test ./drive_sql "select timeslice('2015-08-07 12:01:00', '5m')"
check_output "timeslice 5m" <<EOF

Loading…
Cancel
Save