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