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.
1137 lines
39 KiB
C++
1137 lines
39 KiB
C++
/**
|
|
* Copyright (c) 2015, 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.
|
|
*/
|
|
|
|
#include <unordered_set>
|
|
|
|
#include "relative_time.hh"
|
|
|
|
#include "base/lnav_log.hh"
|
|
#include "base/time_util.hh"
|
|
#include "config.h"
|
|
#include "pcrepp/pcre2pp.hh"
|
|
#include "scn/scn.h"
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
static const struct {
|
|
const char* name;
|
|
lnav::pcre2pp::code pcre;
|
|
} MATCHERS[relative_time::RTT__MAX] = {
|
|
{
|
|
"ws",
|
|
lnav::pcre2pp::code::from_const("\\A\\s+\\b"),
|
|
},
|
|
{
|
|
"am",
|
|
lnav::pcre2pp::code::from_const("\\Aam|a\\.m\\.\\b"),
|
|
},
|
|
{
|
|
"pm",
|
|
lnav::pcre2pp::code::from_const("\\Apm|p\\.m\\.\\b"),
|
|
},
|
|
{
|
|
"a",
|
|
lnav::pcre2pp::code::from_const("\\Aa\\b"),
|
|
},
|
|
{
|
|
"an",
|
|
lnav::pcre2pp::code::from_const("\\Aan\\b"),
|
|
},
|
|
{
|
|
"at",
|
|
lnav::pcre2pp::code::from_const("\\Aat\\b"),
|
|
},
|
|
{
|
|
"time",
|
|
lnav::pcre2pp::code::from_const(
|
|
"\\A(\\d{1,2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3,6}))?)?"),
|
|
},
|
|
{
|
|
"num",
|
|
lnav::pcre2pp::code::from_const("\\A((?:-|\\+)?\\d+)"),
|
|
},
|
|
|
|
{
|
|
"sun",
|
|
lnav::pcre2pp::code::from_const("\\Asun(days?)?\\b"),
|
|
},
|
|
{
|
|
"mon",
|
|
lnav::pcre2pp::code::from_const("\\Amon(days?)?\\b"),
|
|
},
|
|
{
|
|
"tue",
|
|
lnav::pcre2pp::code::from_const("\\Atue(s(days?)?)?\\b"),
|
|
},
|
|
{
|
|
"wed",
|
|
lnav::pcre2pp::code::from_const("\\Awed(nesdays?)?\\b"),
|
|
},
|
|
{
|
|
"thu",
|
|
lnav::pcre2pp::code::from_const("\\Athu(rsdays?)?\\b"),
|
|
},
|
|
{
|
|
"fri",
|
|
lnav::pcre2pp::code::from_const("\\Afri(days?)?\\b"),
|
|
},
|
|
{
|
|
"sat",
|
|
lnav::pcre2pp::code::from_const("\\Asat(urdays?)?\\b"),
|
|
},
|
|
|
|
{
|
|
"us",
|
|
lnav::pcre2pp::code::from_const(
|
|
"\\A(?:micros(?:econds?)?|us(?![a-zA-Z]))"),
|
|
},
|
|
{
|
|
"ms",
|
|
lnav::pcre2pp::code::from_const(
|
|
"\\A(?:millis(?:econds?)?|ms(?![a-zA-Z]))"),
|
|
},
|
|
{
|
|
"sec",
|
|
lnav::pcre2pp::code::from_const("\\As(?:ec(?:onds?)?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"min",
|
|
lnav::pcre2pp::code::from_const("\\Am(?:in(?:utes?)?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"h",
|
|
lnav::pcre2pp::code::from_const("\\Ah(?:ours?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"day",
|
|
lnav::pcre2pp::code::from_const("\\Ad(?:ays?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"week",
|
|
lnav::pcre2pp::code::from_const("\\Aw(?:eeks?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"mon",
|
|
lnav::pcre2pp::code::from_const("\\Amon(?:ths?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"year",
|
|
lnav::pcre2pp::code::from_const("\\Ay(?:ears?)?(?![a-zA-Z])"),
|
|
},
|
|
{
|
|
"today",
|
|
lnav::pcre2pp::code::from_const("\\Atoday\\b"),
|
|
},
|
|
{
|
|
"yest",
|
|
lnav::pcre2pp::code::from_const("\\Ayesterday\\b"),
|
|
},
|
|
{
|
|
"tomo",
|
|
lnav::pcre2pp::code::from_const("\\Atomorrow\\b"),
|
|
},
|
|
{
|
|
"noon",
|
|
lnav::pcre2pp::code::from_const("\\Anoon\\b"),
|
|
},
|
|
{
|
|
"and",
|
|
lnav::pcre2pp::code::from_const("\\Aand\\b"),
|
|
},
|
|
{
|
|
"the",
|
|
lnav::pcre2pp::code::from_const("\\Athe\\b"),
|
|
},
|
|
{
|
|
"ago",
|
|
lnav::pcre2pp::code::from_const("\\Aago\\b"),
|
|
},
|
|
{
|
|
"lter",
|
|
lnav::pcre2pp::code::from_const("\\Alater\\b"),
|
|
},
|
|
{
|
|
"bfor",
|
|
lnav::pcre2pp::code::from_const("\\Abefore\\b"),
|
|
},
|
|
{
|
|
"aft",
|
|
lnav::pcre2pp::code::from_const("\\Aafter\\b"),
|
|
},
|
|
{
|
|
"now",
|
|
lnav::pcre2pp::code::from_const("\\Anow\\b"),
|
|
},
|
|
{
|
|
"here",
|
|
lnav::pcre2pp::code::from_const("\\Ahere\\b"),
|
|
},
|
|
{
|
|
"next",
|
|
lnav::pcre2pp::code::from_const("\\Anext\\b"),
|
|
},
|
|
{
|
|
"previous",
|
|
lnav::pcre2pp::code::from_const("\\A(?:previous\\b|last\\b)"),
|
|
},
|
|
};
|
|
|
|
static int64_t TIME_SCALES[] = {
|
|
1000 * 1000,
|
|
60,
|
|
60,
|
|
24,
|
|
};
|
|
|
|
const char relative_time::FIELD_CHARS[] = {
|
|
'u',
|
|
's',
|
|
'm',
|
|
'h',
|
|
'd',
|
|
'M',
|
|
'y',
|
|
};
|
|
|
|
Result<relative_time, relative_time::parse_error>
|
|
relative_time::from_str(string_fragment str)
|
|
{
|
|
int64_t number = 0;
|
|
bool number_set = false, number_was_set = false;
|
|
bool next_set = false;
|
|
token_t base_token = RTT_INVALID;
|
|
rt_field_type last_field_type = RTF__MAX;
|
|
relative_time retval;
|
|
parse_error pe_out;
|
|
std::unordered_set<int> seen_tokens;
|
|
|
|
pe_out.pe_column = 0;
|
|
pe_out.pe_msg.clear();
|
|
|
|
auto remaining = str;
|
|
while (true) {
|
|
rt_field_type curr_field_type = RTF__MAX;
|
|
|
|
if (remaining.empty()) {
|
|
if (number_set) {
|
|
if (number > 1970 && number < 2050) {
|
|
retval.rt_field[RTF_YEARS] = number - 1900;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
|
|
switch (base_token) {
|
|
case RTT_BEFORE: {
|
|
auto epoch = retval.to_timeval();
|
|
retval.rt_duration
|
|
= std::chrono::duration_cast<
|
|
std::chrono::microseconds>(
|
|
std::chrono::seconds(epoch.tv_sec))
|
|
+ std::chrono::microseconds(epoch.tv_usec);
|
|
retval.rt_field[RTF_YEARS] = 70;
|
|
break;
|
|
}
|
|
case RTT_AFTER:
|
|
retval.rt_duration = std::chrono::duration_cast<
|
|
std::chrono::microseconds>(
|
|
std::chrono::hours(24 * 365 * 200));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return Ok(retval);
|
|
}
|
|
|
|
pe_out.pe_msg = "Number given without a time unit";
|
|
return Err(pe_out);
|
|
}
|
|
|
|
if (base_token != RTT_INVALID) {
|
|
switch (base_token) {
|
|
case RTT_BEFORE:
|
|
pe_out.pe_msg
|
|
= "'before' requires a point in time (e.g. before "
|
|
"10am)";
|
|
break;
|
|
case RTT_AFTER:
|
|
pe_out.pe_msg
|
|
= "'after' requires a point in time (e.g. after "
|
|
"10am)";
|
|
break;
|
|
default:
|
|
ensure(false);
|
|
break;
|
|
}
|
|
return Err(pe_out);
|
|
}
|
|
|
|
retval.rollover();
|
|
return Ok(retval);
|
|
}
|
|
|
|
bool found = false;
|
|
for (int lpc = 0; lpc < RTT__MAX && !found; lpc++) {
|
|
static thread_local auto md
|
|
= lnav::pcre2pp::match_data::unitialized();
|
|
|
|
token_t token = (token_t) lpc;
|
|
auto match_res = MATCHERS[lpc]
|
|
.pcre.capture_from(remaining)
|
|
.into(md)
|
|
.matches()
|
|
.ignore_error();
|
|
if (!match_res) {
|
|
continue;
|
|
}
|
|
|
|
remaining = match_res->f_remaining;
|
|
pe_out.pe_column = match_res->f_all.sf_begin;
|
|
found = true;
|
|
if (RTT_MICROS <= token && token <= RTT_YEARS) {
|
|
if (!number_set) {
|
|
if (base_token != RTT_INVALID) {
|
|
base_token = RTT_INVALID;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
continue;
|
|
}
|
|
if (!retval.rt_next && !retval.rt_previous) {
|
|
pe_out.pe_msg = "Expecting a number before time unit";
|
|
return Err(pe_out);
|
|
}
|
|
}
|
|
number_was_set = number_set;
|
|
number_set = false;
|
|
}
|
|
switch (token) {
|
|
case RTT_YESTERDAY:
|
|
case RTT_TODAY:
|
|
case RTT_NOW: {
|
|
if (seen_tokens.count(token) > 0) {
|
|
pe_out.pe_msg
|
|
= "Current time reference has already been used";
|
|
return Err(pe_out);
|
|
}
|
|
|
|
seen_tokens.insert(RTT_YESTERDAY);
|
|
seen_tokens.insert(RTT_TODAY);
|
|
seen_tokens.insert(RTT_NOW);
|
|
|
|
struct timeval tv;
|
|
struct exttm tm;
|
|
|
|
gettimeofday(&tv, nullptr);
|
|
localtime_r(&tv.tv_sec, &tm.et_tm);
|
|
tm.et_nsec = tv.tv_usec * 1000;
|
|
tm = retval.adjust(tm);
|
|
|
|
retval.rt_field[RTF_YEARS] = tm.et_tm.tm_year;
|
|
retval.rt_field[RTF_MONTHS] = tm.et_tm.tm_mon;
|
|
retval.rt_field[RTF_DAYS] = tm.et_tm.tm_mday;
|
|
switch (token) {
|
|
case RTT_NOW:
|
|
retval.rt_field[RTF_HOURS] = tm.et_tm.tm_hour;
|
|
retval.rt_field[RTF_MINUTES] = tm.et_tm.tm_min;
|
|
retval.rt_field[RTF_SECONDS] = tm.et_tm.tm_sec;
|
|
retval.rt_field[RTF_MICROSECONDS]
|
|
= tm.et_nsec / 1000;
|
|
break;
|
|
case RTT_YESTERDAY:
|
|
retval.rt_field[RTF_DAYS].value -= 1;
|
|
case RTT_TODAY:
|
|
retval.rt_field[RTF_HOURS] = 0;
|
|
retval.rt_field[RTF_MINUTES] = 0;
|
|
retval.rt_field[RTF_SECONDS] = 0;
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
break;
|
|
}
|
|
case RTT_INVALID:
|
|
case RTT_WHITE:
|
|
case RTT_AND:
|
|
case RTT_THE:
|
|
curr_field_type = last_field_type;
|
|
break;
|
|
case RTT_AM:
|
|
case RTT_PM:
|
|
if (seen_tokens.count(token) > 0) {
|
|
pe_out.pe_msg = "Time has already been set";
|
|
return Err(pe_out);
|
|
}
|
|
seen_tokens.insert(RTT_AM);
|
|
seen_tokens.insert(RTT_PM);
|
|
if (number_set) {
|
|
retval.rt_field[RTF_HOURS] = number;
|
|
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;
|
|
}
|
|
if (!retval.is_absolute(RTF_YEARS)) {
|
|
pe_out.pe_msg
|
|
= "Expecting absolute time with A.M. or P.M.";
|
|
return Err(pe_out);
|
|
}
|
|
if (token == RTT_AM) {
|
|
if (retval.rt_field[RTF_HOURS].value == 12) {
|
|
retval.rt_field[RTF_HOURS] = 0;
|
|
}
|
|
} 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);
|
|
retval.rt_field[RTF_HOURS].value = 0;
|
|
retval.rt_field[RTF_MINUTES].value = 0;
|
|
retval.rt_field[RTF_SECONDS].value = 0;
|
|
retval.rt_field[RTF_MICROSECONDS].value = 0;
|
|
}
|
|
base_token = RTT_INVALID;
|
|
break;
|
|
case RTT_A:
|
|
case RTT_AN:
|
|
number = 1;
|
|
number_set = true;
|
|
break;
|
|
case RTT_AT:
|
|
break;
|
|
case RTT_TIME: {
|
|
const auto hstr = md[1]->to_string();
|
|
const auto mstr = md[2]->to_string();
|
|
retval.rt_field[RTF_HOURS] = atoi(hstr.c_str());
|
|
retval.rt_field[RTF_MINUTES] = atoi(mstr.c_str());
|
|
if (md[3]) {
|
|
const auto sstr = md[3]->to_string();
|
|
retval.rt_field[RTF_SECONDS] = atoi(sstr.c_str());
|
|
if (md[4]) {
|
|
const auto substr = md[4]->to_string();
|
|
|
|
switch (substr.length()) {
|
|
case 3:
|
|
retval.rt_field[RTF_MICROSECONDS]
|
|
= atoi(substr.c_str()) * 1000;
|
|
break;
|
|
case 6:
|
|
retval.rt_field[RTF_MICROSECONDS]
|
|
= atoi(substr.c_str());
|
|
break;
|
|
}
|
|
} else {
|
|
retval.rt_field[RTF_MICROSECONDS].clear();
|
|
retval.rt_duration = 1s;
|
|
}
|
|
} else {
|
|
retval.rt_field[RTF_SECONDS].clear();
|
|
retval.rt_field[RTF_MICROSECONDS].clear();
|
|
retval.rt_duration = 1min;
|
|
}
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
break;
|
|
}
|
|
case RTT_NUMBER: {
|
|
if (number_set) {
|
|
pe_out.pe_msg
|
|
= "No time unit given for the previous number";
|
|
return Err(pe_out);
|
|
}
|
|
|
|
auto num_scan_res
|
|
= scn::scan_value<int64_t>(md[0]->to_string_view());
|
|
|
|
if (!num_scan_res) {
|
|
pe_out.pe_msg = fmt::format(
|
|
FMT_STRING("Invalid number: {}"), md[0].value());
|
|
return Err(pe_out);
|
|
}
|
|
number = num_scan_res.value();
|
|
number_set = true;
|
|
break;
|
|
}
|
|
case RTT_MICROS:
|
|
retval.rt_field[RTF_MICROSECONDS] = number;
|
|
break;
|
|
case RTT_MILLIS:
|
|
retval.rt_field[RTF_MICROSECONDS] = number * 1000;
|
|
break;
|
|
case RTT_SECONDS:
|
|
if (number_was_set) {
|
|
retval.rt_field[RTF_SECONDS] = number;
|
|
curr_field_type = RTF_SECONDS;
|
|
} else if (next_set) {
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
}
|
|
break;
|
|
case RTT_MINUTES:
|
|
if (number_was_set) {
|
|
retval.rt_field[RTF_MINUTES] = number;
|
|
curr_field_type = RTF_MINUTES;
|
|
} else if (next_set) {
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
retval.rt_field[RTF_SECONDS] = 0;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
}
|
|
break;
|
|
case RTT_HOURS:
|
|
if (number_was_set) {
|
|
retval.rt_field[RTF_HOURS] = number;
|
|
curr_field_type = RTF_HOURS;
|
|
} else if (next_set) {
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
retval.rt_field[RTF_SECONDS] = 0;
|
|
retval.rt_field[RTF_MINUTES] = 0;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
}
|
|
break;
|
|
case RTT_DAYS:
|
|
if (number_was_set) {
|
|
retval.rt_field[RTF_DAYS] = number;
|
|
curr_field_type = RTF_DAYS;
|
|
} else if (next_set) {
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
retval.rt_field[RTF_SECONDS] = 0;
|
|
retval.rt_field[RTF_MINUTES] = 0;
|
|
retval.rt_field[RTF_HOURS] = 0;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
}
|
|
break;
|
|
case RTT_WEEKS:
|
|
retval.rt_field[RTF_DAYS] = number * 7;
|
|
break;
|
|
case RTT_MONTHS:
|
|
if (number_was_set) {
|
|
retval.rt_field[RTF_MONTHS] = number;
|
|
curr_field_type = RTF_MONTHS;
|
|
} else if (next_set) {
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
retval.rt_field[RTF_SECONDS] = 0;
|
|
retval.rt_field[RTF_MINUTES] = 0;
|
|
retval.rt_field[RTF_HOURS] = 0;
|
|
retval.rt_field[RTF_DAYS] = 0;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
}
|
|
break;
|
|
case RTT_YEARS:
|
|
if (number_was_set) {
|
|
retval.rt_field[RTF_YEARS] = number;
|
|
curr_field_type = RTF_YEARS;
|
|
} else if (next_set) {
|
|
retval.rt_field[RTF_MICROSECONDS] = 0;
|
|
retval.rt_field[RTF_SECONDS] = 0;
|
|
retval.rt_field[RTF_MINUTES] = 0;
|
|
retval.rt_field[RTF_HOURS] = 0;
|
|
retval.rt_field[RTF_DAYS] = 0;
|
|
retval.rt_field[RTF_MONTHS] = 0;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
}
|
|
break;
|
|
case RTT_AGO:
|
|
if (retval.empty()) {
|
|
pe_out.pe_msg = "Expecting a time unit";
|
|
return Err(pe_out);
|
|
}
|
|
for (int field = 0; field < RTF__MAX; field++) {
|
|
if (retval.rt_field[field].value > 0) {
|
|
retval.rt_field[field]
|
|
= -retval.rt_field[field].value;
|
|
}
|
|
if (last_field_type != RTF__MAX
|
|
&& field < last_field_type)
|
|
{
|
|
retval.rt_field[field] = 0;
|
|
}
|
|
}
|
|
if (last_field_type != RTF__MAX) {
|
|
retval.rt_absolute_field_end = last_field_type;
|
|
}
|
|
break;
|
|
case RTT_BEFORE:
|
|
case RTT_AFTER:
|
|
if (base_token != RTT_INVALID) {
|
|
pe_out.pe_msg
|
|
= "Before/after ranges are not supported yet";
|
|
return Err(pe_out);
|
|
}
|
|
base_token = token;
|
|
break;
|
|
case RTT_LATER:
|
|
if (retval.empty()) {
|
|
pe_out.pe_msg = "Expecting a time unit before 'later'";
|
|
return Err(pe_out);
|
|
}
|
|
break;
|
|
case RTT_HERE:
|
|
break;
|
|
case RTT_NEXT:
|
|
retval.rt_next = true;
|
|
next_set = true;
|
|
break;
|
|
case RTT_PREVIOUS:
|
|
retval.rt_previous = true;
|
|
next_set = true;
|
|
break;
|
|
case RTT_TOMORROW:
|
|
retval.rt_field[RTF_DAYS] = 1;
|
|
break;
|
|
case RTT_NOON:
|
|
retval.rt_field[RTF_HOURS] = 12;
|
|
retval.rt_absolute_field_end = RTF__MAX;
|
|
for (int lpc2 = RTF_MICROSECONDS; lpc2 < RTF_HOURS; lpc2++)
|
|
{
|
|
retval.rt_field[lpc2] = 0;
|
|
}
|
|
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) {
|
|
switch (base_token) {
|
|
case RTT_BEFORE:
|
|
if (token == RTT_SUNDAY) {
|
|
pe_out.pe_msg
|
|
= "Sunday is the start of the week, so "
|
|
"there is nothing before it";
|
|
return Err(pe_out);
|
|
}
|
|
for (int wday = RTT_SUNDAY; wday < token;
|
|
wday++)
|
|
{
|
|
retval.rt_included_days.insert(
|
|
(token_t) wday);
|
|
}
|
|
break;
|
|
case RTT_AFTER:
|
|
if (token == RTT_SATURDAY) {
|
|
pe_out.pe_msg
|
|
= "Saturday is the end of the week, so "
|
|
"there is nothing after it";
|
|
return Err(pe_out);
|
|
}
|
|
for (int wday = RTT_SATURDAY; wday > token;
|
|
wday--)
|
|
{
|
|
retval.rt_included_days.insert(
|
|
(token_t) wday);
|
|
}
|
|
break;
|
|
default:
|
|
retval.rt_included_days.insert(token);
|
|
break;
|
|
}
|
|
base_token = RTT_INVALID;
|
|
} else {
|
|
retval.rt_included_days.insert(token);
|
|
}
|
|
if (retval.rt_duration == 0s) {
|
|
retval.rt_duration = 24h;
|
|
}
|
|
break;
|
|
|
|
case RTT__MAX:
|
|
ensure(false);
|
|
break;
|
|
}
|
|
|
|
if (token != RTT_NEXT && token != RTT_PREVIOUS
|
|
&& token != RTT_WHITE)
|
|
{
|
|
next_set = false;
|
|
}
|
|
|
|
number_was_set = false;
|
|
seen_tokens.insert(token);
|
|
}
|
|
|
|
if (!found) {
|
|
pe_out.pe_msg = "Unrecognized input";
|
|
return Err(pe_out);
|
|
}
|
|
|
|
last_field_type = curr_field_type;
|
|
}
|
|
}
|
|
|
|
void
|
|
relative_time::rollover()
|
|
{
|
|
for (int lpc = 0; lpc < RTF_DAYS; lpc++) {
|
|
if (!this->rt_field[lpc].is_set) {
|
|
continue;
|
|
}
|
|
int64_t val = this->rt_field[lpc].value;
|
|
this->rt_field[lpc].value = val % TIME_SCALES[lpc];
|
|
this->rt_field[lpc + 1].value += val / TIME_SCALES[lpc];
|
|
if (this->rt_field[lpc + 1].value) {
|
|
this->rt_field[lpc + 1].is_set = true;
|
|
}
|
|
}
|
|
if (std::abs(this->rt_field[RTF_DAYS].value) > 31) {
|
|
int64_t val = this->rt_field[RTF_DAYS].value;
|
|
this->rt_field[RTF_DAYS].value = val % 31;
|
|
this->rt_field[RTF_MONTHS].value += val / 31;
|
|
if (this->rt_field[RTF_MONTHS].value) {
|
|
this->rt_field[RTF_MONTHS].is_set = true;
|
|
}
|
|
}
|
|
if (std::abs(this->rt_field[RTF_MONTHS].value) > 12) {
|
|
int64_t val = this->rt_field[RTF_MONTHS].value;
|
|
this->rt_field[RTF_MONTHS].value = val % 12;
|
|
this->rt_field[RTF_YEARS].value += val / 12;
|
|
if (this->rt_field[RTF_YEARS].value) {
|
|
this->rt_field[RTF_YEARS].is_set = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
relative_time retval;
|
|
|
|
retval.clear();
|
|
retval.rt_field[RTF_MICROSECONDS] = usecs.count();
|
|
retval.rollover();
|
|
|
|
return retval;
|
|
}
|
|
|
|
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 " : (this->rt_previous ? "last " : ""));
|
|
if (this->rt_field[RTF_YEARS].is_set
|
|
&& (this->rt_next || this->rt_previous
|
|
|| this->rt_field[RTF_YEARS].value != 0))
|
|
{
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
"year %" PRId64 " ",
|
|
this->rt_field[RTF_YEARS].value);
|
|
} else if ((this->rt_next || this->rt_previous)
|
|
&& this->rt_field[RTF_MONTHS].is_set)
|
|
{
|
|
pos += snprintf(pos, sizeof(dst) - (pos - dst), "year ");
|
|
}
|
|
if (this->rt_field[RTF_MONTHS].is_set
|
|
&& (this->rt_next || this->rt_previous
|
|
|| this->rt_field[RTF_MONTHS].value != 0))
|
|
{
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
"month %" PRId64 " ",
|
|
this->rt_field[RTF_MONTHS].value);
|
|
} else if ((this->rt_next || this->rt_previous)
|
|
&& this->rt_field[RTF_DAYS].is_set)
|
|
{
|
|
pos += snprintf(pos, sizeof(dst) - (pos - dst), "month ");
|
|
}
|
|
if (this->rt_field[RTF_DAYS].is_set
|
|
&& (this->rt_next || this->rt_previous
|
|
|| this->rt_field[RTF_DAYS].value != 0))
|
|
{
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
"day %" PRId64 " ",
|
|
this->rt_field[RTF_DAYS].value);
|
|
} else if ((this->rt_next || this->rt_previous)
|
|
&& this->rt_field[RTF_HOURS].is_set)
|
|
{
|
|
pos += snprintf(pos, sizeof(dst) - (pos - dst), "day ");
|
|
}
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
"%" PRId64 ":%02" PRId64,
|
|
this->rt_field[RTF_HOURS].value,
|
|
this->rt_field[RTF_MINUTES].value);
|
|
if (this->rt_field[RTF_SECONDS].is_set
|
|
&& this->rt_field[RTF_SECONDS].value != 0)
|
|
{
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
":%.02" PRId64,
|
|
this->rt_field[RTF_SECONDS].value);
|
|
if (this->rt_field[RTF_MICROSECONDS].is_set
|
|
&& this->rt_field[RTF_MICROSECONDS].value != 0)
|
|
{
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
".%.03" PRId64,
|
|
this->rt_field[RTF_MICROSECONDS].value / 1000);
|
|
}
|
|
}
|
|
} else {
|
|
for (int lpc = RTF__MAX - 1; lpc >= 0; lpc--) {
|
|
if (this->rt_field[lpc].value == 0) {
|
|
continue;
|
|
}
|
|
pos += snprintf(pos,
|
|
sizeof(dst) - (pos - dst),
|
|
"%" PRId64 "%c",
|
|
this->rt_field[lpc].value,
|
|
FIELD_CHARS[lpc]);
|
|
}
|
|
}
|
|
|
|
if (dst[0] == '\0') {
|
|
dst[0] = '0';
|
|
dst[1] = 's';
|
|
dst[2] = '\0';
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
std::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 std::nullopt;
|
|
}
|
|
retval.et_tm.tm_year = this->rt_field[RTF_YEARS].value;
|
|
clear = true;
|
|
}
|
|
|
|
if (this->rt_field[RTF_MONTHS].is_set) {
|
|
if (this->rt_field[RTF_MONTHS].value > tm.et_tm.tm_mon) {
|
|
return std::nullopt;
|
|
}
|
|
retval.et_tm.tm_mon = this->rt_field[RTF_MONTHS].value;
|
|
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 std::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 std::nullopt;
|
|
}
|
|
clear = true;
|
|
}
|
|
|
|
if (this->rt_field[RTF_HOURS].is_set) {
|
|
if (this->rt_field[RTF_HOURS].value > tm.et_tm.tm_hour) {
|
|
return std::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 std::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 std::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 std::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 std::nullopt;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
int64_t
|
|
relative_time::to_microseconds() const
|
|
{
|
|
int64_t retval;
|
|
|
|
if (this->is_absolute()) {
|
|
struct exttm etm;
|
|
|
|
memset(&etm, 0, sizeof(etm));
|
|
etm.et_tm.tm_year = this->rt_field[RTF_YEARS].value;
|
|
etm.et_tm.tm_mon = this->rt_field[RTF_MONTHS].value;
|
|
if (this->rt_field[RTF_DAYS].is_set) {
|
|
etm.et_tm.tm_mday = this->rt_field[RTF_DAYS].value;
|
|
} else {
|
|
etm.et_tm.tm_mday = 1;
|
|
}
|
|
etm.et_tm.tm_min = this->rt_field[RTF_MINUTES].value;
|
|
etm.et_tm.tm_sec = this->rt_field[RTF_SECONDS].value;
|
|
|
|
auto epoch_secs = std::chrono::seconds(etm.to_timeval().tv_sec);
|
|
retval
|
|
= std::chrono::duration_cast<std::chrono::microseconds>(epoch_secs)
|
|
.count();
|
|
retval += this->rt_field[RTF_MICROSECONDS].value;
|
|
} else {
|
|
retval = this->rt_field[RTF_YEARS].value * 12;
|
|
retval = (retval + this->rt_field[RTF_MONTHS].value) * 30;
|
|
retval = (retval + this->rt_field[RTF_DAYS].value) * 24;
|
|
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;
|
|
}
|