| e8555c89 Andy McCall |
1 |
#include "Date.hh" |
| 2 |
|
| 3 |
#include <array> |
| 4 |
#include <cassert> |
| 5 |
#include <concepts> |
| 6 |
#include <format> |
| 7 |
#include <string_view> |
| 8 |
|
| 9 |
namespace openmsx::Date { |
| 10 |
|
| 11 |
static constexpr std::array<std::string_view, 7> days = { |
| 12 |
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" |
| 13 |
}; |
| 14 |
|
| 15 |
static constexpr std::array<std::string_view, 12> months = { |
| 16 |
"Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| 17 |
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
| 18 |
}; |
| 19 |
|
| 20 |
template<bool FIRST, unsigned MUL> |
| 21 |
[[nodiscard]] static constexpr bool parseDigit(unsigned c, std::integral auto& t) |
| 22 |
{ |
| 23 |
c -= '0'; |
| 24 |
if (c > 9) return false; |
| 25 |
if constexpr (FIRST) { |
| 26 |
t = c * MUL; |
| 27 |
} else { |
| 28 |
t += c * MUL; |
| 29 |
} |
| 30 |
return true; |
| 31 |
} |
| 32 |
|
| 33 |
time_t fromString(std::span<const char, 24> s) |
| 34 |
{ |
| 35 |
struct tm tm{}; |
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
if (s[3] != ' ') return INVALID_TIME_T; |
| 41 |
|
| 42 |
|
| 43 |
switch (s[4]) { |
| 44 |
case 'J': |
| 45 |
switch (s[5]) { |
| 46 |
case 'a': |
| 47 |
if (s[6] != 'n') return INVALID_TIME_T; |
| 48 |
tm.tm_mon = 0; break; |
| 49 |
case 'u': |
| 50 |
switch (s[6]) { |
| 51 |
case 'n': tm.tm_mon = 5; break; |
| 52 |
case 'l': tm.tm_mon = 6; break; |
| 53 |
default: return INVALID_TIME_T; |
| 54 |
} |
| 55 |
break; |
| 56 |
default: return INVALID_TIME_T; |
| 57 |
} |
| 58 |
break; |
| 59 |
case 'F': |
| 60 |
if (s[5] != 'e') return INVALID_TIME_T; |
| 61 |
if (s[6] != 'b') return INVALID_TIME_T; |
| 62 |
tm.tm_mon = 1; |
| 63 |
break; |
| 64 |
case 'M': |
| 65 |
if (s[5] != 'a') return INVALID_TIME_T; |
| 66 |
switch (s[6]) { |
| 67 |
case 'r': tm.tm_mon = 2; break; |
| 68 |
case 'y': tm.tm_mon = 4; break; |
| 69 |
default: return INVALID_TIME_T; |
| 70 |
} |
| 71 |
break; |
| 72 |
case 'A': |
| 73 |
switch (s[5]) { |
| 74 |
case 'p': |
| 75 |
if (s[6] != 'r') return INVALID_TIME_T; |
| 76 |
tm.tm_mon = 3; break; |
| 77 |
case 'u': |
| 78 |
if (s[6] != 'g') return INVALID_TIME_T; |
| 79 |
tm.tm_mon = 7; break; |
| 80 |
default: return INVALID_TIME_T; |
| 81 |
} |
| 82 |
break; |
| 83 |
case 'S': |
| 84 |
if (s[5] != 'e') return INVALID_TIME_T; |
| 85 |
if (s[6] != 'p') return INVALID_TIME_T; |
| 86 |
tm.tm_mon = 8; |
| 87 |
break; |
| 88 |
case 'O': |
| 89 |
if (s[5] != 'c') return INVALID_TIME_T; |
| 90 |
if (s[6] != 't') return INVALID_TIME_T; |
| 91 |
tm.tm_mon = 9; |
| 92 |
break; |
| 93 |
case 'N': |
| 94 |
if (s[5] != 'o') return INVALID_TIME_T; |
| 95 |
if (s[6] != 'v') return INVALID_TIME_T; |
| 96 |
tm.tm_mon = 10; |
| 97 |
break; |
| 98 |
case 'D': |
| 99 |
if (s[5] != 'e') return INVALID_TIME_T; |
| 100 |
if (s[6] != 'c') return INVALID_TIME_T; |
| 101 |
tm.tm_mon = 11; |
| 102 |
break; |
| 103 |
default: return INVALID_TIME_T; |
| 104 |
} |
| 105 |
|
| 106 |
|
| 107 |
if (s[7] != ' ') return INVALID_TIME_T; |
| 108 |
|
| 109 |
|
| 110 |
if (!parseDigit<true, 10>(s[8], tm.tm_mday)) return INVALID_TIME_T; |
| 111 |
if (!parseDigit<false, 1>(s[9], tm.tm_mday)) return INVALID_TIME_T; |
| 112 |
if ((tm.tm_mday < 1) || (31 < tm.tm_mday)) return INVALID_TIME_T; |
| 113 |
|
| 114 |
|
| 115 |
if (s[10] != ' ') return INVALID_TIME_T; |
| 116 |
|
| 117 |
|
| 118 |
if (!parseDigit<true, 10>(s[11], tm.tm_hour)) return INVALID_TIME_T; |
| 119 |
if (!parseDigit<false, 1>(s[12], tm.tm_hour)) return INVALID_TIME_T; |
| 120 |
if ((tm.tm_hour < 0) || (23 < tm.tm_hour)) return INVALID_TIME_T; |
| 121 |
|
| 122 |
|
| 123 |
if (s[13] != ':') return INVALID_TIME_T; |
| 124 |
|
| 125 |
|
| 126 |
if (!parseDigit<true, 10>(s[14], tm.tm_min)) return INVALID_TIME_T; |
| 127 |
if (!parseDigit<false, 1>(s[15], tm.tm_min)) return INVALID_TIME_T; |
| 128 |
if ((tm.tm_min < 0) || (59 < tm.tm_min)) return INVALID_TIME_T; |
| 129 |
|
| 130 |
|
| 131 |
if (s[16] != ':') return INVALID_TIME_T; |
| 132 |
|
| 133 |
|
| 134 |
if (!parseDigit<true, 10>(s[17], tm.tm_sec)) return INVALID_TIME_T; |
| 135 |
if (!parseDigit<false, 1>(s[18], tm.tm_sec)) return INVALID_TIME_T; |
| 136 |
if ((tm.tm_sec < 0) || (59 < tm.tm_sec)) return INVALID_TIME_T; |
| 137 |
|
| 138 |
|
| 139 |
if (s[19] != ' ') return INVALID_TIME_T; |
| 140 |
|
| 141 |
|
| 142 |
if (!parseDigit<true, 1000>(s[20], tm.tm_year)) return INVALID_TIME_T; |
| 143 |
if (!parseDigit<false, 100>(s[21], tm.tm_year)) return INVALID_TIME_T; |
| 144 |
if (!parseDigit<false, 10>(s[22], tm.tm_year)) return INVALID_TIME_T; |
| 145 |
if (!parseDigit<false, 1>(s[23], tm.tm_year)) return INVALID_TIME_T; |
| 146 |
tm.tm_year -= 1900; |
| 147 |
if (tm.tm_year < 0) return INVALID_TIME_T; |
| 148 |
|
| 149 |
tm.tm_isdst = -1; |
| 150 |
return mktime(&tm); |
| 151 |
} |
| 152 |
|
| 153 |
std::string_view toString(time_t time, std::span<char, 24> buffer) |
| 154 |
{ |
| 155 |
time = std::max(time, time_t(0)); |
| 156 |
const struct tm* tm = localtime(&time); |
| 157 |
[[maybe_unused]] auto result = std::format_to_n( |
| 158 |
buffer.data(), 24, "{} {} {:02d} {:02d}:{:02d}:{:02d} {:04d}", |
| 159 |
days[tm->tm_wday], months[tm->tm_mon], tm->tm_mday, |
| 160 |
tm->tm_hour, tm->tm_min, tm->tm_sec, |
| 161 |
tm->tm_year + 1900); |
| 162 |
assert(result.size >= 24); |
| 163 |
return {buffer.data(), 24}; |
| 164 |
} |
| 165 |
|
| 166 |
std::string toString(time_t time) |
| 167 |
{ |
| 168 |
std::array<char, 24> buffer; |
| 169 |
return std::string(toString(time, buffer)); |
| 170 |
} |
| 171 |
|
| 172 |
} |