OXYGENHUB
◆ main openmsx / src / utils / Date.cc
4.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
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
	// skip day
38
39
	// space
40
	if (s[3] != ' ') return INVALID_TIME_T;
41
42
	// Parse month
43
	switch (s[4]) {
44
	case 'J': // Jan Jun Jul
45
		switch (s[5]) {
46
		case 'a': // Jan
47
			if (s[6] != 'n') return INVALID_TIME_T;
48
			tm.tm_mon = 0; break;
49
		case 'u': // Jun Jul
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': // Feb
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': // Mar May
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': // Apr Aug
73
		switch (s[5]) {
74
		case 'p': // Apr
75
			if (s[6] != 'r') return INVALID_TIME_T;
76
			tm.tm_mon = 3; break;
77
		case 'u': // Aug
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': // Sep
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': // Oct
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': // Nov
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': // Dec
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
	// space
107
	if (s[7] != ' ') return INVALID_TIME_T;
108
109
	// parse mday
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
	// space
115
	if (s[10] != ' ') return INVALID_TIME_T;
116
117
	// parse hour
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
	// colon
123
	if (s[13] != ':') return INVALID_TIME_T;
124
125
	// parse minute
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
	// colon
131
	if (s[16] != ':') return INVALID_TIME_T;
132
133
	// parse second
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
	// space
139
	if (s[19] != ' ') return INVALID_TIME_T;
140
141
	// parse year
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); // most likely exactly equal to 24, but could be more for year > 9999
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
} // namespace openmsx::Date