mirror of
https://github.com/tstack/lnav.git
synced 2024-09-11 13:05:51 +03:00
[cmds] fix some timezone issues with :convert-time-to and other spots
Related to #1236
This commit is contained in:
parent
c68b7ec7a3
commit
9cf2832ff3
8
NEWS.md
8
NEWS.md
@ -174,9 +174,11 @@ Breaking changes:
|
||||
the data is now stored (and cleaned up) as well as being
|
||||
spread across multiple files, this option doesn't make
|
||||
sense anymore.
|
||||
* Removed the `-t` command-line flag. Text data fed in
|
||||
on stdin and captured from a `:sh` execution is
|
||||
automatically timestamped.
|
||||
* The `-t` command-line flag behaves a little differently
|
||||
behind the scenes now. Timestamps will always be
|
||||
recorded for each line piped into lnav. This flag means
|
||||
that the data should be treated as a log file instead of
|
||||
plain text.
|
||||
* Data piped into **lnav** is now stored in the work
|
||||
directory instead of the `stdin-captures` dot-lnav
|
||||
directory.
|
||||
|
@ -116,6 +116,7 @@ test_base_SOURCES = \
|
||||
test_base_LDADD = \
|
||||
libbase.a \
|
||||
../fmtlib/libcppfmt.a \
|
||||
../third-party/date/src/libdatepp.a \
|
||||
../third-party/scnlib/src/libscnlib.a \
|
||||
../pcrepp/libpcrepp.a
|
||||
|
||||
|
@ -30,10 +30,15 @@
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
|
||||
#include "time_util.hh"
|
||||
|
||||
#include <date/ptz.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "lnav_log.hh"
|
||||
#include "optional.hpp"
|
||||
|
||||
namespace lnav {
|
||||
|
||||
@ -75,6 +80,64 @@ strftime_rfc3339(
|
||||
return index;
|
||||
}
|
||||
|
||||
static nonstd::optional<Posix::time_zone>
|
||||
get_posix_zone(const char* name)
|
||||
{
|
||||
if (name == nullptr) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
try {
|
||||
return date::zoned_traits<Posix::time_zone>::locate_zone(name);
|
||||
} catch (const std::runtime_error& e) {
|
||||
log_error("invalid TZ value: %s -- %s", name, e.what());
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static const date::time_zone*
|
||||
get_date_zone(const char* name)
|
||||
{
|
||||
if (name == nullptr) {
|
||||
return date::current_zone();
|
||||
}
|
||||
|
||||
try {
|
||||
return date::locate_zone(name);
|
||||
} catch (const std::runtime_error& e) {
|
||||
log_error("invalid TZ value: %s -- %s", name, e.what());
|
||||
return date::current_zone();
|
||||
}
|
||||
}
|
||||
|
||||
date::sys_seconds
|
||||
to_sys_time(date::local_seconds secs)
|
||||
{
|
||||
static const auto* TZ = getenv("TZ");
|
||||
static const auto TZ_POSIX_ZONE = get_posix_zone(TZ);
|
||||
static const auto* TZ_DATE_ZONE = get_date_zone(TZ);
|
||||
|
||||
if (TZ_POSIX_ZONE) {
|
||||
return TZ_POSIX_ZONE.value().to_sys(secs);
|
||||
}
|
||||
|
||||
return TZ_DATE_ZONE->to_sys(secs);
|
||||
}
|
||||
|
||||
date::local_seconds
|
||||
to_local_time(date::sys_seconds secs)
|
||||
{
|
||||
static const auto* TZ = getenv("TZ");
|
||||
static const auto TZ_POSIX_ZONE = get_posix_zone(TZ);
|
||||
static const auto* TZ_DATE_ZONE = get_date_zone(TZ);
|
||||
|
||||
if (TZ_POSIX_ZONE) {
|
||||
return TZ_POSIX_ZONE.value().to_local(secs);
|
||||
}
|
||||
|
||||
return TZ_DATE_ZONE->to_local(secs);
|
||||
}
|
||||
|
||||
} // namespace lnav
|
||||
|
||||
static time_t BAD_DATE = -1;
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <date/date.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
@ -50,6 +51,10 @@ ssize_t strftime_rfc3339(char* buffer,
|
||||
int millis,
|
||||
char sep = ' ');
|
||||
|
||||
date::sys_seconds to_sys_time(date::local_seconds secs);
|
||||
|
||||
date::local_seconds to_local_time(date::sys_seconds secs);
|
||||
|
||||
} // namespace lnav
|
||||
|
||||
struct tm* secs2tm(lnav::time64_t tim, struct tm* res);
|
||||
|
@ -1239,6 +1239,11 @@ line_buffer::load_next_line(file_range prev_line)
|
||||
retval.li_timestamp.tv_usec,
|
||||
level);
|
||||
if (scan_res) {
|
||||
retval.li_timestamp.tv_sec
|
||||
= lnav::to_local_time(date::sys_seconds{std::chrono::seconds{
|
||||
retval.li_timestamp.tv_sec}})
|
||||
.time_since_epoch()
|
||||
.count();
|
||||
retval.li_level = abbrev2level(&level, 1);
|
||||
}
|
||||
}
|
||||
|
20
src/lnav.cc
20
src/lnav.cc
@ -647,6 +647,10 @@ make it easier to navigate through files quickly.
|
||||
.append(" ")
|
||||
.append("Execute a shell command-line.\n")
|
||||
.append(" ")
|
||||
.append("-t"_symbol)
|
||||
.append(" ")
|
||||
.append("Treat data piped into standard in as a log file.\n")
|
||||
.append(" ")
|
||||
.append("-n"_symbol)
|
||||
.append(" ")
|
||||
.append("Run without the curses UI. (headless mode)\n")
|
||||
@ -2469,6 +2473,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
"-R", lnav_data.ld_active_files.fc_rotated, "rotated");
|
||||
auto* recurse_flag = app.add_flag(
|
||||
"-r", lnav_data.ld_active_files.fc_recursive, "recurse");
|
||||
auto* as_log_flag
|
||||
= app.add_flag("-t", lnav_data.ld_treat_stdin_as_log, "as-log");
|
||||
app.add_flag("-W", mode_flags.mf_print_warnings);
|
||||
auto* headless_flag = app.add_flag(
|
||||
"-n",
|
||||
@ -2549,6 +2555,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
|
||||
install_flag->needs(file_opt);
|
||||
install_flag->excludes(no_default_flag,
|
||||
as_log_flag,
|
||||
rotated_flag,
|
||||
recurse_flag,
|
||||
headless_flag,
|
||||
@ -2947,6 +2954,9 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
lnav_data.ld_vtab_manager->register_vtab(
|
||||
std::make_shared<log_format_vtab_impl>(
|
||||
*log_format::find_root_format("generic_log")));
|
||||
lnav_data.ld_vtab_manager->register_vtab(
|
||||
std::make_shared<log_format_vtab_impl>(
|
||||
*log_format::find_root_format("lnav_piper_log")));
|
||||
|
||||
for (auto& iter : log_format::get_root_formats()) {
|
||||
auto lvi = iter->get_vtab_impl();
|
||||
@ -3253,10 +3263,12 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
auto stdin_piper = stdin_piper_res.unwrap();
|
||||
stdin_url = stdin_piper.get_url();
|
||||
stdin_dir = stdin_piper.get_out_dir();
|
||||
lnav_data.ld_active_files
|
||||
.fc_file_names[stdin_piper.get_name()]
|
||||
.with_piper(stdin_piper)
|
||||
.with_include_in_session(false);
|
||||
auto& loo = lnav_data.ld_active_files
|
||||
.fc_file_names[stdin_piper.get_name()];
|
||||
loo.with_piper(stdin_piper).with_include_in_session(false);
|
||||
if (lnav_data.ld_treat_stdin_as_log) {
|
||||
loo.with_text_format(text_format_t::TF_LOG);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (S_ISREG(stdin_st.st_mode)) {
|
||||
|
@ -253,6 +253,7 @@ struct lnav_data_t {
|
||||
|
||||
bool ld_initial_build{false};
|
||||
bool ld_show_help_view{false};
|
||||
bool ld_treat_stdin_as_log{false};
|
||||
lnav_exec_phase ld_exec_phase{lnav_exec_phase::INIT};
|
||||
|
||||
lnav::func::scoped_cb ld_status_refresher;
|
||||
|
@ -503,9 +503,8 @@ com_clear_file_timezone_prompt(exec_context& ec, const std::string& cmdline)
|
||||
pattern_arg = match_res->first;
|
||||
}
|
||||
|
||||
retval = fmt::format(FMT_STRING("{} {}"),
|
||||
trim(cmdline),
|
||||
pattern_arg);
|
||||
retval = fmt::format(
|
||||
FMT_STRING("{} {}"), trim(cmdline), pattern_arg);
|
||||
} catch (const std::runtime_error& e) {
|
||||
log_error("cannot get timezones: %s", e.what());
|
||||
}
|
||||
@ -595,15 +594,26 @@ com_convert_time_to(exec_context& ec,
|
||||
|
||||
const auto* ll = lss->find_line(lss->at(tc->get_selection()));
|
||||
try {
|
||||
auto* tz = date::locate_zone(args[1]);
|
||||
auto utime = std::chrono::system_clock::from_time_t(ll->get_time());
|
||||
auto ztime = date::make_zoned(tz, utime);
|
||||
auto* dst_tz = date::locate_zone(args[1]);
|
||||
auto utime = date::local_time<std::chrono::seconds>{
|
||||
std::chrono::seconds{ll->get_time()}};
|
||||
auto cz_time = lnav::to_sys_time(utime);
|
||||
auto dz_time = date::make_zoned(dst_tz, cz_time);
|
||||
auto etime = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
ztime.get_local_time().time_since_epoch());
|
||||
dz_time.get_local_time().time_since_epoch());
|
||||
char ftime[128];
|
||||
sql_strftime(
|
||||
ftime, sizeof(ftime), etime.count(), ll->get_millis(), 'T');
|
||||
retval = ftime;
|
||||
|
||||
off_t off = 0;
|
||||
exttm tm;
|
||||
tm.et_flags |= ETF_ZONE_SET;
|
||||
tm.et_gmtoff = dz_time.get_info().offset.count();
|
||||
ftime_Z(ftime, off, sizeof(ftime), tm);
|
||||
ftime[off] = '\0';
|
||||
retval.append(" ");
|
||||
retval.append(ftime);
|
||||
} catch (const std::runtime_error& e) {
|
||||
return ec.make_error(FMT_STRING("Unable to get timezone: {} -- {}"),
|
||||
args[1],
|
||||
|
@ -45,6 +45,76 @@
|
||||
#include "sql_util.hh"
|
||||
#include "yajlpp/yajlpp.hh"
|
||||
|
||||
class piper_log_format : public log_format {
|
||||
public:
|
||||
const intern_string_t get_name() const override
|
||||
{
|
||||
static const intern_string_t RETVAL
|
||||
= intern_string::lookup("lnav_piper_log");
|
||||
|
||||
return RETVAL;
|
||||
}
|
||||
|
||||
scan_result_t scan(logfile& lf,
|
||||
std::vector<logline>& dst,
|
||||
const line_info& li,
|
||||
shared_buffer_ref& sbr,
|
||||
scan_batch_context& sbc) override
|
||||
{
|
||||
if (lf.has_line_metadata()
|
||||
&& lf.get_text_format() == text_format_t::TF_LOG)
|
||||
{
|
||||
dst.emplace_back(
|
||||
li.li_file_range.fr_offset, li.li_timestamp, li.li_level);
|
||||
return scan_match{100};
|
||||
}
|
||||
|
||||
return scan_no_match{""};
|
||||
}
|
||||
|
||||
void annotate(uint64_t line_number,
|
||||
string_attrs_t& sa,
|
||||
logline_value_vector& values,
|
||||
bool annotate_module) const override
|
||||
{
|
||||
auto lr = line_range{0, 0};
|
||||
sa.emplace_back(lr, logline::L_TIMESTAMP.value());
|
||||
}
|
||||
|
||||
void get_subline(const logline& ll,
|
||||
shared_buffer_ref& sbr,
|
||||
bool full_message) override
|
||||
{
|
||||
this->plf_cached_line.resize(23);
|
||||
sql_strftime(this->plf_cached_line.data(),
|
||||
this->plf_cached_line.size(),
|
||||
ll.get_timeval(),
|
||||
'T');
|
||||
this->plf_cached_line.push_back(' ');
|
||||
const auto prefix_len = this->plf_cached_line.size();
|
||||
this->plf_cached_line.resize(this->plf_cached_line.size()
|
||||
+ sbr.length());
|
||||
memcpy(
|
||||
&this->plf_cached_line[prefix_len], sbr.get_data(), sbr.length());
|
||||
|
||||
sbr.share(this->plf_share_manager,
|
||||
this->plf_cached_line.data(),
|
||||
this->plf_cached_line.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<log_format> specialized(int fmt_lock) override
|
||||
{
|
||||
auto retval = std::make_shared<piper_log_format>(*this);
|
||||
|
||||
retval->lf_specialized = true;
|
||||
return retval;
|
||||
}
|
||||
|
||||
private:
|
||||
shared_buffer plf_share_manager;
|
||||
std::vector<char> plf_cached_line;
|
||||
};
|
||||
|
||||
class generic_log_format : public log_format {
|
||||
static const pcre_format* get_pcre_log_formats()
|
||||
{
|
||||
@ -1946,4 +2016,5 @@ static auto format_binder = injector::bind_multiple<log_format>()
|
||||
.add<logfmt_format>()
|
||||
.add<bro_log_format>()
|
||||
.add<w3c_log_format>()
|
||||
.add<generic_log_format>();
|
||||
.add<generic_log_format>()
|
||||
.add<piper_log_format>();
|
||||
|
@ -133,6 +133,8 @@ logfile::open(std::string filename, const logfile_open_options& loo, auto_fd fd)
|
||||
lf->lf_index.reserve(INDEX_RESERVE_INCREMENT);
|
||||
|
||||
lf->lf_indexing = lf->lf_options.loo_is_visible;
|
||||
lf->lf_text_format
|
||||
= lf->lf_options.loo_text_format.value_or(text_format_t::TF_UNKNOWN);
|
||||
|
||||
const auto& hdr = lf->lf_line_buffer.get_header_data();
|
||||
if (hdr.valid()) {
|
||||
|
@ -171,6 +171,8 @@ public:
|
||||
|
||||
text_format_t get_text_format() const { return this->lf_text_format; }
|
||||
|
||||
void set_text_format(text_format_t tf) { this->lf_text_format = tf; }
|
||||
|
||||
/**
|
||||
* @return The last modified time of the file when the file was last
|
||||
* indexed.
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "base/auto_fd.hh"
|
||||
#include "file_format.hh"
|
||||
#include "piper.looper.hh"
|
||||
#include "text_format.hh"
|
||||
#include "vis_line.hh"
|
||||
|
||||
using ui_clock = std::chrono::steady_clock;
|
||||
@ -70,6 +71,7 @@ struct logfile_open_options_base {
|
||||
bool loo_tail{true};
|
||||
file_format_t loo_file_format{file_format_t::UNKNOWN};
|
||||
nonstd::optional<std::string> loo_format_name;
|
||||
nonstd::optional<text_format_t> loo_text_format;
|
||||
nonstd::optional<lnav::piper::running_handle> loo_piper;
|
||||
file_location_t loo_init_location{mapbox::util::no_init{}};
|
||||
};
|
||||
@ -167,6 +169,13 @@ struct logfile_open_options : public logfile_open_options_base {
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
logfile_open_options& with_text_format(text_format_t tf)
|
||||
{
|
||||
this->loo_text_format = tf;
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -379,9 +379,7 @@ logfile_sub_source::text_value_for_line(textview_curses& tc,
|
||||
if (format->lf_timestamp_flags & ETF_ZONE_SET
|
||||
&& format->lf_date_time.dts_zoned_to_local)
|
||||
{
|
||||
if (format->lf_timestamp_flags & ETF_Z_IS_UTC) {
|
||||
adjusted_tm.et_flags &= ~ETF_Z_IS_UTC;
|
||||
}
|
||||
adjusted_tm.et_flags &= ~ETF_Z_IS_UTC;
|
||||
}
|
||||
adjusted_tm.et_gmtoff
|
||||
= format->lf_date_time.dts_local_offset_cache;
|
||||
|
@ -76,6 +76,7 @@ tailer_LDADD = libtailercommon.a
|
||||
drive_tailer_CPPFLAGS = \
|
||||
-I$(srcdir)/.. \
|
||||
-I$(srcdir)/../fmtlib \
|
||||
-I$(top_srcdir)/src/third-party/date/include \
|
||||
-I$(top_srcdir)/src/third-party/scnlib/include
|
||||
|
||||
drive_tailer_SOURCES = \
|
||||
|
@ -27,8 +27,12 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "textfile_sub_source.hh"
|
||||
|
||||
#include <date/date.h>
|
||||
|
||||
#include "base/ansi_scrubber.hh"
|
||||
#include "base/attr_line.builder.hh"
|
||||
#include "base/fs_util.hh"
|
||||
|
105
src/third-party/date/include/date/ptz.h
vendored
105
src/third-party/date/include/date/ptz.h
vendored
@ -36,8 +36,24 @@
|
||||
// Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"};
|
||||
// zoned_time<system_clock::duration, Posix::time_zone> zt{tz, system_clock::now()};
|
||||
//
|
||||
// If the rule set is missing (everything starting with ','), then the rule is that the
|
||||
// alternate offset is never enabled.
|
||||
// In C++17 CTAD simplifies this to:
|
||||
//
|
||||
// Posix::time_zone tz{"EST5EDT,M3.2.0,M11.1.0"};
|
||||
// zoned_time zt{tz, system_clock::now()};
|
||||
//
|
||||
// Extension to the Posix rules to allow a constant daylight saving offset:
|
||||
//
|
||||
// If the rule set is missing (everything starting with ','), then
|
||||
// there must be exactly one abbreviation (std or daylight) with
|
||||
// length 3 or greater, and that will be used as the constant offset. If
|
||||
// there are two, the std abbreviation is silently set to "", and the
|
||||
// result is constant daylight saving. If there are zero abbreviations
|
||||
// with no rule set, an exception is thrown.
|
||||
//
|
||||
// Example:
|
||||
// "EST5" yields a constant offset of -5h with 0h save and "EST abbreviation.
|
||||
// "5EDT" yields a constant offset of -4h with 1h save and "EDT" abbreviation.
|
||||
// "EST5EDT" and "5EDT4" are both equal to "5EDT".
|
||||
//
|
||||
// Note, Posix-style time zones are not recommended for all of the reasons described here:
|
||||
// https://stackoverflow.com/tags/timezone/info
|
||||
@ -46,6 +62,7 @@
|
||||
// have to have Posix time zones, you're welcome to use this one.
|
||||
|
||||
#include "date/tz.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
@ -322,6 +339,7 @@ time_zone::get_next_end(date::year y) const
|
||||
return date::sys_seconds{(end_rule_(++y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_info
|
||||
time_zone::contant_offset() const
|
||||
{
|
||||
@ -331,11 +349,22 @@ time_zone::contant_offset() const
|
||||
using date::January;
|
||||
using date::December;
|
||||
using date::last;
|
||||
using std::chrono::minutes;
|
||||
sys_info r;
|
||||
r.begin = sys_days{year::min()/January/1};
|
||||
r.end = sys_days{year::max()/December/last};
|
||||
r.abbrev = std_abbrev_;
|
||||
r.offset = offset_;
|
||||
if (std_abbrev_.size() > 0)
|
||||
{
|
||||
r.abbrev = std_abbrev_;
|
||||
r.offset = offset_;
|
||||
r.save = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
r.abbrev = dst_abbrev_;
|
||||
r.offset = offset_ + save_;
|
||||
r.save = date::ceil<minutes>(save_);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -346,11 +375,14 @@ time_zone::time_zone(const detail::string_t& s)
|
||||
using detail::read_signed_time;
|
||||
using detail::throw_invalid;
|
||||
auto i = read_name(s, 0, std_abbrev_);
|
||||
auto std_name_i = i;
|
||||
auto abbrev_name_i = i;
|
||||
i = read_signed_time(s, i, offset_);
|
||||
offset_ = -offset_;
|
||||
if (i != s.size())
|
||||
{
|
||||
i = read_name(s, i, dst_abbrev_);
|
||||
abbrev_name_i = i;
|
||||
if (i != s.size())
|
||||
{
|
||||
if (s[i] != ',')
|
||||
@ -373,6 +405,32 @@ time_zone::time_zone(const detail::string_t& s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
if (std_abbrev_.size() < 3)
|
||||
throw_invalid(s, std_name_i, "Zone with rules must have a std"
|
||||
" abbreviation of length 3 or greater");
|
||||
if (dst_abbrev_.size() < 3)
|
||||
throw_invalid(s, abbrev_name_i, "Zone with rules must have a daylight"
|
||||
" abbreviation of length 3 or greater");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dst_abbrev_.size() >= 3)
|
||||
{
|
||||
std_abbrev_.clear();
|
||||
}
|
||||
else if (std_abbrev_.size() < 3)
|
||||
{
|
||||
throw_invalid(s, std_name_i, "Zone must have at least one abbreviation"
|
||||
" of length 3 or greater");
|
||||
}
|
||||
else
|
||||
{
|
||||
dst_abbrev_.clear();
|
||||
save_ = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
@ -395,6 +453,10 @@ time_zone::get_info(date::sys_time<Duration> st) const
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
auto y = year_month_day{floor<days>(st)}.year();
|
||||
if (st >= get_next_start(y))
|
||||
++y;
|
||||
else if (st < get_prev_end(y))
|
||||
--y;
|
||||
auto start = get_start(y);
|
||||
auto end = get_end(y);
|
||||
if (start <= end) // (northern hemisphere)
|
||||
@ -448,6 +510,7 @@ time_zone::get_info(date::sys_time<Duration> st) const
|
||||
}
|
||||
else
|
||||
r = contant_offset();
|
||||
assert(r.begin <= st && st < r.end);
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -589,7 +652,18 @@ time_zone::name() const
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
auto nm = std_abbrev_;
|
||||
auto print_abbrev = [](std::string const& nm)
|
||||
{
|
||||
if (std::any_of(nm.begin(), nm.end(),
|
||||
[](char c)
|
||||
{
|
||||
return !std::isalpha(c);
|
||||
}))
|
||||
{
|
||||
return '<' + nm + '>';
|
||||
}
|
||||
return nm;
|
||||
};
|
||||
auto print_offset = [](seconds off)
|
||||
{
|
||||
std::string nm;
|
||||
@ -613,10 +687,11 @@ time_zone::name() const
|
||||
}
|
||||
return nm;
|
||||
};
|
||||
auto nm = print_abbrev(std_abbrev_);
|
||||
nm += print_offset(offset_);
|
||||
if (!dst_abbrev_.empty())
|
||||
{
|
||||
nm += dst_abbrev_;
|
||||
nm += print_abbrev(dst_abbrev_);
|
||||
if (save_ != hours{1})
|
||||
nm += print_offset(offset_+save_);
|
||||
if (start_rule_.ok())
|
||||
@ -678,6 +753,8 @@ read_date(const string_t& s, unsigned i, rule& r)
|
||||
++i;
|
||||
unsigned n;
|
||||
i = read_unsigned(s, i, 3, n, "Expected to find the Julian day [1, 365]");
|
||||
if (!(1 <= n && n <= 365))
|
||||
throw_invalid(s, i-1, "Expected Julian day to be in the range [1, 365]");
|
||||
r.mode_ = rule::J;
|
||||
r.n_ = n;
|
||||
}
|
||||
@ -686,16 +763,22 @@ read_date(const string_t& s, unsigned i, rule& r)
|
||||
++i;
|
||||
unsigned m;
|
||||
i = read_unsigned(s, i, 2, m, "Expected to find month [1, 12]");
|
||||
if (!(1 <= m && m <= 12))
|
||||
throw_invalid(s, i-1, "Expected month to be in the range [1, 12]");
|
||||
if (i == s.size() || s[i] != '.')
|
||||
throw_invalid(s, i, "Expected '.' after month");
|
||||
++i;
|
||||
unsigned n;
|
||||
i = read_unsigned(s, i, 1, n, "Expected to find week number [1, 5]");
|
||||
if (!(1 <= n && n <= 5))
|
||||
throw_invalid(s, i-1, "Expected week number to be in the range [1, 5]");
|
||||
if (i == s.size() || s[i] != '.')
|
||||
throw_invalid(s, i, "Expected '.' after weekday index");
|
||||
++i;
|
||||
unsigned wd;
|
||||
i = read_unsigned(s, i, 1, wd, "Expected to find day of week [0, 6]");
|
||||
if (wd > 6)
|
||||
throw_invalid(s, i-1, "Expected day of week to be in the range [0, 6]");
|
||||
r.mode_ = rule::M;
|
||||
r.m_ = month{m};
|
||||
r.wd_ = weekday{wd};
|
||||
@ -705,6 +788,8 @@ read_date(const string_t& s, unsigned i, rule& r)
|
||||
{
|
||||
unsigned n;
|
||||
i = read_unsigned(s, i, 3, n);
|
||||
if (n > 365)
|
||||
throw_invalid(s, i-1, "Expected Julian day to be in the range [0, 365]");
|
||||
r.mode_ = rule::N;
|
||||
r.n_ = n;
|
||||
}
|
||||
@ -749,8 +834,6 @@ read_name(const string_t& s, unsigned i, std::string& name)
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (name.size() < 3)
|
||||
throw_invalid(s, i, "Found name to be shorter than 3 characters");
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -786,16 +869,22 @@ read_unsigned_time(const string_t& s, unsigned i, std::chrono::seconds& t)
|
||||
throw_invalid(s, i, "Expected to read unsigned time, but found end of string");
|
||||
unsigned x;
|
||||
i = read_unsigned(s, i, 2, x, "Expected to find hours [0, 24]");
|
||||
if (x > 24)
|
||||
throw_invalid(s, i-1, "Expected hours to be in the range [0, 24]");
|
||||
t = hours{x};
|
||||
if (i != s.size() && s[i] == ':')
|
||||
{
|
||||
++i;
|
||||
i = read_unsigned(s, i, 2, x, "Expected to find minutes [0, 59]");
|
||||
if (x > 59)
|
||||
throw_invalid(s, i-1, "Expected minutes to be in the range [0, 59]");
|
||||
t += minutes{x};
|
||||
if (i != s.size() && s[i] == ':')
|
||||
{
|
||||
++i;
|
||||
i = read_unsigned(s, i, 2, x, "Expected to find seconds [0, 59]");
|
||||
if (x > 59)
|
||||
throw_invalid(s, i-1, "Expected seconds to be in the range [0, 59]");
|
||||
t += seconds{x};
|
||||
}
|
||||
}
|
||||
|
@ -220,6 +220,8 @@ EXPECTED_FILES = \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_d836c84398c831c976df46f46fe3bf5983c44c37.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_d8eeef53a58bdeddbc1028d7c525413e3ca1c8df.err \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_d8eeef53a58bdeddbc1028d7c525413e3ca1c8df.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_da5f7160b967e60dbd772573614e2da89c5e22b2.err \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_da5f7160b967e60dbd772573614e2da89c5e22b2.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_dbdd62995fdefc8318053af05a32416eccfa79fc.err \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_dbdd62995fdefc8318053af05a32416eccfa79fc.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_dd41fbbcd71699314af232156d4155fbdf849131.err \
|
||||
@ -236,6 +238,8 @@ EXPECTED_FILES = \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_eb22c3e94c536a1bfaeae0c40d271b5b4b08f4fc.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_ec2b28c6ea328e3ea56b13ab8ca3d9ee856a9dda.err \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_ec2b28c6ea328e3ea56b13ab8ca3d9ee856a9dda.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_ec3a64cad41b070a1d04e2bfc3dc14cb2d964091.err \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_ec3a64cad41b070a1d04e2bfc3dc14cb2d964091.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_ed5b73be0b991e0e8d6735e31df5b37c4286321b.err \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_ed5b73be0b991e0e8d6735e31df5b37c4286321b.out \
|
||||
$(srcdir)/%reldir%/test_cmds.sh_f788d5f5932905d09ecbd581040ec5ce76459da5.err \
|
||||
@ -1202,6 +1206,10 @@ EXPECTED_FILES = \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_1ce4056d72b871f8bb844c86aade2a9b1da58030.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_1ce4056d72b871f8bb844c86aade2a9b1da58030.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_25cef06efcbe106c2e1cc4a166b673e7b244c6d7.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_25cef06efcbe106c2e1cc4a166b673e7b244c6d7.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_265a8a5825e6c7dbc85cbe496dab6be7a349f3db.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_265a8a5825e6c7dbc85cbe496dab6be7a349f3db.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4226123565a53b4e3f80e602c1f294721e8e07bf.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4226123565a53b4e3f80e602c1f294721e8e07bf.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.err \
|
||||
@ -1240,4 +1248,6 @@ EXPECTED_FILES = \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_e556fa91b91579df20d38540a5db9cedbaf68a62.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_f586ef080a86dfe1f981b345bcf8d7a279b2b247.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_f586ef080a86dfe1f981b345bcf8d7a279b2b247.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_f7522b0a99550a3ff91aae6582eb861547c535e1.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_f7522b0a99550a3ff91aae6582eb861547c535e1.out \
|
||||
$()
|
||||
|
@ -0,0 +1 @@
|
||||
2009-07-20T15:59:26.000 -0700
|
@ -0,0 +1,6 @@
|
||||
[1m[31m✘ error[0m: Unable to get timezone: bad-zone -- bad-zone not found in timezone database
|
||||
[36m --> [0m[1mcommand-option[0m:2
|
||||
[36m | [0m[37m[40m:[0m[1m[36m[40mconvert-time-to[0m[37m[40m bad-zone [0m
|
||||
[36m =[0m [36mhelp[0m: [4m:[0m[1m[4mconvert-time-to[0m[4m [0m[4mzone[0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Convert the focused timestamp to the given timezone
|
@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"top_meta": {
|
||||
"file": "stdin",
|
||||
"breadcrumbs": [
|
||||
{
|
||||
"display_value": "stdin",
|
||||
"search_placeholder": "",
|
||||
"possibilities": [
|
||||
{
|
||||
"display_value": "stdin"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_value": "2013-06-06T12:13:20.123",
|
||||
"search_placeholder": "",
|
||||
"possibilities": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1 @@
|
||||
2013-06-06T12:13:20.123 Hello, World!
|
@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"top_meta": {
|
||||
"file": "stdin",
|
||||
"breadcrumbs": [
|
||||
{
|
||||
"display_value": "stdin",
|
||||
"search_placeholder": "",
|
||||
"possibilities": [
|
||||
{
|
||||
"display_value": "stdin"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_value": "2013-06-06T19:13:20.123",
|
||||
"search_placeholder": "",
|
||||
"possibilities": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -4,6 +4,16 @@ export TZ=UTC
|
||||
export YES_COLOR=1
|
||||
export DUMP_CRASH=1
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ":goto 0" \
|
||||
-c ":convert-time-to bad-zone" \
|
||||
${test_dir}/logfile_access_log.0
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ":goto 0" \
|
||||
-c ":convert-time-to America/Los_Angeles" \
|
||||
${test_dir}/logfile_access_log.0
|
||||
|
||||
run_cap_test ${lnav_test} -nN \
|
||||
-c ";SELECT ':echo Hello' || char(10) || ':echo World' AS cmds" \
|
||||
-c ':eval ${cmds}'
|
||||
|
@ -1,5 +1,6 @@
|
||||
#! /bin/bash
|
||||
|
||||
export TZ=UTC
|
||||
export YES_COLOR=1
|
||||
unset XDG_CONFIG_HOME
|
||||
|
||||
@ -97,3 +98,17 @@ run_cap_test ${lnav_test} -n \
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ':goto #/catalog/1/title' \
|
||||
< ${test_dir}/books.json
|
||||
|
||||
echo "Hello, World!" | run_cap_test env TEST_COMMENT="piper crumbs" ${lnav_test} -n \
|
||||
-c ';SELECT top_meta FROM lnav_top_view' \
|
||||
-c ':write-json-to -'
|
||||
|
||||
echo "Hello, World!" | run_cap_test \
|
||||
env TEST_COMMENT="piper crumbs" TZ=America/Los_Angeles \
|
||||
${lnav_test} -n \
|
||||
-c ';SELECT top_meta FROM lnav_top_view' \
|
||||
-c ':write-json-to -'
|
||||
|
||||
echo "Hello, World!" | run_cap_test \
|
||||
env TEST_COMMENT="piper crumbs" TZ=America/Los_Angeles \
|
||||
${lnav_test} -nt
|
||||
|
Loading…
Reference in New Issue
Block a user