@@ -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