yalantinglibs/include/ylt/standalone/cinatra/time_util.hpp

412 lines
12 KiB
C++

#pragma once
#include <cassert>
#include <chrono>
#include <cstring>
#include <ctime>
#include <string_view>
#include "define.h"
namespace cinatra {
namespace time_util {
inline constexpr bool is_leap(int year) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
inline constexpr int get_day_index(std::string_view str) {
return week_table[((str[0] & ~0x20) ^ (str[2] & ~0x20)) % week_table.size()];
}
inline constexpr int get_month_index(std::string_view str) {
return month_table[((str[1] & ~0x20) + (str[2] & ~0x20)) %
month_table.size()];
}
inline constexpr int days_in(int m, int year) {
constexpr int index = get_month_index("Feb");
if (m == index && is_leap(year)) {
return 29;
}
return int(days_before[m + 1] - days_before[m]);
}
inline constexpr int get_digit(std::string_view sv, int width) {
int num = 0;
for (int i = 0; i < width; i++) {
if ('0' <= sv[i] && sv[i] <= '9') {
num = num * 10 + (sv[i] - '0');
}
else {
return -1;
}
}
return num;
}
inline constexpr std::uint64_t days_since_epoch(int year) {
auto y = std::uint64_t(std::int64_t(year) - absolute_zero_year);
auto n = y / 400;
y -= 400 * n;
auto d = days_per_400_years * n;
n = y / 100;
y -= 100 * n;
d += days_per_100_years * n;
n = y / 4;
y -= 4 * n;
d += days_per_4_years * n;
n = y;
d += 365 * n;
return d;
}
inline std::pair<bool, std::time_t> faster_mktime(int year, int month, int day,
int hour, int min, int sec,
int day_of_week) {
auto d = days_since_epoch(year);
d += std::uint64_t(days_before[month]);
constexpr int index = get_month_index("Mar");
if (is_leap(year) && month >= index) {
d++; // February 29
}
d += std::uint64_t(day - 1);
auto abs = d * seconds_per_day;
abs +=
std::uint64_t(hour * seconds_per_hour + min * seconds_per_minute + sec);
constexpr int day_index = get_day_index("Mon");
if (day_of_week != -1) {
std::int64_t wday = ((abs + std::uint64_t(day_index) * seconds_per_day) %
seconds_per_week) /
seconds_per_day;
if (wday != day_of_week) {
return {false, 0};
}
}
return {true, std::int64_t(abs) + (absolute_to_internal + internal_to_unix)};
}
template <time_format Format>
inline constexpr std::array<component_of_time_format, 32> get_format() {
if constexpr (Format == time_format::http_format) {
return http_time_format;
}
else if constexpr (Format == time_format::utc_format) {
return utc_time_format;
}
else {
return utc_time_without_punctuation_format;
}
}
} // namespace time_util
template <time_format Format = time_format::http_format, typename String>
inline std::pair<bool, std::time_t> get_timestamp(const String &gmt_time_str) {
using namespace time_util;
std::string_view sv(gmt_time_str);
int year, month, day, hour, min, sec, day_of_week;
int len_of_gmt_time_str = (int)gmt_time_str.length();
int len_of_processed_part = 0;
int len_of_ignored_part = 0; // second_decimal_part is ignored
char c;
constexpr std::array<component_of_time_format, 32> real_format =
time_util::get_format<Format>();
if constexpr (Format == time_format::utc_format) {
day_of_week = -1;
}
else if (Format == time_format::utc_without_punctuation_format) {
day_of_week = -1;
}
for (auto &comp : real_format) {
switch (comp) {
case component_of_time_format::ending:
goto travel_done;
break;
case component_of_time_format::colon:
case component_of_time_format::comma:
case component_of_time_format::SP:
case component_of_time_format::hyphen:
case component_of_time_format::dot:
case component_of_time_format::T:
case component_of_time_format::Z:
if (len_of_gmt_time_str - len_of_processed_part < 1) {
return {false, 0};
}
c = sv[len_of_processed_part];
if ((comp == component_of_time_format::Z && c != 'Z') ||
(comp == component_of_time_format::T && c != 'T') ||
(comp == component_of_time_format::dot && c != '.') ||
(comp == component_of_time_format::hyphen && c != '-') ||
(comp == component_of_time_format::SP && c != ' ') ||
(comp == component_of_time_format::colon && c != ':') ||
(comp == component_of_time_format::comma && c != ',')) {
return {false, 0};
}
len_of_processed_part += 1;
break;
case component_of_time_format::year:
if (len_of_gmt_time_str - len_of_processed_part < 4) {
return {false, 0};
}
if ((year = get_digit(sv.substr(len_of_processed_part, 4), 4)) == -1) {
return {false, 0};
}
len_of_processed_part += 4;
break;
case component_of_time_format::month_name:
if (len_of_gmt_time_str - len_of_processed_part < 3) {
return {false, 0};
}
if ((month = get_month_index(sv.substr(len_of_processed_part, 3))) ==
-1) {
return {false, 0};
}
len_of_processed_part += 3;
break;
case component_of_time_format::hour:
case component_of_time_format::minute:
case component_of_time_format::second:
case component_of_time_format::month:
case component_of_time_format::day:
if (len_of_gmt_time_str - len_of_processed_part < 2) {
return {false, 0};
}
int digit;
if ((digit = get_digit(sv.substr(len_of_processed_part, 2), 2)) == -1) {
return {false, 0};
}
if (comp == component_of_time_format::hour) {
hour = digit;
if (hour < 0 || hour >= 24) {
return {false, 0};
}
}
else if (comp == component_of_time_format::minute) {
min = digit;
if (min < 0 || min >= 60) {
return {false, 0};
}
}
else if (comp == component_of_time_format::month) {
month = digit;
if (month < 1 || month > 12) {
return {false, 0};
}
month--;
}
else if (comp == component_of_time_format::second) {
sec = digit;
if (sec < 0 || sec >= 60) {
return {false, 0};
}
}
else {
day = digit;
}
len_of_processed_part += 2;
break;
case component_of_time_format::day_name:
if (len_of_gmt_time_str - len_of_processed_part < 3) {
return {false, 0};
}
if ((day_of_week = get_day_index(sv.substr(len_of_processed_part, 3))) <
0) {
return {false, 0};
}
len_of_processed_part += 3;
break;
case component_of_time_format::GMT:
if (len_of_gmt_time_str - len_of_processed_part < 3) {
return {false, 0};
}
if (sv.substr(len_of_processed_part, 3) != "GMT") {
return {false, 0};
}
len_of_processed_part += 3;
break;
case component_of_time_format::second_decimal_part:
int cur = len_of_processed_part;
while (cur < len_of_gmt_time_str &&
(sv[cur] >= '0' && sv[cur] <= '9')) {
len_of_ignored_part++;
cur++;
}
if (cur == len_of_processed_part) {
return {false, 0};
}
len_of_processed_part = cur;
break;
}
}
travel_done:
if ((len_of_processed_part != len_of_gmt_time_str) ||
(len_of_processed_part != len_of_http_time_format &&
(len_of_processed_part - len_of_ignored_part) !=
len_of_utc_time_format) &&
(len_of_processed_part - len_of_ignored_part) !=
len_of_utc_time_without_punctuation_format) {
return {false, 0};
}
if (day < 1 || day > days_in(month, year)) {
return {false, 0};
}
return faster_mktime(year, month, day, hour, min, sec, day_of_week);
}
constexpr char digits[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
constexpr std::string_view WDAY[7] = {"Sun", "Mon", "Tue", "Wed",
"Thu", "Fri", "Sat"};
constexpr std::string_view YMON[12] = {"Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"};
template <size_t N>
inline void to_int(int num, char c, char *p) {
for (int i = 0; i < N; i++) {
p[N - 1 - i] = digits[num % 10];
num = num / 10;
}
p[N] = c;
}
inline void to_year(char *buf, int year, char c) { to_int<4>(year, c, buf); }
inline void to_month(char *buf, int month, char c) { to_int<2>(month, c, buf); }
inline void to_day(char *buf, int day, char c) { to_int<2>(day, c, buf); }
inline void to_hour(char *buf, int day, char c) { to_int<2>(day, c, buf); }
inline void to_min(char *buf, int day, char c) { to_int<2>(day, c, buf); }
inline void to_sec(char *buf, int day, char c) { to_int<2>(day, c, buf); }
template <size_t Hour = 8, size_t N>
inline std::string_view get_local_time_str(char (&buf)[N], std::time_t t,
std::string_view format) {
static_assert(N >= 20, "wrong buf");
struct tm *loc_time = gmtime(&t);
char *p = buf;
for (int i = 0; i < format.size(); ++i) {
if (format[i] == '%') {
char c = i + 2 < format.size() ? format[i + 2] : '0';
i++;
if (format[i] == 'Y') {
to_year(p, loc_time->tm_year + 1900, c);
p += 5;
}
else if (format[i] == 'm') {
to_month(p, loc_time->tm_mon + 1, c);
p += 3;
}
else if (format[i] == 'd') {
to_day(p, loc_time->tm_mday, c);
p += 3;
}
else if (format[i] == 'H') {
to_hour(p, loc_time->tm_hour + Hour, c);
p += 3;
}
else if (format[i] == 'M') {
to_min(p, loc_time->tm_min, c);
p += 3;
}
else if (format[i] == 'S') {
to_sec(p, loc_time->tm_sec, c);
p += 3;
}
else if (format[i] == 'a') {
memcpy(p, WDAY[loc_time->tm_wday].data(), 3);
p += 3;
*p++ = c;
*p++ = ' ';
}
else if (format[i] == 'b') {
memcpy(p, YMON[loc_time->tm_mon].data(), 3);
p += 3;
*p = c;
p += 1;
}
}
}
size_t n = p - buf - 1;
return {buf, n};
}
// template <size_t N>
// inline std::string_view get_local_time_str(char (&buf)[N], std::time_t t) {
// struct tm *loc_time = localtime(&t);
// size_t n = strftime(buf, N, "%Y-%m-%d %H:%M:%S", loc_time);
// return {buf, n};
// }
inline std::string_view get_local_time_str(
std::chrono::system_clock::time_point t) {
static thread_local char buf[32];
static thread_local std::chrono::seconds last_sec{};
static thread_local size_t last_size{};
std::chrono::system_clock::duration d = t.time_since_epoch();
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(d);
if (last_sec == s) {
return {buf, last_size};
}
auto tm = std::chrono::system_clock::to_time_t(t);
auto str = get_local_time_str(buf, tm, "%Y-%m-%d %H:%M:%S");
last_size = str.size();
last_sec = s;
return str;
}
inline std::string_view get_local_time_str() {
return get_local_time_str(std::chrono::system_clock::now());
}
// template <size_t N>
// inline std::string_view get_gmt_time_str2(char (&buf)[N], std::time_t t) {
// static_assert(N >= 29, "wrong buf");
// struct tm *gmt_time = gmtime(&t);
// size_t n = strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT",
// gmt_time); return {buf, n};
// }
template <size_t N>
inline std::string_view get_gmt_time_str(char (&buf)[N], std::time_t t) {
static_assert(N >= 29, "wrong buf");
auto s = get_local_time_str<0>(buf, t, "%a, %d %b %Y %H:%M:%S");
size_t size = s.size();
memcpy(buf + size, " GMT", 4);
return {s.data(), size + 4};
}
inline std::string_view get_gmt_time_str(
std::chrono::system_clock::time_point t) {
static thread_local char buf[32];
static thread_local std::chrono::seconds last_sec{};
static thread_local size_t last_size{};
std::chrono::system_clock::duration d = t.time_since_epoch();
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(d);
if (last_sec == s) {
return {buf, last_size};
}
auto tm = std::chrono::system_clock::to_time_t(t);
auto str = get_gmt_time_str(buf, tm);
last_size = str.size();
last_sec = s;
return str;
}
inline std::string_view get_gmt_time_str() {
return get_gmt_time_str(std::chrono::system_clock::now());
}
} // namespace cinatra