mirror of
https://github.com/tstack/lnav.git
synced 2024-08-16 16:30:25 +03:00
[cmd] add support for times in goto
This commit is contained in:
parent
7cd7bf4dc4
commit
ec473edc85
7
NEWS
7
NEWS
@ -29,6 +29,13 @@ lnav v0.8.0:
|
|||||||
* When typing in a command, the status bar will display a short
|
* When typing in a command, the status bar will display a short
|
||||||
summary of the currently entered command.
|
summary of the currently entered command.
|
||||||
* Added a "delete-filter" command.
|
* Added a "delete-filter" command.
|
||||||
|
* The 'goto' command now supports relative time values like
|
||||||
|
'a minute ago', 'an hour later', and many more.
|
||||||
|
|
||||||
|
Interface Changes:
|
||||||
|
* The 'r/R' hotkeys have been reassigned to navigate through the log
|
||||||
|
messages by the relative time value that was last used with the
|
||||||
|
'goto' command.
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
* The pretty-print view should now work for text files.
|
* The pretty-print view should now work for text files.
|
||||||
|
@ -29,8 +29,9 @@ with the following commands:
|
|||||||
Navigation
|
Navigation
|
||||||
----------
|
----------
|
||||||
|
|
||||||
* goto <line#|N%|time> - Go to the given line number, N percent into the
|
* goto <line#|N%|abs-time|relative-time> - Go to the given line number, N
|
||||||
file, or the given timestamp in the log view.
|
percent into the file, the given timestamp in the log view, or by the
|
||||||
|
relative time (e.g. 'a minute ago').
|
||||||
* relative-goto <line#|N%> - Move the current view up or down by the given
|
* relative-goto <line#|N%> - Move the current view up or down by the given
|
||||||
amount.
|
amount.
|
||||||
* next-mark error|warning|search|user|file|partition - Move to the next
|
* next-mark error|warning|search|user|file|partition - Move to the next
|
||||||
|
@ -146,6 +146,9 @@ Chronological Navigation
|
|||||||
* - |ks| 0 |ke|
|
* - |ks| 0 |ke|
|
||||||
- |ks| Shift |ke| + |ks| 0 |ke|
|
- |ks| Shift |ke| + |ks| 0 |ke|
|
||||||
- Next/previous day
|
- Next/previous day
|
||||||
|
* - |ks| r |ke|
|
||||||
|
- |ks| Shift |ke| + |ks| r |ke|
|
||||||
|
- Forward/backward by the relative time that was last used with the goto command.
|
||||||
|
|
||||||
Bookmarks
|
Bookmarks
|
||||||
---------
|
---------
|
||||||
|
@ -38,6 +38,7 @@ set(diag_STAT_SRCS
|
|||||||
readline_curses.cc
|
readline_curses.cc
|
||||||
readline_highlighters.cc
|
readline_highlighters.cc
|
||||||
readline_possibilities.cc
|
readline_possibilities.cc
|
||||||
|
relative_time.cc
|
||||||
session_data.cc
|
session_data.cc
|
||||||
sequence_matcher.cc
|
sequence_matcher.cc
|
||||||
shared_buffer.cc
|
shared_buffer.cc
|
||||||
@ -108,6 +109,7 @@ set(diag_STAT_SRCS
|
|||||||
pthreadpp.hh
|
pthreadpp.hh
|
||||||
readline_callbacks.hh
|
readline_callbacks.hh
|
||||||
readline_possibilities.hh
|
readline_possibilities.hh
|
||||||
|
relative_time.hh
|
||||||
sequence_sink.hh
|
sequence_sink.hh
|
||||||
status_controllers.hh
|
status_controllers.hh
|
||||||
strong_int.hh
|
strong_int.hh
|
||||||
|
@ -151,6 +151,7 @@ noinst_HEADERS = \
|
|||||||
readline_curses.hh \
|
readline_curses.hh \
|
||||||
readline_highlighters.hh \
|
readline_highlighters.hh \
|
||||||
readline_possibilities.hh \
|
readline_possibilities.hh \
|
||||||
|
relative_time.hh \
|
||||||
sequence_matcher.hh \
|
sequence_matcher.hh \
|
||||||
sequence_sink.hh \
|
sequence_sink.hh \
|
||||||
session_data.hh \
|
session_data.hh \
|
||||||
@ -235,6 +236,7 @@ libdiag_a_SOURCES = \
|
|||||||
readline_curses.cc \
|
readline_curses.cc \
|
||||||
readline_highlighters.cc \
|
readline_highlighters.cc \
|
||||||
readline_possibilities.cc \
|
readline_possibilities.cc \
|
||||||
|
relative_time.cc \
|
||||||
session_data.cc \
|
session_data.cc \
|
||||||
sequence_matcher.cc \
|
sequence_matcher.cc \
|
||||||
shared_buffer.cc \
|
shared_buffer.cc \
|
||||||
|
18
src/help.txt
18
src/help.txt
@ -207,6 +207,13 @@ through the file.
|
|||||||
|
|
||||||
0/Shift 0 Move to the next/previous day boundary.
|
0/Shift 0 Move to the next/previous day boundary.
|
||||||
|
|
||||||
|
r/R Move forward/backward based on the relative time that
|
||||||
|
was last used with the 'goto' command. For example,
|
||||||
|
executing ':goto a minute later' will move the log view
|
||||||
|
forward a minute and then pressing 'r' will move it
|
||||||
|
forward a minute again. Pressing 'R' will then move the
|
||||||
|
view in the opposite direction, so backwards a minute.
|
||||||
|
|
||||||
m Mark/unmark the line at the top of the display.
|
m Mark/unmark the line at the top of the display.
|
||||||
The line will be highlighted with reverse video to
|
The line will be highlighted with reverse video to
|
||||||
indicate that it is a user bookmark. You can use
|
indicate that it is a user bookmark. You can use
|
||||||
@ -345,9 +352,6 @@ through the file.
|
|||||||
|
|
||||||
CTRL-W Toggle word-wrapping.
|
CTRL-W Toggle word-wrapping.
|
||||||
|
|
||||||
r/R Restore the next/previous session. The current session is
|
|
||||||
saved and then the new state is restored.
|
|
||||||
|
|
||||||
F2 Toggle mouse support.
|
F2 Toggle mouse support.
|
||||||
|
|
||||||
|
|
||||||
@ -383,11 +387,15 @@ COMMANDS
|
|||||||
current-time Print the current time in human-readable form and
|
current-time Print the current time in human-readable form and
|
||||||
as a unix-timestamp.
|
as a unix-timestamp.
|
||||||
|
|
||||||
goto <line#|N%|time>
|
goto <line#|N%|abs-time|relative-time>
|
||||||
Go to the given line number, N percent into the
|
Go to the given line number, N percent into the
|
||||||
file, or the given timestamp in the log view. If the
|
file, or the given timestamp in the log view. If the
|
||||||
line number is negative, it is considered an offset
|
line number is negative, it is considered an offset
|
||||||
from the last line.
|
from the last line. Relative time values, like
|
||||||
|
'a minute ago', 'an hour later', and many other formats
|
||||||
|
are supported. When using a relative time, the 'r/R'
|
||||||
|
hotkeys can be used to move the same amount again or in
|
||||||
|
the same amount in the opposite direction.
|
||||||
|
|
||||||
relative-goto <line#|N%>
|
relative-goto <line#|N%>
|
||||||
Move the current view up or down by the given amount.
|
Move the current view up or down by the given amount.
|
||||||
|
@ -1030,35 +1030,44 @@ void handle_paging_key(int ch)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
if (!lnav_data.ld_session_file_names.empty()) {
|
|
||||||
lnav_data.ld_session_file_index =
|
|
||||||
(lnav_data.ld_session_file_index + 1) %
|
|
||||||
lnav_data.ld_session_file_names.size();
|
|
||||||
reset_session();
|
|
||||||
load_session();
|
|
||||||
rebuild_indexes(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'R':
|
case 'R':
|
||||||
if (lnav_data.ld_session_file_index == 0) {
|
if (lss) {
|
||||||
lnav_data.ld_session_file_index =
|
if (lnav_data.ld_last_relative_time.empty()) {
|
||||||
lnav_data.ld_session_file_names.size() - 1;
|
lnav_data.ld_rl_view->set_value(
|
||||||
|
"Use the 'goto' command to set the relative time to move by");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vis_line_t vl = tc->get_top();
|
||||||
|
relative_time rt = lnav_data.ld_last_relative_time;
|
||||||
|
struct timeval tv;
|
||||||
|
content_line_t cl;
|
||||||
|
struct exttm tm;
|
||||||
|
|
||||||
|
if (ch == 'R') {
|
||||||
|
rt.negate();
|
||||||
|
}
|
||||||
|
|
||||||
|
cl = lnav_data.ld_log_source.at(vl);
|
||||||
|
logline *ll = lnav_data.ld_log_source.find_line(cl);
|
||||||
|
ll->to_exttm(tm);
|
||||||
|
rt.add(tm);
|
||||||
|
tv.tv_sec = timegm(&tm.et_tm);
|
||||||
|
tv.tv_usec = tm.et_nsec / 1000;
|
||||||
|
vl = lnav_data.ld_log_source.find_from_time(tv);
|
||||||
|
if (rt.is_negative() && (vl > vis_line_t(0))) {
|
||||||
|
--vl;
|
||||||
|
if (vl == tc->get_top()) {
|
||||||
|
vl = vis_line_t(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tc->set_top(vl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
lnav_data.ld_session_file_index -= 1;
|
|
||||||
}
|
|
||||||
reset_session();
|
|
||||||
load_session();
|
|
||||||
rebuild_indexes(true);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KEY_CTRL_R:
|
case KEY_CTRL_R:
|
||||||
reset_session();
|
reset_session();
|
||||||
rebuild_indexes(true);
|
rebuild_indexes(true);
|
||||||
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
|
|
||||||
r, R,
|
|
||||||
"to restore the next/previous session"));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KEY_CTRL_W:
|
case KEY_CTRL_W:
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
#include "ansi_scrubber.hh"
|
#include "ansi_scrubber.hh"
|
||||||
#include "curl_looper.hh"
|
#include "curl_looper.hh"
|
||||||
#include "papertrail_proc.hh"
|
#include "papertrail_proc.hh"
|
||||||
|
#include "relative_time.hh"
|
||||||
|
|
||||||
/** The command modes that are available while viewing a file. */
|
/** The command modes that are available while viewing a file. */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -248,6 +249,8 @@ struct _lnav_data {
|
|||||||
input_state_tracker ld_input_state;
|
input_state_tracker ld_input_state;
|
||||||
|
|
||||||
curl_looper ld_curl_looper;
|
curl_looper ld_curl_looper;
|
||||||
|
|
||||||
|
relative_time ld_last_relative_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct _lnav_data lnav_data;
|
extern struct _lnav_data lnav_data;
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
#include "command_executor.hh"
|
#include "command_executor.hh"
|
||||||
#include "url_loader.hh"
|
#include "url_loader.hh"
|
||||||
#include "readline_curses.hh"
|
#include "readline_curses.hh"
|
||||||
|
#include "relative_time.hh"
|
||||||
#include "log_search_table.hh"
|
#include "log_search_table.hh"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -249,20 +250,52 @@ static string com_current_time(string cmdline, vector<string> &args)
|
|||||||
|
|
||||||
static string com_goto(string cmdline, vector<string> &args)
|
static string com_goto(string cmdline, vector<string> &args)
|
||||||
{
|
{
|
||||||
string retval = "error: expecting line number/percentage or timestamp";
|
string retval = "error: expecting line number/percentage, timestamp, or relative time";
|
||||||
|
|
||||||
if (args.size() == 0) {
|
if (args.size() == 0) {
|
||||||
args.push_back("line-time");
|
args.push_back("line-time");
|
||||||
}
|
}
|
||||||
else if (args.size() > 1) {
|
else if (args.size() > 1) {
|
||||||
|
string all_args = cmdline.substr(cmdline.find(args[1], args[0].size()));
|
||||||
textview_curses *tc = lnav_data.ld_view_stack.top();
|
textview_curses *tc = lnav_data.ld_view_stack.top();
|
||||||
int line_number, consumed;
|
int line_number, consumed;
|
||||||
date_time_scanner dts;
|
date_time_scanner dts;
|
||||||
|
struct relative_time::parse_error pe;
|
||||||
|
relative_time rt;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
struct exttm tm;
|
struct exttm tm;
|
||||||
float value;
|
float value;
|
||||||
|
|
||||||
if (dts.scan(args[1].c_str(), args[1].size(), NULL, &tm, tv) != NULL) {
|
if (rt.parse(all_args, pe)) {
|
||||||
|
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
||||||
|
content_line_t cl;
|
||||||
|
vis_line_t vl;
|
||||||
|
logline *ll;
|
||||||
|
|
||||||
|
if (!rt.is_absolute()) {
|
||||||
|
lnav_data.ld_last_relative_time = rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
vl = tc->get_top();
|
||||||
|
cl = lnav_data.ld_log_source.at(vl);
|
||||||
|
ll = lnav_data.ld_log_source.find_line(cl);
|
||||||
|
ll->to_exttm(tm);
|
||||||
|
rt.add(tm);
|
||||||
|
tv.tv_sec = timegm(&tm.et_tm);
|
||||||
|
tv.tv_usec = tm.et_nsec / 1000;
|
||||||
|
|
||||||
|
vl = lnav_data.ld_log_source.find_from_time(tv);
|
||||||
|
tc->set_top(vl);
|
||||||
|
retval = "";
|
||||||
|
if (!rt.is_absolute() && lnav_data.ld_rl_view != NULL) {
|
||||||
|
lnav_data.ld_rl_view->set_alt_value(
|
||||||
|
HELP_MSG_2(r, R, "to move forward/backward the same amount of time"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
retval = "error: relative time values only work in the log view";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dts.scan(args[1].c_str(), args[1].size(), NULL, &tm, tv) != NULL) {
|
||||||
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
||||||
vis_line_t vl;
|
vis_line_t vl;
|
||||||
|
|
||||||
@ -751,6 +784,7 @@ static string com_highlight(string cmdline, vector<string> &args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
retval = "info: highlight pattern now active";
|
retval = "info: highlight pattern now active";
|
||||||
|
tc->reload_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -777,6 +811,7 @@ static string com_clear_highlight(string cmdline, vector<string> &args)
|
|||||||
else {
|
else {
|
||||||
hm.erase(hm_iter);
|
hm.erase(hm_iter);
|
||||||
retval = "info: highlight pattern cleared";
|
retval = "info: highlight pattern cleared";
|
||||||
|
tc->reload_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1945,10 +1980,6 @@ static string com_redraw(string cmdline, vector<string> &args)
|
|||||||
}
|
}
|
||||||
else if (lnav_data.ld_window) {
|
else if (lnav_data.ld_window) {
|
||||||
redrawwin(lnav_data.ld_window);
|
redrawwin(lnav_data.ld_window);
|
||||||
if (lnav_data.ld_rl_view != NULL) {
|
|
||||||
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
|
|
||||||
CTRL-L, "to redraw the window"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
@ -430,6 +430,7 @@ const char *date_time_scanner::scan(const char *time_dest,
|
|||||||
}
|
}
|
||||||
tv_out.tv_sec = gmt;
|
tv_out.tv_sec = gmt;
|
||||||
tv_out.tv_usec = 0;
|
tv_out.tv_usec = 0;
|
||||||
|
tm_out->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET;
|
||||||
|
|
||||||
this->dts_fmt_lock = curr_time_fmt;
|
this->dts_fmt_lock = curr_time_fmt;
|
||||||
this->dts_fmt_len = off;
|
this->dts_fmt_len = off;
|
||||||
|
@ -288,40 +288,46 @@ const char *log_format::log_scanf(const char *line,
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void log_format::check_for_new_year(std::vector<logline> &dst,
|
void log_format::check_for_new_year(std::vector<logline> &dst, exttm etm,
|
||||||
const struct timeval &log_tv)
|
struct timeval log_tv)
|
||||||
{
|
{
|
||||||
if (dst.empty()) {
|
if (dst.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t diff = dst.back().get_time() - log_tv.tv_sec;
|
time_t diff = dst.back().get_time() - log_tv.tv_sec;
|
||||||
|
int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
|
||||||
|
std::vector<logline>::iterator iter;
|
||||||
|
bool do_change = true;
|
||||||
|
|
||||||
if (diff > (5 * 60)) {
|
if (diff <= 0) {
|
||||||
int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
|
return;
|
||||||
std::vector<logline>::iterator iter;
|
}
|
||||||
|
if (diff > (60 * 24 * 60 * 60)) {
|
||||||
|
off_year = 1;
|
||||||
|
} else if (diff > (15 * 24 * 60 * 60)) {
|
||||||
|
off_month = 1;
|
||||||
|
} else if (diff > (12 * 60 * 60)) {
|
||||||
|
off_day = 1;
|
||||||
|
} else if (!(etm.et_flags & ETF_DAY_SET)) {
|
||||||
|
off_hour = 1;
|
||||||
|
} else {
|
||||||
|
do_change = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (diff > (60 * 24 * 60 * 60)) {
|
if (!do_change) {
|
||||||
off_year = 1;
|
return;
|
||||||
} else if (diff > (15 * 24 * 60 * 60)) {
|
}
|
||||||
off_month = 1;
|
for (iter = dst.begin(); iter != dst.end(); iter++) {
|
||||||
} else if (diff > (12 * 60 * 60)) {
|
time_t ot = iter->get_time();
|
||||||
off_day = 1;
|
struct tm otm;
|
||||||
} else {
|
|
||||||
off_hour = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (iter = dst.begin(); iter != dst.end(); iter++) {
|
gmtime_r(&ot, &otm);
|
||||||
time_t ot = iter->get_time();
|
otm.tm_year -= off_year;
|
||||||
struct tm otm;
|
otm.tm_mon -= off_month;
|
||||||
|
otm.tm_yday -= off_day;
|
||||||
gmtime_r(&ot, &otm);
|
otm.tm_hour -= off_hour;
|
||||||
otm.tm_year -= off_year;
|
iter->set_time(tm2sec(&otm));
|
||||||
otm.tm_mon -= off_month;
|
|
||||||
otm.tm_yday -= off_day;
|
|
||||||
otm.tm_hour -= off_hour;
|
|
||||||
iter->set_time(tm2sec(&otm));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,7 +712,7 @@ bool external_log_format::scan(std::vector<logline> &dst,
|
|||||||
if (!((log_time_tm.et_flags & ETF_DAY_SET) &&
|
if (!((log_time_tm.et_flags & ETF_DAY_SET) &&
|
||||||
(log_time_tm.et_flags & ETF_MONTH_SET) &&
|
(log_time_tm.et_flags & ETF_MONTH_SET) &&
|
||||||
(log_time_tm.et_flags & ETF_YEAR_SET))) {
|
(log_time_tm.et_flags & ETF_YEAR_SET))) {
|
||||||
this->check_for_new_year(dst, log_tv);
|
this->check_for_new_year(dst, log_time_tm, log_tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mod_cap != NULL) {
|
if (mod_cap != NULL) {
|
||||||
|
@ -150,6 +150,11 @@ public:
|
|||||||
/** @return The timestamp for the line. */
|
/** @return The timestamp for the line. */
|
||||||
time_t get_time() const { return this->ll_time; };
|
time_t get_time() const { return this->ll_time; };
|
||||||
|
|
||||||
|
void to_exttm(struct exttm &tm_out) const {
|
||||||
|
tm_out.et_tm = *gmtime(&this->ll_time);
|
||||||
|
tm_out.et_nsec = this->ll_millis * 1000 * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
void set_time(time_t t) { this->ll_time = t; };
|
void set_time(time_t t) { this->ll_time = t; };
|
||||||
|
|
||||||
/** @return The millisecond timestamp for the line. */
|
/** @return The millisecond timestamp for the line. */
|
||||||
@ -255,7 +260,8 @@ public:
|
|||||||
|
|
||||||
bool operator<(const struct timeval &rhs) const {
|
bool operator<(const struct timeval &rhs) const {
|
||||||
return ((this->ll_time < rhs.tv_sec) ||
|
return ((this->ll_time < rhs.tv_sec) ||
|
||||||
(this->ll_millis < (rhs.tv_usec / 1000)));
|
((this->ll_time == rhs.tv_sec) &&
|
||||||
|
(this->ll_millis < (rhs.tv_usec / 1000))));
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -654,8 +660,8 @@ public:
|
|||||||
return &this->lf_timestamp_format[0];
|
return &this->lf_timestamp_format[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
void check_for_new_year(std::vector<logline> &dst,
|
void check_for_new_year(std::vector<logline> &dst, exttm log_tv,
|
||||||
const struct timeval &log_tv);
|
timeval timeval1);
|
||||||
|
|
||||||
virtual std::string get_pattern_name() const {
|
virtual std::string get_pattern_name() const {
|
||||||
char name[32];
|
char name[32];
|
||||||
|
@ -154,7 +154,7 @@ class generic_log_format : public log_format {
|
|||||||
logline::level_t level_val = logline::string2level(
|
logline::level_t level_val = logline::string2level(
|
||||||
level_str, level.length());
|
level_str, level.length());
|
||||||
|
|
||||||
this->check_for_new_year(dst, log_tv);
|
this->check_for_new_year(dst, log_time, log_tv);
|
||||||
|
|
||||||
dst.push_back(logline(offset, log_tv, level_val));
|
dst.push_back(logline(offset, log_tv, level_val));
|
||||||
retval = true;
|
retval = true;
|
||||||
|
@ -175,6 +175,7 @@ inline bool ptime_s(struct exttm *dst, const char *str, off_t &off_inout, ssize_
|
|||||||
}
|
}
|
||||||
|
|
||||||
secs2tm(&epoch, &dst->et_tm);
|
secs2tm(&epoch, &dst->et_tm);
|
||||||
|
dst->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET;
|
||||||
|
|
||||||
return (epoch > 0);
|
return (epoch > 0);
|
||||||
}
|
}
|
||||||
@ -241,6 +242,7 @@ inline bool ptime_i(struct exttm *dst, const char *str, off_t &off_inout, ssize_
|
|||||||
dst->et_nsec = (epoch_ms % 1000ULL) * 1000000;
|
dst->et_nsec = (epoch_ms % 1000ULL) * 1000000;
|
||||||
epoch = (epoch_ms / 1000ULL);
|
epoch = (epoch_ms / 1000ULL);
|
||||||
secs2tm(&epoch, &dst->et_tm);
|
secs2tm(&epoch, &dst->et_tm);
|
||||||
|
dst->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET;
|
||||||
|
|
||||||
return (epoch_ms > 0);
|
return (epoch_ms > 0);
|
||||||
}
|
}
|
||||||
|
274
src/relative_time.cc
Normal file
274
src/relative_time.cc
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* 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 "config.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "pcrepp.hh"
|
||||||
|
#include "lnav_util.hh"
|
||||||
|
#include "relative_time.hh"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
const char *name;
|
||||||
|
pcrepp pcre;
|
||||||
|
} MATCHERS[relative_time::RTT__MAX] = {
|
||||||
|
{ "ws", pcrepp("\\A\\s+\\b") },
|
||||||
|
{ "am", pcrepp("\\Aam|a\\.m\\.\\b") },
|
||||||
|
{ "pm", pcrepp("\\Apm|p\\.m\\.\\b") },
|
||||||
|
{ "a", pcrepp("\\Aa\\b") },
|
||||||
|
{ "an", pcrepp("\\Aan\\b") },
|
||||||
|
{ "time", pcrepp("\\A(\\d{1,2}):(\\d{2})(?::(\\d{2}))?") },
|
||||||
|
{ "num", pcrepp("\\A((?:-|\\+)?\\d+)") },
|
||||||
|
{ "us", pcrepp("\\Amicros(?:econds?)?|us(?![a-zA-Z])") },
|
||||||
|
{ "ms", pcrepp("\\Amillis(?:econds?)?|ms(?![a-zA-Z])") },
|
||||||
|
{ "sec", pcrepp("\\As(?:ec(?:onds?)?)?(?![a-zA-Z])") },
|
||||||
|
{ "min", pcrepp("\\Am(?:in(?:utes?)?)?(?![a-zA-Z])") },
|
||||||
|
{ "h", pcrepp("\\Ah(?:ours?)?(?![a-zA-Z])") },
|
||||||
|
{ "day", pcrepp("\\Ad(?:ays?)?(?![a-zA-Z])") },
|
||||||
|
{ "week", pcrepp("\\Aw(?:eeks?)?(?![a-zA-Z])") },
|
||||||
|
{ "mon", pcrepp("\\Amon(?:ths?)?(?![a-zA-Z])") },
|
||||||
|
{ "year", pcrepp("\\Ay(?:ears?)?(?![a-zA-Z])") },
|
||||||
|
{ "today", pcrepp("\\Atoday\\b") },
|
||||||
|
{ "yest", pcrepp("\\Ayesterday\\b") },
|
||||||
|
{ "tomo", pcrepp("\\Atomorrow\\b") },
|
||||||
|
{ "noon", pcrepp("\\Anoon\\b") },
|
||||||
|
{ "and", pcrepp("\\Aand\\b") },
|
||||||
|
{ "ago", pcrepp("\\Aago\\b") },
|
||||||
|
{ "lter", pcrepp("\\Alater\\b") },
|
||||||
|
{ "bfor", pcrepp("\\Abefore\\b") },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int64_t TIME_SCALES[] = {
|
||||||
|
1000 * 1000,
|
||||||
|
60,
|
||||||
|
60,
|
||||||
|
24,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_out)
|
||||||
|
{
|
||||||
|
pcre_input pi(str, 0, len);
|
||||||
|
pcre_context_static<30> pc;
|
||||||
|
int64_t number = 0;
|
||||||
|
bool number_set = false;
|
||||||
|
|
||||||
|
pe_out.pe_column = -1;
|
||||||
|
pe_out.pe_msg.clear();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (pi.pi_next_offset >= pi.pi_length) {
|
||||||
|
if (number_set) {
|
||||||
|
pe_out.pe_msg = "Number given without a time unit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->rollover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (int lpc = 0; lpc < RTT__MAX && !found; lpc++) {
|
||||||
|
token_t token = (token_t) lpc;
|
||||||
|
if (!MATCHERS[lpc].pcre.match(pc, pi, PCRE_ANCHORED)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pe_out.pe_column = pc.all()->c_begin;
|
||||||
|
found = true;
|
||||||
|
if (RTT_MICROS <= token && token <= RTT_YEARS) {
|
||||||
|
if (!number_set) {
|
||||||
|
pe_out.pe_msg = "Expecting a number before time unit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
number_set = false;
|
||||||
|
}
|
||||||
|
switch (token) {
|
||||||
|
case RTT_INVALID:
|
||||||
|
case RTT_WHITE:
|
||||||
|
case RTT_AND:
|
||||||
|
break;
|
||||||
|
case RTT_AM:
|
||||||
|
case RTT_PM:
|
||||||
|
if (number_set) {
|
||||||
|
this->rt_field[RTF_HOURS] = number;
|
||||||
|
this->rt_is_absolute[RTF_HOURS] = true;
|
||||||
|
this->rt_field[RTF_MINUTES] = 0;
|
||||||
|
this->rt_is_absolute[RTF_MINUTES] = true;
|
||||||
|
this->rt_field[RTF_SECONDS] = 0;
|
||||||
|
this->rt_is_absolute[RTF_SECONDS] = true;
|
||||||
|
this->rt_field[RTF_MICROSECONDS] = 0;
|
||||||
|
this->rt_is_absolute[RTF_MICROSECONDS] = true;
|
||||||
|
number_set = false;
|
||||||
|
}
|
||||||
|
if (!this->rt_is_absolute[RTF_HOURS]) {
|
||||||
|
pe_out.pe_msg = "Expecting absolute time with A.M. or P.M.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (token == RTT_AM) {
|
||||||
|
if (this->rt_field[RTF_HOURS] == 12) {
|
||||||
|
this->rt_field[RTF_HOURS] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->rt_field[RTF_HOURS] += 12;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RTT_A:
|
||||||
|
case RTT_AN:
|
||||||
|
number = 1;
|
||||||
|
number_set = true;
|
||||||
|
break;
|
||||||
|
case RTT_TIME: {
|
||||||
|
string hstr = pi.get_substr(pc[0]);
|
||||||
|
string mstr = pi.get_substr(pc[1]);
|
||||||
|
this->rt_field[RTF_HOURS] = atoi(hstr.c_str());
|
||||||
|
this->rt_is_absolute[RTF_HOURS] = true;
|
||||||
|
this->rt_field[RTF_MINUTES] = atoi(mstr.c_str());
|
||||||
|
this->rt_is_absolute[RTF_MINUTES] = true;
|
||||||
|
if (pc[2]->is_valid()) {
|
||||||
|
string sstr = pi.get_substr(pc[2]);
|
||||||
|
this->rt_field[RTF_SECONDS] = atoi(sstr.c_str());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->rt_field[RTF_SECONDS] = 0;
|
||||||
|
}
|
||||||
|
this->rt_is_absolute[RTF_SECONDS] = true;
|
||||||
|
this->rt_field[RTF_MICROSECONDS] = 0;
|
||||||
|
this->rt_is_absolute[RTF_MICROSECONDS] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RTT_NUMBER: {
|
||||||
|
if (number_set) {
|
||||||
|
pe_out.pe_msg = "No time unit given for the previous number";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string numstr = pi.get_substr(pc[0]);
|
||||||
|
|
||||||
|
if (sscanf(numstr.c_str(), "%qd", &number) != 1) {
|
||||||
|
pe_out.pe_msg = "Invalid number: " + numstr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
number_set = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RTT_MICROS:
|
||||||
|
this->rt_field[RTF_MICROSECONDS] = number;
|
||||||
|
break;
|
||||||
|
case RTT_MILLIS:
|
||||||
|
this->rt_field[RTF_MICROSECONDS] = number * 1000;
|
||||||
|
break;
|
||||||
|
case RTT_SECONDS:
|
||||||
|
this->rt_field[RTF_SECONDS] = number;
|
||||||
|
break;
|
||||||
|
case RTT_MINUTES:
|
||||||
|
this->rt_field[RTF_MINUTES] = number;
|
||||||
|
break;
|
||||||
|
case RTT_HOURS:
|
||||||
|
this->rt_field[RTF_HOURS] = number;
|
||||||
|
break;
|
||||||
|
case RTT_DAYS:
|
||||||
|
this->rt_field[RTF_DAYS] = number;
|
||||||
|
break;
|
||||||
|
case RTT_WEEKS:
|
||||||
|
this->rt_field[RTF_DAYS] = number * 7;
|
||||||
|
break;
|
||||||
|
case RTT_MONTHS:
|
||||||
|
this->rt_field[RTF_MONTHS] = number;
|
||||||
|
break;
|
||||||
|
case RTT_YEARS:
|
||||||
|
this->rt_field[RTF_YEARS] = number;
|
||||||
|
break;
|
||||||
|
case RTT_BEFORE:
|
||||||
|
case RTT_AGO:
|
||||||
|
if (this->empty()) {
|
||||||
|
pe_out.pe_msg = "Expecting a time unit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int field = 0; field < RTF__MAX; field++) {
|
||||||
|
if (this->rt_field[field] > 0) {
|
||||||
|
this->rt_field[field] = -this->rt_field[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RTT_LATER:
|
||||||
|
if (this->empty()) {
|
||||||
|
pe_out.pe_msg = "Expecting a time unit before 'later'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RTT_TODAY:
|
||||||
|
break;
|
||||||
|
case RTT_YESTERDAY:
|
||||||
|
this->rt_field[RTF_DAYS] = -1;
|
||||||
|
break;
|
||||||
|
case RTT_TOMORROW:
|
||||||
|
this->rt_field[RTF_DAYS] = 1;
|
||||||
|
break;
|
||||||
|
case RTT_NOON:
|
||||||
|
this->rt_field[RTF_HOURS] = 12;
|
||||||
|
this->rt_is_absolute[RTF_HOURS] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RTT__MAX:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
pe_out.pe_msg = "Unrecognized input";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void relative_time::rollover()
|
||||||
|
{
|
||||||
|
for (int lpc = 0; lpc < RTF_DAYS; lpc++) {
|
||||||
|
int64_t val = this->rt_field[lpc];
|
||||||
|
this->rt_field[lpc] = val % TIME_SCALES[lpc];
|
||||||
|
this->rt_field[lpc + 1] += val / TIME_SCALES[lpc];
|
||||||
|
}
|
||||||
|
if (std::abs(this->rt_field[RTF_DAYS]) > 31) {
|
||||||
|
int64_t val = this->rt_field[RTF_DAYS];
|
||||||
|
this->rt_field[RTF_DAYS] = val % 31;
|
||||||
|
this->rt_field[RTF_MONTHS] += val / 31;
|
||||||
|
}
|
||||||
|
if (std::abs(this->rt_field[RTF_MONTHS]) > 12) {
|
||||||
|
int64_t val = this->rt_field[RTF_MONTHS];
|
||||||
|
this->rt_field[RTF_MONTHS] = val % 12;
|
||||||
|
this->rt_field[RTF_YEARS] += val / 12;
|
||||||
|
}
|
||||||
|
}
|
214
src/relative_time.hh
Normal file
214
src/relative_time.hh
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LNAV_RELATIVE_TIME_HH
|
||||||
|
#define LNAV_RELATIVE_TIME_HH
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ptimec.hh"
|
||||||
|
|
||||||
|
class relative_time {
|
||||||
|
public:
|
||||||
|
enum token_t {
|
||||||
|
RTT_INVALID = -1,
|
||||||
|
|
||||||
|
RTT_WHITE,
|
||||||
|
RTT_AM,
|
||||||
|
RTT_PM,
|
||||||
|
RTT_A,
|
||||||
|
RTT_AN,
|
||||||
|
RTT_TIME,
|
||||||
|
RTT_NUMBER,
|
||||||
|
RTT_MICROS,
|
||||||
|
RTT_MILLIS,
|
||||||
|
RTT_SECONDS,
|
||||||
|
RTT_MINUTES,
|
||||||
|
RTT_HOURS,
|
||||||
|
RTT_DAYS,
|
||||||
|
RTT_WEEKS,
|
||||||
|
RTT_MONTHS,
|
||||||
|
RTT_YEARS,
|
||||||
|
RTT_TODAY,
|
||||||
|
RTT_YESTERDAY,
|
||||||
|
RTT_TOMORROW,
|
||||||
|
RTT_NOON,
|
||||||
|
RTT_AND,
|
||||||
|
RTT_AGO,
|
||||||
|
RTT_LATER,
|
||||||
|
RTT_BEFORE,
|
||||||
|
|
||||||
|
RTT__MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
relative_time() {
|
||||||
|
this->clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
memset(this->rt_field, 0, sizeof(this->rt_field));
|
||||||
|
memset(this->rt_is_absolute, 0, sizeof(this->rt_is_absolute));
|
||||||
|
};
|
||||||
|
|
||||||
|
void negate() {
|
||||||
|
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||||
|
if (!this->rt_is_absolute[lpc] && this->rt_field[lpc] != 0) {
|
||||||
|
this->rt_field[lpc] = -this->rt_field[lpc];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_negative() const {
|
||||||
|
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||||
|
if (this->rt_field[lpc] < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_absolute() const {
|
||||||
|
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||||
|
if (this->rt_is_absolute[lpc]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||||
|
if (this->rt_field[lpc]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct parse_error {
|
||||||
|
int pe_column;
|
||||||
|
std::string pe_msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool parse(const char *str, size_t len, struct parse_error &pe_out);
|
||||||
|
|
||||||
|
bool parse(const std::string &str, struct parse_error &pe_out) {
|
||||||
|
return this->parse(str.c_str(), str.length(), pe_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(struct exttm &tm) {
|
||||||
|
if (this->rt_is_absolute[RTF_MICROSECONDS]) {
|
||||||
|
tm.et_nsec = this->rt_field[RTF_MICROSECONDS] * 1000;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_nsec += this->rt_field[RTF_MICROSECONDS] * 1000;
|
||||||
|
}
|
||||||
|
if (this->rt_is_absolute[RTF_SECONDS]) {
|
||||||
|
tm.et_tm.tm_sec = this->rt_field[RTF_SECONDS];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_tm.tm_sec += this->rt_field[RTF_SECONDS];
|
||||||
|
}
|
||||||
|
if (this->rt_is_absolute[RTF_MINUTES]) {
|
||||||
|
tm.et_tm.tm_min = this->rt_field[RTF_MINUTES];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_tm.tm_min += this->rt_field[RTF_MINUTES];
|
||||||
|
}
|
||||||
|
if (this->rt_is_absolute[RTF_HOURS]) {
|
||||||
|
tm.et_tm.tm_hour = this->rt_field[RTF_HOURS];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_tm.tm_hour += this->rt_field[RTF_HOURS];
|
||||||
|
}
|
||||||
|
if (this->rt_is_absolute[RTF_DAYS]) {
|
||||||
|
tm.et_tm.tm_mday = this->rt_field[RTF_DAYS];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_tm.tm_mday += this->rt_field[RTF_DAYS];
|
||||||
|
}
|
||||||
|
if (this->rt_is_absolute[RTF_MONTHS]) {
|
||||||
|
tm.et_tm.tm_mon = this->rt_field[RTF_MONTHS];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_tm.tm_mon += this->rt_field[RTF_MONTHS];
|
||||||
|
}
|
||||||
|
if (this->rt_is_absolute[RTF_YEARS]) {
|
||||||
|
tm.et_tm.tm_year = this->rt_field[RTF_YEARS];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tm.et_tm.tm_year += this->rt_field[RTF_YEARS];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string to_string() {
|
||||||
|
char dst[128];
|
||||||
|
|
||||||
|
snprintf(dst, sizeof(dst),
|
||||||
|
"%qd%c%qd%c%qd%c%qd%c%qd%c%qd%c%qd%c",
|
||||||
|
this->rt_field[RTF_YEARS],
|
||||||
|
this->rt_is_absolute[RTF_YEARS] ? 'Y' : 'y',
|
||||||
|
this->rt_field[RTF_MONTHS],
|
||||||
|
this->rt_is_absolute[RTF_MONTHS] ? 'M' : 'm',
|
||||||
|
this->rt_field[RTF_DAYS],
|
||||||
|
this->rt_is_absolute[RTF_DAYS] ? 'D' : 'd',
|
||||||
|
this->rt_field[RTF_HOURS],
|
||||||
|
this->rt_is_absolute[RTF_HOURS] ? 'H' : 'h',
|
||||||
|
this->rt_field[RTF_MINUTES],
|
||||||
|
this->rt_is_absolute[RTF_MINUTES] ? 'M' : 'm',
|
||||||
|
this->rt_field[RTF_SECONDS],
|
||||||
|
this->rt_is_absolute[RTF_SECONDS] ? 'S' : 's',
|
||||||
|
this->rt_field[RTF_MICROSECONDS],
|
||||||
|
this->rt_is_absolute[RTF_MICROSECONDS] ? 'U' : 'u');
|
||||||
|
return dst;
|
||||||
|
};
|
||||||
|
|
||||||
|
void rollover();
|
||||||
|
|
||||||
|
enum {
|
||||||
|
RTF_MICROSECONDS,
|
||||||
|
RTF_SECONDS,
|
||||||
|
RTF_MINUTES,
|
||||||
|
RTF_HOURS,
|
||||||
|
RTF_DAYS,
|
||||||
|
RTF_MONTHS,
|
||||||
|
RTF_YEARS,
|
||||||
|
|
||||||
|
RTF__MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
int64_t rt_field[RTF__MAX];
|
||||||
|
bool rt_is_absolute[RTF__MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //LNAV_RELATIVE_TIME_HH
|
@ -1300,7 +1300,6 @@ void reset_session(void)
|
|||||||
textview_curses::highlight_map_t &hmap =
|
textview_curses::highlight_map_t &hmap =
|
||||||
lnav_data.ld_views[LNV_LOG].get_highlights();
|
lnav_data.ld_views[LNV_LOG].get_highlights();
|
||||||
textview_curses::highlight_map_t::iterator hl_iter = hmap.begin();
|
textview_curses::highlight_map_t::iterator hl_iter = hmap.begin();
|
||||||
logfile_sub_source &lss = lnav_data.ld_log_source;
|
|
||||||
|
|
||||||
log_info("reset session: time=%d", lnav_data.ld_session_time);
|
log_info("reset session: time=%d", lnav_data.ld_session_time);
|
||||||
|
|
||||||
@ -1328,8 +1327,19 @@ void reset_session(void)
|
|||||||
lf->clear_time_offset();
|
lf->clear_time_offset();
|
||||||
}
|
}
|
||||||
|
|
||||||
lnav_data.ld_log_source.get_filters().clear_filters();
|
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
|
||||||
|
textview_curses &tc = lnav_data.ld_views[lpc];
|
||||||
|
text_sub_source *tss = tc.get_sub_source();
|
||||||
|
|
||||||
lss.get_user_bookmarks()[&textview_curses::BM_USER].clear();
|
if (tss == NULL) {
|
||||||
lss.get_user_bookmarks()[&textview_curses::BM_PARTITION].clear();
|
continue;
|
||||||
|
}
|
||||||
|
tss->get_filters().clear_filters();
|
||||||
|
tss->text_filters_changed();
|
||||||
|
tss->text_clear_marks(&textview_curses::BM_USER);
|
||||||
|
tc.get_bookmarks()[&textview_curses::BM_USER].clear();
|
||||||
|
tss->text_clear_marks(&textview_curses::BM_PARTITION);
|
||||||
|
tc.get_bookmarks()[&textview_curses::BM_PARTITION].clear();
|
||||||
|
tc.reload_data();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,10 @@ include_directories(../../lbuild/src ../src/ /opt/local/include)
|
|||||||
|
|
||||||
add_executable(test_chunky_index test_chunky_index.cc)
|
add_executable(test_chunky_index test_chunky_index.cc)
|
||||||
add_executable(test_pcrepp test_pcrepp.cc ../src/lnav_log.cc ../src/pcrepp.cc)
|
add_executable(test_pcrepp test_pcrepp.cc ../src/lnav_log.cc ../src/pcrepp.cc)
|
||||||
|
add_executable(test_reltime test_reltime.cc
|
||||||
|
../src/relative_time.cc
|
||||||
|
../src/pcrepp.cc
|
||||||
|
../src/lnav_log.cc)
|
||||||
link_directories(/opt/local/lib)
|
link_directories(/opt/local/lib)
|
||||||
target_link_libraries(test_pcrepp /opt/local/lib/libpcre.a)
|
target_link_libraries(test_pcrepp /opt/local/lib/libpcre.a)
|
||||||
|
target_link_libraries(test_reltime /opt/local/lib/libpcre.a)
|
||||||
|
@ -44,6 +44,7 @@ check_PROGRAMS = \
|
|||||||
test_line_buffer2 \
|
test_line_buffer2 \
|
||||||
test_log_accel \
|
test_log_accel \
|
||||||
test_pcrepp \
|
test_pcrepp \
|
||||||
|
test_reltime \
|
||||||
test_top_status \
|
test_top_status \
|
||||||
test_yajlpp
|
test_yajlpp
|
||||||
|
|
||||||
@ -99,6 +100,9 @@ test_concise_LDADD = ../src/libdiag.a
|
|||||||
test_json_ptr_SOURCES = test_json_ptr.cc
|
test_json_ptr_SOURCES = test_json_ptr.cc
|
||||||
test_json_ptr_LDADD = ../src/libdiag.a
|
test_json_ptr_LDADD = ../src/libdiag.a
|
||||||
|
|
||||||
|
test_reltime_SOURCES = test_reltime.cc
|
||||||
|
test_reltime_LDADD = ../src/libdiag.a
|
||||||
|
|
||||||
drive_line_buffer_SOURCES = drive_line_buffer.cc
|
drive_line_buffer_SOURCES = drive_line_buffer.cc
|
||||||
drive_line_buffer_LDADD = ../src/libdiag.a $(CURSES_LIB) -lz
|
drive_line_buffer_LDADD = ../src/libdiag.a $(CURSES_LIB) -lz
|
||||||
|
|
||||||
@ -244,6 +248,7 @@ dist_noinst_DATA = \
|
|||||||
logfile_syslog.1 \
|
logfile_syslog.1 \
|
||||||
logfile_syslog.2 \
|
logfile_syslog.2 \
|
||||||
logfile_syslog_with_access_log.0 \
|
logfile_syslog_with_access_log.0 \
|
||||||
|
logfile_syslog_with_mixed_times.0 \
|
||||||
logfile_tcf.0 \
|
logfile_tcf.0 \
|
||||||
logfile_tcf.1 \
|
logfile_tcf.1 \
|
||||||
logfile_tcsh_history.0 \
|
logfile_tcsh_history.0 \
|
||||||
@ -293,6 +298,7 @@ TESTS = \
|
|||||||
test_log_accel \
|
test_log_accel \
|
||||||
test_logfile.sh \
|
test_logfile.sh \
|
||||||
test_pcrepp \
|
test_pcrepp \
|
||||||
|
test_reltime \
|
||||||
test_sessions.sh \
|
test_sessions.sh \
|
||||||
test_sql.sh \
|
test_sql.sh \
|
||||||
test_sql_coll_func.sh \
|
test_sql_coll_func.sh \
|
||||||
|
@ -67,7 +67,9 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
std::vector<std::string> paths, errors;
|
std::vector<std::string> paths, errors;
|
||||||
|
|
||||||
paths.push_back(getenv("test_dir"));
|
if (getenv("test_dir") != NULL) {
|
||||||
|
paths.push_back(getenv("test_dir"));
|
||||||
|
}
|
||||||
load_formats(paths, errors);
|
load_formats(paths, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
test/logfile_syslog_with_mixed_times.0
Normal file
13
test/logfile_syslog_with_mixed_times.0
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Sep 13 00:58:45 Tim-Stacks-iMac kernel[0]: AirParrot device perform power state change 0 -> 1
|
||||||
|
Sep 13 00:59:30 Tim-Stacks-iMac.local airportd[59]: _configureScanOffloadParameters: Unable to configure scan offloading on en1 (Device power is off)
|
||||||
|
Sep 13 01:23:54 Tim-Stacks-iMac kernel[0]: RTC: PowerByCalendarDate setting ignored
|
||||||
|
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: vm_compressor_record_warmup (9478314 - 9492476)
|
||||||
|
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||||
|
Sep 13 01:25:39 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||||
|
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff8428276000, 0xffffff8428336000
|
||||||
|
Sep 13 03:12:58 Tim-Stacks-iMac kernel[0]: *** kernel exceeded 500 log message per second limit - remaining messages this second discarded ***
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: IOThunderboltSwitch<0xffffff803f4b3000>(0x0)::listenerCallback - Thunderbolt HPD packet for route = 0x0 port = 11 unplug = 0
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: vm_compressor_flush - starting
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||||
|
Sep 13 03:13:16 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff838f1fc000, 0xffffff838f2bc000
|
@ -30,12 +30,45 @@ check_output "goto -1 is not working" <<EOF
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
run_test ${lnav_test} -n \
|
||||||
|
-c ":goto 0" \
|
||||||
|
-c ":goto 2 hours later" \
|
||||||
|
${test_dir}/logfile_syslog_with_mixed_times.0
|
||||||
|
|
||||||
|
check_output "goto 3:45 is not working?" <<EOF
|
||||||
|
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: vm_compressor_record_warmup (9478314 - 9492476)
|
||||||
|
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||||
|
Sep 13 01:25:39 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||||
|
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff8428276000, 0xffffff8428336000
|
||||||
|
Sep 13 03:12:58 Tim-Stacks-iMac kernel[0]: *** kernel exceeded 500 log message per second limit - remaining messages this second discarded ***
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: IOThunderboltSwitch<0xffffff803f4b3000>(0x0)::listenerCallback - Thunderbolt HPD packet for route = 0x0 port = 11 unplug = 0
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: vm_compressor_flush - starting
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||||
|
Sep 13 03:13:16 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff838f1fc000, 0xffffff838f2bc000
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
run_test ${lnav_test} -n \
|
||||||
|
-c ":goto 0" \
|
||||||
|
-c ":goto 3:45" \
|
||||||
|
${test_dir}/logfile_syslog_with_mixed_times.0
|
||||||
|
|
||||||
|
check_output "goto 3:45 is not working?" <<EOF
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: IOThunderboltSwitch<0xffffff803f4b3000>(0x0)::listenerCallback - Thunderbolt HPD packet for route = 0x0 port = 11 unplug = 0
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: vm_compressor_flush - starting
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||||
|
Sep 13 03:13:16 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||||
|
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff838f1fc000, 0xffffff838f2bc000
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
run_test ${lnav_test} -n \
|
run_test ${lnav_test} -n \
|
||||||
-c ":goto invalid" \
|
-c ":goto invalid" \
|
||||||
${test_dir}/logfile_access_log.0
|
${test_dir}/logfile_access_log.0
|
||||||
|
|
||||||
check_error_output "goto invalid is working" <<EOF
|
check_error_output "goto invalid is working" <<EOF
|
||||||
error: expecting line number/percentage or timestamp
|
error: expecting line number/percentage, timestamp, or relative time
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
check_output "goto invalid is not working" <<EOF
|
check_output "goto invalid is not working" <<EOF
|
||||||
|
@ -121,6 +121,27 @@ Apr 10 02:58:07 2015 -- 123
|
|||||||
Apr 10 02:58:07 2015 -- 456
|
Apr 10 02:58:07 2015 -- 456
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
touch -t 201509130923 ${srcdir}/logfile_syslog_with_mixed_times.0
|
||||||
|
run_test ./drive_logfile -t -f syslog_log ${srcdir}/logfile_syslog_with_mixed_times.0
|
||||||
|
|
||||||
|
check_output "syslog_log with mixed times interpreted incorrectly?" <<EOF
|
||||||
|
Sep 13 00:58:45 2015 -- 000
|
||||||
|
Sep 13 00:59:30 2015 -- 000
|
||||||
|
Sep 13 01:23:54 2015 -- 000
|
||||||
|
Sep 13 03:12:04 2015 -- 000
|
||||||
|
Sep 13 03:12:04 2015 -- 000
|
||||||
|
Sep 13 03:12:04 2015 -- 000
|
||||||
|
Sep 13 03:12:04 2015 -- 000
|
||||||
|
Sep 13 03:12:58 2015 -- 000
|
||||||
|
Sep 13 03:46:03 2015 -- 000
|
||||||
|
Sep 13 03:46:03 2015 -- 000
|
||||||
|
Sep 13 03:46:03 2015 -- 000
|
||||||
|
Sep 13 03:46:03 2015 -- 000
|
||||||
|
Sep 13 03:46:03 2015 -- 000
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
run_test ./drive_logfile -v -f syslog_log ${srcdir}/logfile_syslog.0
|
run_test ./drive_logfile -v -f syslog_log ${srcdir}/logfile_syslog.0
|
||||||
|
151
test/test_reltime.cc
Normal file
151
test/test_reltime.cc
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* 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 "config.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "relative_time.hh"
|
||||||
|
|
||||||
|
struct {
|
||||||
|
const char *reltime;
|
||||||
|
const char *expected;
|
||||||
|
} TEST_DATA[] = {
|
||||||
|
{ "a minute ago", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "1m ago", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "a min ago", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "a m ago", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "+1 minute ago", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "-1 minute ago", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "-1 minute", "0y0m0d0h-1m0s0u" },
|
||||||
|
{ "1:40", "0y0m0d1H40M0S0U" },
|
||||||
|
{ "01:40", "0y0m0d1H40M0S0U" },
|
||||||
|
{ "1h40m", "0y0m0d1h40m0s0u" },
|
||||||
|
{ "1pm", "0y0m0d13H0M0S0U" },
|
||||||
|
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
const char *reltime;
|
||||||
|
const char *expected_error;
|
||||||
|
} BAD_TEST_DATA[] = {
|
||||||
|
{ "ago", "" },
|
||||||
|
{ "minute", "" },
|
||||||
|
{ "1 2", "" },
|
||||||
|
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
time_t base_time = 1317913200;
|
||||||
|
struct exttm base_tm;
|
||||||
|
base_tm.et_tm = *gmtime(&base_time);
|
||||||
|
struct relative_time::parse_error pe;
|
||||||
|
struct exttm tm;
|
||||||
|
time_t new_time;
|
||||||
|
|
||||||
|
relative_time rt;
|
||||||
|
|
||||||
|
for (int lpc = 0; TEST_DATA[lpc].reltime; lpc++) {
|
||||||
|
rt.clear();
|
||||||
|
rt.parse(TEST_DATA[lpc].reltime, pe);
|
||||||
|
printf("%s %s %s\n", TEST_DATA[lpc].reltime, TEST_DATA[lpc].expected, rt.to_string().c_str());
|
||||||
|
assert(std::string(TEST_DATA[lpc].expected) == rt.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int lpc = 0; BAD_TEST_DATA[lpc].reltime; lpc++) {
|
||||||
|
bool rc;
|
||||||
|
rt.clear();
|
||||||
|
rc = rt.parse(BAD_TEST_DATA[lpc].reltime, pe);
|
||||||
|
printf("%s -- %s\n", BAD_TEST_DATA[lpc].reltime, pe.pe_msg.c_str());
|
||||||
|
assert(!rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.parse("a minute ago", pe);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_MINUTES] == -1);
|
||||||
|
|
||||||
|
rt.parse("5 milliseconds", pe);
|
||||||
|
|
||||||
|
assert(rt.rt_field[relative_time::RTF_MICROSECONDS] == 5 * 1000);
|
||||||
|
|
||||||
|
rt.clear();
|
||||||
|
rt.parse("5000 ms ago", pe);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_SECONDS] == -5);
|
||||||
|
|
||||||
|
rt.clear();
|
||||||
|
rt.parse("5 hours 20 minutes ago", pe);
|
||||||
|
|
||||||
|
assert(rt.rt_field[relative_time::RTF_HOURS] == -5);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_MINUTES] == -20);
|
||||||
|
|
||||||
|
rt.clear();
|
||||||
|
rt.parse("5 hours and 20 minutes ago", pe);
|
||||||
|
|
||||||
|
assert(rt.rt_field[relative_time::RTF_HOURS] == -5);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_MINUTES] == -20);
|
||||||
|
|
||||||
|
rt.clear();
|
||||||
|
rt.parse("1:23", pe);
|
||||||
|
|
||||||
|
assert(rt.rt_field[relative_time::RTF_HOURS] == 1);
|
||||||
|
assert(rt.rt_is_absolute[relative_time::RTF_HOURS]);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_MINUTES] == 23);
|
||||||
|
assert(rt.rt_is_absolute[relative_time::RTF_MINUTES]);
|
||||||
|
|
||||||
|
rt.clear();
|
||||||
|
rt.parse("1:23:45", pe);
|
||||||
|
|
||||||
|
assert(rt.rt_field[relative_time::RTF_HOURS] == 1);
|
||||||
|
assert(rt.rt_is_absolute[relative_time::RTF_HOURS]);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_MINUTES] == 23);
|
||||||
|
assert(rt.rt_is_absolute[relative_time::RTF_MINUTES]);
|
||||||
|
assert(rt.rt_field[relative_time::RTF_SECONDS] == 45);
|
||||||
|
assert(rt.rt_is_absolute[relative_time::RTF_SECONDS]);
|
||||||
|
|
||||||
|
tm = base_tm;
|
||||||
|
rt.add(tm);
|
||||||
|
|
||||||
|
new_time = timegm(&tm.et_tm);
|
||||||
|
tm.et_tm = *gmtime(&new_time);
|
||||||
|
assert(tm.et_tm.tm_hour == 1);
|
||||||
|
assert(tm.et_tm.tm_min == 23);
|
||||||
|
|
||||||
|
rt.clear();
|
||||||
|
rt.parse("5 minutes ago", pe);
|
||||||
|
|
||||||
|
tm = base_tm;
|
||||||
|
rt.add(tm);
|
||||||
|
|
||||||
|
new_time = timegm(&tm.et_tm);
|
||||||
|
|
||||||
|
assert(new_time == (base_time - (5 * 60)));
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user