Skip to content

Commit e3dba06

Browse files
authored
Merge pull request #590 from OpenVicProject/add/data-fmt-parse
Add fmt specifier parsing to Date
2 parents 57475e0 + 3ce7fca commit e3dba06

File tree

3 files changed

+674
-17
lines changed

3 files changed

+674
-17
lines changed

src/openvic-simulation/types/Date.cpp

Lines changed: 303 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,309 @@ std::ostream& OpenVic::operator<<(std::ostream& out, Date date) {
5656
return out << static_cast<std::string_view>(result);
5757
}
5858

59-
auto fmt::formatter<Date>::format(Date d, format_context& ctx) const -> format_context::iterator {
60-
Date::stack_string result = d.to_array();
61-
if (OV_unlikely(result.empty())) {
62-
return formatter<string_view>::format(string_view {}, ctx);
59+
using namespace ovfmt::detail;
60+
61+
template<typename OutputIt, typename Char = char>
62+
struct date_writer {
63+
bool _unsupported = false;
64+
65+
inline static constexpr Date::day_t DAYS_PER_WEEK = Date::WEEKDAY_NAMES.size();
66+
67+
constexpr date_writer(OutputIt out, Date const& date, Char = {}) : _out(out), _date(date) {}
68+
69+
constexpr OutputIt out() const {
70+
return _out;
71+
}
72+
73+
inline void unsupported() {
74+
_unsupported = true;
75+
report_error("no format");
76+
}
77+
78+
constexpr void on_text(const Char* begin, const Char* end) {
79+
_out = fmt::detail::copy<Char>(begin, end, _out);
80+
}
81+
82+
void on_year(numeric_system ns, pad_type pad) {
83+
return write_year(_date.get_year(), pad);
84+
}
85+
86+
void on_short_year(numeric_system ns) {
87+
return write2(split_year_lower(_date.get_year()));
88+
}
89+
90+
void on_century(numeric_system ns) {
91+
OpenVic::Date::year_t year = _date.get_year();
92+
OpenVic::Date::year_t upper = year / 100;
93+
if (year >= -99 && year < 0) {
94+
// Zero upper on negative year.
95+
*_out++ = '-';
96+
*_out++ = '0';
97+
} else if (upper >= 0 && upper < 100) {
98+
write2(static_cast<int>(upper));
99+
} else {
100+
_out = detail::write<Char>(_out, upper);
101+
}
102+
}
103+
104+
void on_iso_week_based_year() {
105+
write_year(date_iso_week_year(), pad_type::zero);
106+
}
107+
108+
void on_iso_week_based_short_year() {
109+
write2(split_year_lower(date_iso_week_year()));
110+
}
111+
112+
void on_offset_year() {
113+
return write2(split_year_lower(_date.get_year()));
114+
}
115+
116+
void on_abbr_weekday() {
117+
_out = detail::write<Char>(_out, _date.get_weekday_name().substr(0, 3));
118+
}
119+
120+
void on_full_weekday() {
121+
_out = detail::write<Char>(_out, _date.get_weekday_name());
122+
}
123+
124+
void on_dec0_weekday(numeric_system ns) {
125+
return write1(_date.get_day_of_week());
126+
}
127+
128+
void on_dec1_weekday(numeric_system ns) {
129+
auto wday = _date.get_day_of_week();
130+
write1(wday == 0 ? DAYS_PER_WEEK : wday);
131+
}
132+
133+
void on_abbr_month() {
134+
_out = detail::write<Char>(_out, _date.get_month_name().substr(0, 3));
135+
}
136+
void on_full_month() {
137+
_out = detail::write<Char>(_out, _date.get_month_name());
138+
}
139+
140+
void on_dec_month(numeric_system ns, pad_type pad) {
141+
return write2(_date.get_month(), pad);
142+
}
143+
144+
void on_dec0_week_of_year(numeric_system ns, pad_type pad) {
145+
return write2(_date.get_week_of_year(), pad);
146+
}
147+
148+
void on_dec1_week_of_year(numeric_system ns, pad_type pad) {
149+
auto wday = _date.get_day_of_week();
150+
write2((_date.get_day_of_year() + DAYS_PER_WEEK - (wday == 0 ? (DAYS_PER_WEEK - 1) : (wday - 1))) / DAYS_PER_WEEK, pad);
63151
}
64152

65-
return formatter<string_view>::format(string_view { result.data(), result.size() }, ctx);
153+
void on_iso_week_of_year(numeric_system ns, pad_type pad) {
154+
return write2(date_iso_week_of_year(), pad);
155+
}
156+
157+
void on_day_of_year(pad_type pad) {
158+
auto yday = _date.get_day_of_year() + 1;
159+
auto digit1 = yday / 100;
160+
if (digit1 != 0) {
161+
write1(digit1);
162+
} else {
163+
_out = write_padding(_out, pad);
164+
}
165+
write2(yday % 100, pad);
166+
}
167+
168+
void on_day_of_month(numeric_system ns, pad_type pad) {
169+
return write2(_date.get_day(), pad);
170+
}
171+
172+
void on_loc_date(numeric_system ns) {
173+
char buf[8];
174+
write_digit2_separated(
175+
buf, //
176+
detail::to_unsigned(_date.get_day()), //
177+
detail::to_unsigned(_date.get_month()), //
178+
detail::to_unsigned(split_year_lower(_date.get_year())), //
179+
'/'
180+
);
181+
_out = detail::copy<Char>(std::begin(buf), std::end(buf), _out);
182+
}
183+
184+
void on_us_date() {
185+
char buf[8];
186+
write_digit2_separated(
187+
buf, //
188+
detail::to_unsigned(_date.get_month()), //
189+
detail::to_unsigned(_date.get_day()), //
190+
detail::to_unsigned(split_year_lower(_date.get_year())), //
191+
'/'
192+
);
193+
_out = detail::copy<Char>(std::begin(buf), std::end(buf), _out);
194+
}
195+
196+
void on_iso_date() {
197+
auto year = _date.get_year();
198+
char buf[10];
199+
size_t offset = 0;
200+
if (year >= 0 && year < 10000) {
201+
detail::write2digits(buf, static_cast<size_t>(year / 100));
202+
} else {
203+
offset = 4;
204+
write_year_extended(year, pad_type::zero);
205+
year = 0;
206+
}
207+
write_digit2_separated(
208+
buf + 2, //
209+
static_cast<unsigned>(year % 100), //
210+
detail::to_unsigned(_date.get_month()), //
211+
detail::to_unsigned(_date.get_day()), //
212+
'-'
213+
);
214+
_out = detail::copy<Char>(std::begin(buf) + offset, std::end(buf), _out);
215+
}
216+
217+
private:
218+
static OutputIt write_padding(OutputIt out, pad_type pad, int width) {
219+
if (pad == pad_type::none) {
220+
return out;
221+
}
222+
return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0');
223+
}
224+
225+
static OutputIt write_padding(OutputIt out, pad_type pad) {
226+
if (pad != pad_type::none) {
227+
*out++ = pad == pad_type::space ? ' ' : '0';
228+
}
229+
return out;
230+
}
231+
232+
void write1(int value) {
233+
*_out++ = static_cast<char>('0' + detail::to_unsigned(value) % 10);
234+
}
235+
void write2(int value) {
236+
const char* d = detail::digits2(detail::to_unsigned(value) % 100);
237+
*_out++ = *d++;
238+
*_out++ = *d;
239+
}
240+
void write2(int value, pad_type pad) {
241+
unsigned int v = detail::to_unsigned(value) % 100;
242+
if (v >= 10) {
243+
const char* d = detail::digits2(v);
244+
*_out++ = *d++;
245+
*_out++ = *d;
246+
} else {
247+
_out = write_padding(_out, pad);
248+
*_out++ = static_cast<char>('0' + v);
249+
}
250+
}
251+
252+
void write_year_extended(long long year, pad_type pad) {
253+
// At least 4 characters.
254+
int width = 4;
255+
bool negative = year < 0;
256+
if (negative) {
257+
year = 0 - year;
258+
--width;
259+
}
260+
detail::uint32_or_64_or_128_t<long long> n = detail::to_unsigned(year);
261+
const int num_digits = detail::count_digits(n);
262+
if (negative && pad == pad_type::zero) {
263+
*_out++ = '-';
264+
}
265+
if (width > num_digits) {
266+
_out = write_padding(_out, pad, width - num_digits);
267+
}
268+
if (negative && pad != pad_type::zero) {
269+
*_out++ = '-';
270+
}
271+
_out = detail::format_decimal<Char>(_out, n, num_digits);
272+
}
273+
void write_year(long long year, pad_type pad) {
274+
write_year_extended(year, pad);
275+
}
276+
277+
int split_year_lower(long long year) const {
278+
auto l = year % 100;
279+
if (l < 0) {
280+
l = -l; // l in [0, 99]
281+
}
282+
return static_cast<int>(l);
283+
}
284+
285+
// Writes two-digit numbers a, b and c separated by sep to buf.
286+
// The method by Pavel Novikov based on
287+
// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.
288+
static inline void write_digit2_separated(char* buf, unsigned a, unsigned b, unsigned c, char sep) {
289+
unsigned long long digits = a | (b << 24) | (static_cast<unsigned long long>(c) << 48);
290+
// Convert each value to BCD.
291+
// We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.
292+
// The difference is
293+
// y - x = a * 6
294+
// a can be found from x:
295+
// a = floor(x / 10)
296+
// then
297+
// y = x + a * 6 = x + floor(x / 10) * 6
298+
// floor(x / 10) is (x * 205) >> 11 (needs 16 bits).
299+
digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;
300+
// Put low nibbles to high bytes and high nibbles to low bytes.
301+
digits = ((digits & 0x00f00000f00000f0) >> 4) | ((digits & 0x000f00000f00000f) << 8);
302+
auto usep = static_cast<unsigned long long>(sep);
303+
// Add ASCII '0' to each digit byte and insert separators.
304+
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
305+
306+
constexpr const size_t len = 8;
307+
if (detail::const_check(detail::is_big_endian())) {
308+
char tmp[len];
309+
std::memcpy(tmp, &digits, len);
310+
std::reverse_copy(tmp, tmp + len, buf);
311+
} else {
312+
std::memcpy(buf, &digits, len);
313+
}
314+
}
315+
316+
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
317+
int iso_year_weeks(long long curr_year) const {
318+
auto prev_year = curr_year - 1;
319+
auto curr_p = (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % DAYS_PER_WEEK;
320+
auto prev_p = (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % DAYS_PER_WEEK;
321+
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
322+
}
323+
int iso_week_num(int tm_yday, int tm_wday) const {
324+
return (tm_yday + 11 - (tm_wday == 0 ? DAYS_PER_WEEK : tm_wday)) / DAYS_PER_WEEK;
325+
}
326+
long long date_iso_week_year() const {
327+
auto year = _date.get_year();
328+
auto w = iso_week_num(_date.get_day_of_year(), _date.get_day_of_week());
329+
if (w < 1) {
330+
return year - 1;
331+
}
332+
if (w > iso_year_weeks(year)) {
333+
return year + 1;
334+
}
335+
return year;
336+
}
337+
int date_iso_week_of_year() const {
338+
auto year = _date.get_year();
339+
auto w = iso_week_num(_date.get_day_of_year(), _date.get_day_of_week());
340+
if (w < 1) {
341+
return iso_year_weeks(year - 1);
342+
}
343+
if (w > iso_year_weeks(year)) {
344+
return 1;
345+
}
346+
return w;
347+
}
348+
349+
OutputIt _out;
350+
OpenVic::Date const& _date;
351+
};
352+
353+
fmt::format_context::iterator fmt::formatter<Date>::format(Date d, format_context& ctx) const {
354+
format_specs specs { _specs };
355+
if (_specs.dynamic()) {
356+
detail::handle_dynamic_spec(_specs.dynamic_width(), specs.width, _specs.width_ref, ctx);
357+
}
358+
359+
basic_memory_buffer buf = basic_memory_buffer<char>();
360+
basic_appender out = basic_appender<char>(buf);
361+
362+
parse_date_format(_fmt.begin(), _fmt.end(), date_writer { out, d });
363+
return detail::write(ctx.out(), string_view { buf.data(), buf.size() }, specs);
66364
}

0 commit comments

Comments
 (0)