2929// / database, eliminating the need for users to install IANA tzdata separately.
3030
3131#include < chrono>
32- #include < ctime>
33- #include < iomanip>
34- #include < sstream>
3532#include < string>
3633#include < string_view>
3734
5350
5451#if ARROW_USE_STD_CHRONO
5552// Use C++20 standard library chrono
53+ # include < format>
54+ # include < iterator>
55+ # include < ostream>
5656#else
5757// Use vendored Howard Hinnant date library
5858# include " arrow/vendored/datetime.h"
@@ -119,18 +119,16 @@ using std::chrono::ceil;
119119using std::chrono::floor;
120120using std::chrono::round;
121121
122- // trunc is not in std::chrono - implement proper truncation toward zero
123- // floor rounds toward negative infinity, but trunc rounds toward zero
122+ // trunc (truncation toward zero) is not in std::chrono, only floor/ceil/round
124123template <typename ToDuration, typename Rep, typename Period>
125124constexpr ToDuration trunc (const std::chrono::duration<Rep, Period>& d) {
126- auto floor_result = std::chrono::floor<ToDuration>(d);
127- auto remainder = d - floor_result;
128- // If original was negative and there's a non-zero remainder,
129- // floor went too far negative, so add one unit back
130- if (d.count () < 0 && remainder.count () != 0 ) {
131- return floor_result + ToDuration{1 };
125+ auto floored = std::chrono::floor<ToDuration>(d);
126+ // floor rounds toward -infinity; for negative values with remainder, add 1 to get
127+ // toward zero
128+ if (d.count () < 0 && (d - floored).count () != 0 ) {
129+ return floored + ToDuration{1 };
132130 }
133- return floor_result ;
131+ return floored ;
134132}
135133
136134// Timezone lookup
@@ -140,127 +138,22 @@ inline const time_zone* locate_zone(std::string_view tz_name) {
140138
141139inline const time_zone* current_zone () { return std::chrono::current_zone (); }
142140
143- // Helper to get subsecond decimal places based on duration period
144- template <typename Duration>
145- constexpr int get_subsecond_decimals () {
146- using Period = typename Duration::period;
147- if constexpr (Period::den == 1000 )
148- return 3 ; // milliseconds
149- else if constexpr (Period::den == 1000000 )
150- return 6 ; // microseconds
151- else if constexpr (Period::den == 1000000000 )
152- return 9 ; // nanoseconds
153- else
154- return 0 ; // seconds or coarser
155- }
156-
157- // Formatting support with subsecond precision and timezone handling
158- // Mimics the vendored date library's to_stream behavior for compatibility
141+ // Formatting support - streams directly using C++20 std::vformat_to
142+ // Provides: direct streaming, stream state preservation, chaining, rich format specifiers
159143template <typename CharT, typename Traits, typename Duration, typename TimeZonePtr>
160144std::basic_ostream<CharT, Traits>& to_stream (
161145 std::basic_ostream<CharT, Traits>& os, const CharT* fmt,
162146 const std::chrono::zoned_time<Duration, TimeZonePtr>& zt) {
163- // Get local time and timezone info
164- auto lt = zt.get_local_time ();
165- auto info = zt.get_info ();
166-
167- auto lt_days = std::chrono::floor<days>(lt);
168- auto ymd = year_month_day{lt_days};
169-
170- // Calculate time of day components
171- auto time_since_midnight = lt - local_time<days>{lt_days};
172- auto total_secs = std::chrono::duration_cast<std::chrono::seconds>(time_since_midnight);
173- auto h = std::chrono::duration_cast<std::chrono::hours>(time_since_midnight);
174- auto m = std::chrono::duration_cast<std::chrono::minutes>(time_since_midnight - h);
175- auto s = std::chrono::duration_cast<std::chrono::seconds>(time_since_midnight - h - m);
176-
177- // Build std::tm for strftime
178- std::tm tm{};
179- tm.tm_sec = static_cast <int >(s.count ());
180- tm.tm_min = static_cast <int >(m.count ());
181- tm.tm_hour = static_cast <int >(h.count ());
182- tm.tm_mday = static_cast <int >(static_cast <unsigned >(ymd.day ()));
183- tm.tm_mon = static_cast <int >(static_cast <unsigned >(ymd.month ())) - 1 ;
184- tm.tm_year = static_cast <int >(ymd.year ()) - 1900 ;
185-
186- auto wd = weekday{lt_days};
187- tm.tm_wday = static_cast <int >(wd.c_encoding ());
188-
189- auto year_start =
190- std::chrono::local_days{ymd.year () / std::chrono::January / std::chrono::day{1 }};
191- tm.tm_yday = static_cast <int >((lt_days - year_start).count ());
192- tm.tm_isdst = info.save != std::chrono::minutes{0 } ? 1 : 0 ;
193-
194- // Timezone offset calculation
195- auto offset_mins = std::chrono::duration_cast<std::chrono::minutes>(info.offset );
196- bool neg_offset = offset_mins.count () < 0 ;
197- auto abs_offset = neg_offset ? -offset_mins : offset_mins;
198- auto off_h = std::chrono::duration_cast<std::chrono::hours>(abs_offset);
199- auto off_m = abs_offset - off_h;
200-
201- // Calculate subsecond value
202- constexpr int decimals = get_subsecond_decimals<Duration>();
203- int64_t subsec_value = 0 ;
204- if constexpr (decimals > 0 ) {
205- auto subsec_duration = time_since_midnight - total_secs;
206- subsec_value = std::chrono::duration_cast<Duration>(subsec_duration).count ();
207- if (subsec_value < 0 ) subsec_value = -subsec_value;
208- }
209-
210- // Parse format string, handle %S, %z, %Z specially
211- std::string result;
212- for (const CharT* p = fmt; *p; ++p) {
213- if (*p == ' %' && *(p + 1 )) {
214- CharT spec = *(p + 1 );
215- if (spec == ' S' ) {
216- // %S with subsecond precision
217- result += (tm.tm_sec < 10 ? " 0" : " " ) + std::to_string (tm.tm_sec );
218- if constexpr (decimals > 0 ) {
219- std::ostringstream ss;
220- ss << ' .' << std::setfill (' 0' ) << std::setw (decimals) << subsec_value;
221- result += ss.str ();
222- }
223- ++p;
224- } else if (spec == ' z' ) {
225- // %z timezone offset
226- std::ostringstream ss;
227- ss << (neg_offset ? ' -' : ' +' ) << std::setfill (' 0' ) << std::setw (2 )
228- << off_h.count () << std::setfill (' 0' ) << std::setw (2 ) << off_m.count ();
229- result += ss.str ();
230- ++p;
231- } else if (spec == ' Z' ) {
232- // %Z timezone abbreviation
233- result += info.abbrev ;
234- ++p;
235- } else {
236- // Use strftime for other specifiers
237- char buf[64 ];
238- char small_fmt[3 ] = {' %' , static_cast <char >(spec), ' \0 ' };
239- if (std::strftime (buf, sizeof (buf), small_fmt, &tm) > 0 ) {
240- result += buf;
241- }
242- ++p;
243- }
244- } else {
245- result += static_cast <char >(*p);
246- }
247- }
248-
249- return os << result;
147+ std::vformat_to (std::ostreambuf_iterator<CharT>(os), std::string (" {:" ) + fmt + " }" ,
148+ std::make_format_args (zt));
149+ return os;
250150}
251151
152+ // Format a duration using strftime-like format specifiers
153+ // Converts "%H%M" style to C++20's "{:%H%M}" style and uses std::vformat
252154template <typename Duration>
253155std::string format (const char * fmt, const Duration& d) {
254- std::ostringstream ss;
255- auto total_minutes = std::chrono::duration_cast<std::chrono::minutes>(d).count ();
256- bool negative = total_minutes < 0 ;
257- if (negative) total_minutes = -total_minutes;
258- auto hours = total_minutes / 60 ;
259- auto mins = total_minutes % 60 ;
260- ss << (negative ? " -" : " +" );
261- ss << std::setfill (' 0' ) << std::setw (2 ) << hours;
262- ss << std::setfill (' 0' ) << std::setw (2 ) << mins;
263- return ss.str ();
156+ return std::vformat (std::string (" {:" ) + fmt + " }" , std::make_format_args (d));
264157}
265158
266159// Literals namespace
0 commit comments