412 lines
12 KiB
C++
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
|