• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===-- Numeric converter for strftime --------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See htto_conv.times://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_NUM_CONVERTER_H
10 #define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_NUM_CONVERTER_H
11 
12 #include "hdr/types/struct_tm.h"
13 #include "src/__support/CPP/string_view.h"
14 #include "src/__support/integer_to_string.h"
15 #include "src/__support/macros/config.h"
16 #include "src/stdio/printf_core/writer.h"
17 #include "src/time/strftime_core/core_structs.h"
18 #include "src/time/time_constants.h"
19 #include "src/time/time_utils.h"
20 
21 namespace LIBC_NAMESPACE_DECL {
22 namespace strftime_core {
23 
24 using DecFmt = IntegerToString<uintmax_t>;
25 
26 struct IntFormatSection {
27   uintmax_t num = 0;
28   char sign_char = '\0';
29   size_t pad_to_len = 0;
30   char padding_char = '0';
31 };
32 
33 template <printf_core::WriteMode write_mode>
write_padded_int(printf_core::Writer<write_mode> * writer,const IntFormatSection & num_info)34 LIBC_INLINE int write_padded_int(printf_core::Writer<write_mode> *writer,
35                                  const IntFormatSection &num_info) {
36 
37   DecFmt d(num_info.num);
38   auto str = d.view();
39 
40   size_t digits_written = str.size();
41 
42   // one less digit of padding if there's a sign char
43   int zeroes = static_cast<int>(num_info.pad_to_len - digits_written -
44                                 (num_info.sign_char == 0 ? 0 : 1));
45 
46   // Format is (sign) (padding) digits
47   if (num_info.sign_char != 0)
48     RET_IF_RESULT_NEGATIVE(writer->write(num_info.sign_char));
49   if (zeroes > 0)
50     RET_IF_RESULT_NEGATIVE(writer->write(num_info.padding_char, zeroes))
51   RET_IF_RESULT_NEGATIVE(writer->write(str));
52 
53   return WRITE_OK;
54 }
55 
get_int_format(const FormatSection & to_conv,const tm * timeptr)56 LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,
57                                             const tm *timeptr) {
58   const time_utils::TMReader time_reader(timeptr);
59 
60   intmax_t raw_num;
61 
62   IntFormatSection result = {0, 0, 0, '0'};
63 
64   // gets_plus_sign is only true for year conversions where the year would be
65   // positive and more than 4 digits, including leading spaces. Both the
66   // FORCE_SIGN flag and gets_plus_sign must be true for a plus sign to be
67   // output.
68   bool gets_plus_sign = false;
69 
70   switch (to_conv.conv_name) {
71   case 'C': // Century [00-99]
72     raw_num = time_reader.get_year() / 100;
73     gets_plus_sign = raw_num > 99 || to_conv.min_width > 2;
74     result.pad_to_len = 2;
75     break;
76   case 'd':                           // Day of the month [01-31]
77     raw_num = time_reader.get_mday(); // get_mday is 1 indexed
78     result.pad_to_len = 2;
79     break;
80   case 'e':                           // Day of the month [1-31]
81     raw_num = time_reader.get_mday(); // get_mday is 1 indexed
82     result.pad_to_len = 2;
83     result.padding_char = ' ';
84     break;
85   case 'g': // last 2 digits of ISO year [00-99]
86     raw_num = time_reader.get_iso_year() % 100;
87     result.pad_to_len = 2;
88     break;
89   case 'G': // ISO year
90     raw_num = time_reader.get_iso_year();
91     gets_plus_sign = raw_num > 9999 || to_conv.min_width > 4;
92     result.pad_to_len = 4;
93     break;
94   case 'H': // 24-hour format [00-23]
95     raw_num = time_reader.get_hour();
96     result.pad_to_len = 2;
97     break;
98   case 'I': // 12-hour format [01-12]
99     raw_num = ((time_reader.get_hour() + 11) % 12) + 1;
100     result.pad_to_len = 2;
101     break;
102   case 'j':                               // Day of the year [001-366]
103     raw_num = time_reader.get_yday() + 1; // get_yday is 0 indexed
104     result.pad_to_len = 3;
105     break;
106   case 'm':                              // Month of the year [01-12]
107     raw_num = time_reader.get_mon() + 1; // get_mon is 0 indexed
108     result.pad_to_len = 2;
109     break;
110   case 'M': // Minute of the hour [00-59]
111     raw_num = time_reader.get_min();
112     result.pad_to_len = 2;
113     break;
114   case 's': // Seconds since the epoch
115     raw_num = time_reader.get_epoch();
116     result.pad_to_len = 0;
117     break;
118   case 'S': // Second of the minute [00-60]
119     raw_num = time_reader.get_sec();
120     result.pad_to_len = 2;
121     break;
122   case 'u': // ISO day of the week ([1-7] starting Monday)
123     raw_num = time_reader.get_iso_wday() + 1;
124     // need to add 1 because get_iso_wday returns the weekday [0-6].
125     result.pad_to_len = 1;
126     break;
127   case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
128     // This doesn't actually end up using tm_year, despite the standard saying
129     // it's needed. The end of the current year doesn't really matter, so leap
130     // years aren't relevant. If this is wrong, please tell me what I'm missing.
131     raw_num = time_reader.get_week(time_constants::SUNDAY);
132     result.pad_to_len = 2;
133     break;
134   case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
135     // This does need to know the year, since it may affect what the week of the
136     // previous year it underflows to.
137     raw_num = time_reader.get_iso_week();
138     result.pad_to_len = 2;
139     break;
140   case 'w': // Day of week ([0-6] starting Sunday)
141     raw_num = time_reader.get_wday();
142     result.pad_to_len = 1;
143     break;
144   case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
145     raw_num = time_reader.get_week(time_constants::MONDAY);
146     result.pad_to_len = 2;
147     break;
148   case 'y': // Year of the Century [00-99]
149     raw_num = time_reader.get_year() % 100;
150     result.pad_to_len = 2;
151     break;
152   case 'Y': // Full year
153     raw_num = time_reader.get_year();
154     gets_plus_sign = raw_num > 9999 || to_conv.min_width > 4;
155     result.pad_to_len = 4;
156     break;
157   case 'z': // Timezone offset [+/-HHMM]
158     raw_num = time_reader.get_timezone_offset();
159     result.sign_char = '+'; // force the '+' sign iff raw_num is non-negative
160     result.pad_to_len = 5;  // 4 + 1 for the sign
161     break;
162   default:
163     __builtin_trap(); // this should be unreachable, but trap if you hit it.
164   }
165 
166   result.num = static_cast<uintmax_t>(raw_num < 0 ? -raw_num : raw_num);
167   const bool is_negative = raw_num < 0;
168 
169   // TODO: Handle locale modifiers
170 
171   if ((to_conv.flags & FormatFlags::LEADING_ZEROES) ==
172       FormatFlags::LEADING_ZEROES)
173     result.padding_char = '0';
174 
175   if (is_negative)
176     result.sign_char = '-';
177   else if ((to_conv.flags & FormatFlags::FORCE_SIGN) ==
178                FormatFlags::FORCE_SIGN &&
179            gets_plus_sign)
180     result.sign_char = '+';
181 
182   // sign isn't a problem because we're taking the max. The result is always
183   // non-negative. Also min_width can only be 0 if it's defaulted, since 0 is a
184   // flag.
185   if (to_conv.min_width > 0)
186     result.pad_to_len = to_conv.min_width;
187 
188   return result;
189 }
190 
191 template <printf_core::WriteMode write_mode>
convert_int(printf_core::Writer<write_mode> * writer,const FormatSection & to_conv,const tm * timeptr)192 LIBC_INLINE int convert_int(printf_core::Writer<write_mode> *writer,
193                             const FormatSection &to_conv, const tm *timeptr) {
194 
195   return write_padded_int(writer, get_int_format(to_conv, timeptr));
196 }
197 
198 } // namespace strftime_core
199 } // namespace LIBC_NAMESPACE_DECL
200 
201 #endif
202