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.
395 lines
10 KiB
C++
395 lines
10 KiB
C++
/**
|
|
* Copyright (c) 2020, Timothy Stack
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* * Neither the name of Timothy Stack nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* @file time_util.cc
|
|
*/
|
|
|
|
#include <chrono>
|
|
#include <map>
|
|
|
|
#include "time_util.hh"
|
|
|
|
#include <date/ptz.h>
|
|
|
|
#include "config.h"
|
|
#include "lnav_log.hh"
|
|
|
|
namespace lnav {
|
|
|
|
ssize_t
|
|
strftime_rfc3339(
|
|
char* buffer, size_t buffer_size, lnav::time64_t tim, int millis, char sep)
|
|
{
|
|
struct tm gmtm;
|
|
int year, month, index = 0;
|
|
|
|
secs2tm(tim, &gmtm);
|
|
year = gmtm.tm_year + 1900;
|
|
month = gmtm.tm_mon + 1;
|
|
buffer[index++] = '0' + ((year / 1000) % 10);
|
|
buffer[index++] = '0' + ((year / 100) % 10);
|
|
buffer[index++] = '0' + ((year / 10) % 10);
|
|
buffer[index++] = '0' + ((year / 1) % 10);
|
|
buffer[index++] = '-';
|
|
buffer[index++] = '0' + ((month / 10) % 10);
|
|
buffer[index++] = '0' + ((month / 1) % 10);
|
|
buffer[index++] = '-';
|
|
buffer[index++] = '0' + ((gmtm.tm_mday / 10) % 10);
|
|
buffer[index++] = '0' + ((gmtm.tm_mday / 1) % 10);
|
|
buffer[index++] = sep;
|
|
buffer[index++] = '0' + ((gmtm.tm_hour / 10) % 10);
|
|
buffer[index++] = '0' + ((gmtm.tm_hour / 1) % 10);
|
|
buffer[index++] = ':';
|
|
buffer[index++] = '0' + ((gmtm.tm_min / 10) % 10);
|
|
buffer[index++] = '0' + ((gmtm.tm_min / 1) % 10);
|
|
buffer[index++] = ':';
|
|
buffer[index++] = '0' + ((gmtm.tm_sec / 10) % 10);
|
|
buffer[index++] = '0' + ((gmtm.tm_sec / 1) % 10);
|
|
buffer[index++] = '.';
|
|
buffer[index++] = '0' + ((millis / 100) % 10);
|
|
buffer[index++] = '0' + ((millis / 10) % 10);
|
|
buffer[index++] = '0' + ((millis / 1) % 10);
|
|
buffer[index] = '\0';
|
|
|
|
return index;
|
|
}
|
|
|
|
std::string
|
|
to_rfc3339_string(time64_t tim, int millis, char sep)
|
|
{
|
|
char buf[64];
|
|
|
|
strftime_rfc3339(buf, sizeof(buf), tim, millis, sep);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static std::optional<Posix::time_zone>
|
|
get_posix_zone(const char* name)
|
|
{
|
|
if (name == nullptr) {
|
|
return std::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 std::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);
|
|
}
|
|
|
|
auto inf = TZ_DATE_ZONE->get_info(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);
|
|
}
|
|
|
|
date::sys_info
|
|
sys_time_to_info(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().get_info(secs);
|
|
}
|
|
|
|
return TZ_DATE_ZONE->get_info(secs);
|
|
}
|
|
|
|
date::local_info
|
|
local_time_to_info(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().get_info(secs);
|
|
}
|
|
|
|
return TZ_DATE_ZONE->get_info(secs);
|
|
}
|
|
|
|
} // namespace lnav
|
|
|
|
static time_t BAD_DATE = -1;
|
|
|
|
time_t
|
|
tm2sec(const struct tm* t)
|
|
{
|
|
int year;
|
|
time_t days, secs;
|
|
const int dayoffset[12]
|
|
= {306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275};
|
|
|
|
year = t->tm_year;
|
|
|
|
if (year < 70) {
|
|
return BAD_DATE;
|
|
}
|
|
if ((sizeof(time_t) <= 4) && (year >= 138)) {
|
|
year = 137;
|
|
}
|
|
|
|
/* shift new year to 1st March in order to make leap year calc easy */
|
|
|
|
if (t->tm_mon < 2) {
|
|
year--;
|
|
}
|
|
|
|
/* Find number of days since 1st March 1900 (in the Gregorian calendar). */
|
|
|
|
days = year * 365 + year / 4 - year / 100 + (year / 100 + 3) / 4;
|
|
days += dayoffset[t->tm_mon] + t->tm_mday - 1;
|
|
days -= 25508; /* 1 jan 1970 is 25508 days since 1 mar 1900 */
|
|
|
|
secs = ((days * 24 + t->tm_hour) * 60 + t->tm_min) * 60 + t->tm_sec;
|
|
|
|
if (secs < 0) {
|
|
return BAD_DATE;
|
|
} /* must have overflowed */
|
|
else
|
|
{
|
|
#ifdef HAVE_STRUCT_TM_TM_ZONE
|
|
if (t->tm_zone) {
|
|
secs -= t->tm_gmtoff;
|
|
}
|
|
#endif
|
|
return secs;
|
|
} /* must be a valid time */
|
|
}
|
|
|
|
static const int SECSPERMIN = 60;
|
|
static const int SECSPERHOUR = 60 * SECSPERMIN;
|
|
static const int SECSPERDAY = 24 * SECSPERHOUR;
|
|
static const int YEAR_BASE = 1900;
|
|
static const int EPOCH_WDAY = 4;
|
|
static const int DAYSPERWEEK = 7;
|
|
static const int EPOCH_YEAR = 1970;
|
|
|
|
#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
|
|
|
|
static const int year_lengths[2] = {365, 366};
|
|
|
|
const unsigned short int mon_yday[2][13] = {
|
|
/* Normal years. */
|
|
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
|
|
/* Leap years. */
|
|
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
|
|
|
|
void
|
|
secs2wday(const struct timeval& tv, struct tm* res)
|
|
{
|
|
long days, rem;
|
|
time_t lcltime;
|
|
|
|
/* base decision about std/dst time on current time */
|
|
lcltime = tv.tv_sec;
|
|
|
|
days = ((long) lcltime) / SECSPERDAY;
|
|
rem = ((long) lcltime) % SECSPERDAY;
|
|
while (rem < 0) {
|
|
rem += SECSPERDAY;
|
|
--days;
|
|
}
|
|
|
|
/* compute day of week */
|
|
if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) {
|
|
res->tm_wday += DAYSPERWEEK;
|
|
}
|
|
}
|
|
|
|
struct tm*
|
|
secs2tm(lnav::time64_t tim, struct tm* res)
|
|
{
|
|
long days, rem;
|
|
lnav::time64_t lcltime;
|
|
int y;
|
|
int yleap;
|
|
const unsigned short int* ip;
|
|
|
|
/* base decision about std/dst time on current time */
|
|
lcltime = tim;
|
|
|
|
days = ((long) lcltime) / SECSPERDAY;
|
|
rem = ((long) lcltime) % SECSPERDAY;
|
|
while (rem < 0) {
|
|
rem += SECSPERDAY;
|
|
--days;
|
|
}
|
|
|
|
/* compute hour, min, and sec */
|
|
res->tm_hour = (int) (rem / SECSPERHOUR);
|
|
rem %= SECSPERHOUR;
|
|
res->tm_min = (int) (rem / SECSPERMIN);
|
|
res->tm_sec = (int) (rem % SECSPERMIN);
|
|
|
|
/* compute day of week */
|
|
if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)
|
|
res->tm_wday += DAYSPERWEEK;
|
|
|
|
/* compute year & day of year */
|
|
y = EPOCH_YEAR;
|
|
if (days >= 0) {
|
|
for (;;) {
|
|
yleap = isleap(y);
|
|
if (days < year_lengths[yleap])
|
|
break;
|
|
y++;
|
|
days -= year_lengths[yleap];
|
|
}
|
|
} else {
|
|
do {
|
|
--y;
|
|
yleap = isleap(y);
|
|
days += year_lengths[yleap];
|
|
} while (days < 0);
|
|
}
|
|
|
|
res->tm_year = y - YEAR_BASE;
|
|
res->tm_yday = days;
|
|
ip = mon_yday[isleap(y)];
|
|
for (y = 11; days < (long int) ip[y]; --y)
|
|
continue;
|
|
days -= ip[y];
|
|
res->tm_mon = y;
|
|
res->tm_mday = days + 1;
|
|
|
|
res->tm_isdst = 0;
|
|
|
|
return (res);
|
|
}
|
|
|
|
exttm
|
|
exttm::from_tv(const timeval& tv)
|
|
{
|
|
auto retval = exttm{};
|
|
|
|
retval.et_tm = *gmtime(&tv.tv_sec);
|
|
retval.et_nsec = tv.tv_usec * 1000;
|
|
|
|
return retval;
|
|
}
|
|
|
|
struct timeval
|
|
exttm::to_timeval() const
|
|
{
|
|
struct timeval retval;
|
|
|
|
retval.tv_sec = tm2sec(&this->et_tm);
|
|
retval.tv_sec -= this->et_gmtoff;
|
|
retval.tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
std::chrono::nanoseconds(this->et_nsec))
|
|
.count();
|
|
|
|
return retval;
|
|
}
|
|
|
|
time_range&
|
|
time_range::operator|=(const time_range& rhs)
|
|
{
|
|
if (rhs.tr_begin < this->tr_begin) {
|
|
this->tr_begin = rhs.tr_begin;
|
|
}
|
|
if (this->tr_end < rhs.tr_end) {
|
|
this->tr_end = rhs.tr_end;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
time_range::extend_to(const timeval& tv)
|
|
{
|
|
if (tv < this->tr_begin) {
|
|
// logs aren't always in time-order
|
|
this->tr_begin = tv;
|
|
} else if (this->tr_end < tv) {
|
|
this->tr_end = tv;
|
|
}
|
|
}
|
|
|
|
std::chrono::milliseconds
|
|
time_range::duration() const
|
|
{
|
|
auto diff = this->tr_end - this->tr_begin;
|
|
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::seconds(diff.tv_sec))
|
|
+ std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::microseconds(diff.tv_usec));
|
|
}
|
|
|
|
bool
|
|
time_range::contains_inclusive(const timeval& tv) const
|
|
{
|
|
return (this->tr_begin <= tv) && (tv <= this->tr_end);
|
|
}
|