|
38 | 38 | #if ARROW_USE_STD_CHRONO |
39 | 39 | // Use C++20 standard library chrono |
40 | 40 | # include <format> |
41 | | -# include <iterator> |
| 41 | +# include <locale> |
42 | 42 | # include <ostream> |
| 43 | +# include <ratio> |
| 44 | +# include <type_traits> |
43 | 45 | #else |
44 | 46 | // Use vendored Howard Hinnant date library |
45 | 47 | # include "arrow/vendored/datetime.h" |
@@ -125,22 +127,172 @@ inline const time_zone* locate_zone(std::string_view tz_name) { |
125 | 127 |
|
126 | 128 | inline const time_zone* current_zone() { return std::chrono::current_zone(); } |
127 | 129 |
|
128 | | -// Formatting support - streams directly using C++20 std::vformat_to |
129 | | -// Provides: direct streaming, stream state preservation, chaining, rich format specifiers |
| 130 | +namespace detail { |
| 131 | + |
| 132 | +// Argument positions passed to std::vformat by to_stream below. |
| 133 | +enum class FormatArgument : char { |
| 134 | + ZonedTime = '0', |
| 135 | + TimeOfDay = '1', |
| 136 | + TimeOfDayCount = '2', |
| 137 | +}; |
| 138 | + |
| 139 | +template <typename CharT> |
| 140 | +void AppendEscapedLiteral(std::basic_string<CharT>* out, CharT value) { |
| 141 | + out->push_back(value); |
| 142 | + if (value == CharT{'{'} || value == CharT{'}'}) { |
| 143 | + out->push_back(value); |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +// These are the directives accepted by Arrow's existing strftime syntax. Treat |
| 148 | +// all others as literals to preserve compatibility. |
| 149 | +template <typename CharT> |
| 150 | +bool IsSupportedStrftimeSpecifier(CharT modifier, CharT specifier) { |
| 151 | + const auto contains = [specifier](const char* candidates) { |
| 152 | + for (; *candidates != '\0'; ++candidates) { |
| 153 | + if (specifier == static_cast<CharT>(*candidates)) return true; |
| 154 | + } |
| 155 | + return false; |
| 156 | + }; |
| 157 | + if (modifier == CharT{}) { |
| 158 | + return contains("aAbBhcCxdeDFgGHIjmMprRSTuUVWwXyYzZ"); |
| 159 | + } |
| 160 | + if (modifier == CharT{'E'}) { |
| 161 | + return contains("cCxXyYz"); |
| 162 | + } |
| 163 | + if (modifier == CharT{'O'}) { |
| 164 | + return contains("deHImMSuUVwWyz"); |
| 165 | + } |
| 166 | + return false; |
| 167 | +} |
| 168 | + |
| 169 | +template <typename CharT> |
| 170 | +void AppendChronoField(std::basic_string<CharT>* out, FormatArgument argument, |
| 171 | + CharT specifier, CharT modifier = CharT{}) { |
| 172 | + *out += {CharT{'{'}, static_cast<CharT>(argument), CharT{':'}, CharT{'L'}, CharT{'%'}}; |
| 173 | + if (modifier != CharT{}) out->push_back(modifier); |
| 174 | + *out += {specifier, CharT{'}'}}; |
| 175 | +} |
| 176 | + |
| 177 | +template <typename CharT> |
| 178 | +void AppendLocalizedField(std::basic_string<CharT>* out, FormatArgument argument) { |
| 179 | + *out += {CharT{'{'}, static_cast<CharT>(argument), CharT{':'}, CharT{'L'}, CharT{'}'}}; |
| 180 | +} |
| 181 | + |
| 182 | +template <typename CharT> |
| 183 | +std::basic_string<CharT> ToChronoFormat(const CharT* fmt, bool use_microseconds_suffix) { |
| 184 | + std::basic_string<CharT> out; |
| 185 | + while (*fmt != CharT{}) { |
| 186 | + if (*fmt != CharT{'%'}) { |
| 187 | + AppendEscapedLiteral(&out, *fmt++); |
| 188 | + continue; |
| 189 | + } |
| 190 | + |
| 191 | + ++fmt; |
| 192 | + if (*fmt == CharT{}) { |
| 193 | + AppendEscapedLiteral(&out, CharT{'%'}); |
| 194 | + break; |
| 195 | + } |
| 196 | + |
| 197 | + CharT modifier{}; |
| 198 | + if (*fmt == CharT{'E'} || *fmt == CharT{'O'}) { |
| 199 | + modifier = *fmt++; |
| 200 | + if (*fmt == CharT{}) { |
| 201 | + AppendEscapedLiteral(&out, CharT{'%'}); |
| 202 | + AppendEscapedLiteral(&out, modifier); |
| 203 | + break; |
| 204 | + } |
| 205 | + } |
| 206 | + const CharT specifier = *fmt++; |
| 207 | + |
| 208 | + if (modifier == CharT{}) { |
| 209 | + switch (specifier) { |
| 210 | + case CharT{'%'}: |
| 211 | + AppendEscapedLiteral(&out, CharT{'%'}); |
| 212 | + continue; |
| 213 | + case CharT{'n'}: |
| 214 | + AppendEscapedLiteral(&out, CharT{'\n'}); |
| 215 | + continue; |
| 216 | + case CharT{'t'}: |
| 217 | + AppendEscapedLiteral(&out, CharT{'\t'}); |
| 218 | + continue; |
| 219 | + case CharT{'Q'}: |
| 220 | + // Formatting a duration's %Q does not consistently apply the numeric locale. |
| 221 | + AppendLocalizedField(&out, FormatArgument::TimeOfDayCount); |
| 222 | + continue; |
| 223 | + case CharT{'q'}: |
| 224 | + if (use_microseconds_suffix) { |
| 225 | + // Some standard libraries use "us"; Arrow uses the micro sign. |
| 226 | + if constexpr (std::is_same_v<CharT, char>) { |
| 227 | + AppendEscapedLiteral(&out, CharT{'\xC2'}); |
| 228 | + AppendEscapedLiteral(&out, CharT{'\xB5'}); |
| 229 | + } else { |
| 230 | + AppendEscapedLiteral(&out, static_cast<CharT>(0xB5)); |
| 231 | + } |
| 232 | + AppendEscapedLiteral(&out, CharT{'s'}); |
| 233 | + } else { |
| 234 | + AppendChronoField(&out, FormatArgument::TimeOfDay, specifier); |
| 235 | + } |
| 236 | + continue; |
| 237 | + default: |
| 238 | + break; |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | +# if defined(__GLIBCXX__) |
| 243 | + if (modifier == CharT{'O'} && specifier == CharT{'V'}) { |
| 244 | + // libstdc++ does not yet accept %OV; use its equivalent base representation. |
| 245 | + AppendChronoField(&out, FormatArgument::ZonedTime, specifier); |
| 246 | + continue; |
| 247 | + } |
| 248 | +# endif |
| 249 | + |
| 250 | + if (IsSupportedStrftimeSpecifier(modifier, specifier)) { |
| 251 | + AppendChronoField(&out, FormatArgument::ZonedTime, specifier, modifier); |
| 252 | + } else { |
| 253 | + AppendEscapedLiteral(&out, CharT{'%'}); |
| 254 | + if (modifier != CharT{}) AppendEscapedLiteral(&out, modifier); |
| 255 | + AppendEscapedLiteral(&out, specifier); |
| 256 | + } |
| 257 | + } |
| 258 | + return out; |
| 259 | +} |
| 260 | + |
| 261 | +} // namespace detail |
| 262 | + |
| 263 | +// Convert Arrow's strftime syntax to C++20 replacement fields. Literal braces and |
| 264 | +// unsupported directives remain literal, and %Q/%q use local time of day. |
130 | 265 | template <typename CharT, typename Traits, typename Duration, typename TimeZonePtr> |
131 | 266 | std::basic_ostream<CharT, Traits>& to_stream( |
132 | 267 | std::basic_ostream<CharT, Traits>& os, const CharT* fmt, |
133 | 268 | const std::chrono::zoned_time<Duration, TimeZonePtr>& zt) { |
134 | | - std::vformat_to(std::ostreambuf_iterator<CharT>(os), std::string("{:") + fmt + "}", |
135 | | - std::make_format_args(zt)); |
| 269 | + static_assert(std::is_same_v<CharT, char> || std::is_same_v<CharT, wchar_t>); |
| 270 | + using Precision = typename std::chrono::zoned_time<Duration, TimeZonePtr>::duration; |
| 271 | + const auto standard_format = detail::ToChronoFormat( |
| 272 | + fmt, std::ratio_equal_v<typename Precision::period, std::micro>); |
| 273 | + const auto local_time = zt.get_local_time(); |
| 274 | + const auto local_day = std::chrono::floor<std::chrono::days>(local_time); |
| 275 | + const auto time_of_day = local_time - local_day; |
| 276 | + const auto time_of_day_count = time_of_day.count(); |
| 277 | + |
| 278 | + std::basic_string<CharT> formatted; |
| 279 | + if constexpr (std::is_same_v<CharT, char>) { |
| 280 | + formatted = std::vformat(os.getloc(), standard_format, |
| 281 | + std::make_format_args(zt, time_of_day, time_of_day_count)); |
| 282 | + } else { |
| 283 | + formatted = std::vformat(os.getloc(), standard_format, |
| 284 | + std::make_wformat_args(zt, time_of_day, time_of_day_count)); |
| 285 | + } |
| 286 | + os.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); |
136 | 287 | return os; |
137 | 288 | } |
138 | 289 |
|
139 | | -// Format a duration using strftime-like format specifiers |
140 | | -// Converts "%H%M" style to C++20's "{:%H%M}" style and uses std::vformat |
141 | | -template <typename Duration> |
142 | | -std::string format(const char* fmt, const Duration& d) { |
143 | | - return std::vformat(std::string("{:") + fmt + "}", std::make_format_args(d)); |
| 290 | +// Format a duration or time point using strftime-like format specifiers. |
| 291 | +// Converts "%H%M" style to C++20's "{:L%H%M}" style and uses std::vformat. |
| 292 | +template <typename Temporal> |
| 293 | +std::string format(const char* fmt, const Temporal& value) { |
| 294 | + return std::vformat(std::locale{}, std::string("{:L") + fmt + "}", |
| 295 | + std::make_format_args(value)); |
144 | 296 | } |
145 | 297 |
|
146 | 298 | inline constexpr std::chrono::month jan = std::chrono::January; |
|
0 commit comments