9 #include "quill/bundled/fmt/format.h" 10 #include "quill/core/Attributes.h" 11 #include "quill/core/Common.h" 12 #include "quill/core/QuillError.h" 13 #include "quill/core/TimeUtilities.h" 53 QUILL_ATTRIBUTE_COLD
void init(std::string timestamp_format, Timezone timezone)
55 _timestamp_format = std::move(timestamp_format);
56 _time_zone = timezone;
58 if (_find_unescaped_modifier(_timestamp_format,
"%X") != std::string::npos)
60 QUILL_THROW(
QuillError(
"`%X` as format modifier is not currently supported in format: " + _timestamp_format));
64 _replace_unescaped_modifier(_timestamp_format,
"%r",
"%I:%M:%S %p");
65 _replace_unescaped_modifier(_timestamp_format,
"%R",
"%H:%M");
66 _replace_unescaped_modifier(_timestamp_format,
"%T",
"%H:%M:%S");
69 _populate_initial_parts(_timestamp_format);
73 QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string
const& format_timestamp(time_t timestamp)
78 if (timestamp < _cached_timestamp)
80 _fallback_formatted = _safe_strftime(_timestamp_format.data(), timestamp, _time_zone).data();
81 return _fallback_formatted;
87 if (timestamp >= _next_recalculation_timestamp)
90 _pre_formatted_ts.clear();
91 _cached_indexes.clear();
94 _populate_pre_formatted_string_and_cached_indexes(timestamp);
96 if (_time_zone == Timezone::LocalTime)
101 _next_recalculation_timestamp = _next_quarter_hour_timestamp(timestamp);
103 else if (_time_zone == Timezone::GmtTime)
107 _next_recalculation_timestamp = _next_noon_or_midnight_timestamp(timestamp);
111 if (_cached_indexes.empty())
114 return _pre_formatted_ts;
117 if (_cached_timestamp == timestamp)
123 return _pre_formatted_ts;
127 time_t
const timestamp_diff = timestamp - _cached_timestamp;
130 _cached_timestamp = timestamp;
132 if (QUILL_UNLIKELY(_cached_epoch_seconds_width != 0))
134 size_t const epoch_seconds_width = _decimal_width(_cached_timestamp);
135 if (epoch_seconds_width != _cached_epoch_seconds_width)
137 _next_recalculation_timestamp = 0;
138 return format_timestamp(timestamp);
145 _cached_seconds +=
static_cast<uint32_t
>(timestamp_diff);
147 if (QUILL_UNLIKELY(_cached_seconds >= 86400))
154 _next_recalculation_timestamp = 0;
155 return format_timestamp(timestamp);
158 uint32_t total_seconds = _cached_seconds;
159 uint32_t
const hours = total_seconds / 3600;
160 total_seconds -= hours * 3600;
161 uint32_t
const minutes = total_seconds / 60;
162 total_seconds -= minutes * 60;
163 uint32_t
const seconds = total_seconds;
166 bool const hours_changed = (hours != _prev_hours);
167 bool const minutes_changed = (minutes != _prev_minutes);
168 bool const seconds_changed = (seconds != _prev_seconds);
172 _prev_minutes = minutes;
173 _prev_seconds = seconds;
175 for (
auto const& index : _cached_indexes)
178 switch (index.second)
183 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{:02}", hours);
189 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{:02}", minutes);
195 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{:02}", seconds);
201 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{:02}",
202 (hours == 0 ? 12 : (hours > 12 ? hours - 12 : hours)));
208 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{:2}",
209 (hours == 0 ? 12 : (hours > 12 ? hours - 12 : hours)));
215 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{:2}", hours);
219 fmtquill::format_to(&_pre_formatted_ts[index.first],
"{}", _cached_timestamp);
226 return _pre_formatted_ts;
230 QUILL_NODISCARD
static size_t _find_unescaped_modifier(std::string
const& timestamp_format,
231 std::string
const& modifier) noexcept
233 size_t search_pos = 0;
234 while ((search_pos = timestamp_format.find(modifier, search_pos)) != std::string::npos)
236 if (!_is_escaped_percent(timestamp_format, search_pos))
244 return std::string::npos;
248 QUILL_NODISCARD
static bool _is_escaped_percent(std::string
const& timestamp_format,
size_t percent_pos) noexcept
250 size_t preceding_percent_count = 0;
251 while ((percent_pos > preceding_percent_count) &&
252 (timestamp_format[percent_pos - preceding_percent_count - 1] ==
'%'))
254 ++preceding_percent_count;
257 return (preceding_percent_count % 2u) != 0u;
261 enum class format_type : uint8_t
273 QUILL_ATTRIBUTE_COLD
void _populate_initial_parts(std::string timestamp_format)
279 auto const [part1, part2] = _split_timestamp_format_once(timestamp_format);
283 _initial_parts.push_back(part1);
288 _initial_parts.push_back(part2);
291 if (part1.empty() && part2.empty())
296 if (!timestamp_format.empty())
298 _initial_parts.push_back(timestamp_format);
306 void _populate_pre_formatted_string_and_cached_indexes(time_t timestamp)
308 _cached_timestamp = timestamp;
312 if (_time_zone == Timezone::LocalTime)
314 localtime_rs(reinterpret_cast<time_t const*>(std::addressof(_cached_timestamp)), std::addressof(time_info));
316 else if (_time_zone == Timezone::GmtTime)
318 gmtime_rs(reinterpret_cast<time_t const*>(std::addressof(_cached_timestamp)), std::addressof(time_info));
323 static_cast<uint32_t
>((time_info.tm_hour * 3600) + (time_info.tm_min * 60) + time_info.tm_sec);
326 uint32_t total_seconds = _cached_seconds;
327 _prev_hours = total_seconds / 3600;
328 total_seconds -= _prev_hours * 3600;
329 _prev_minutes = total_seconds / 60;
330 _prev_seconds = total_seconds - _prev_minutes * 60;
333 for (
auto const& format_part : _initial_parts)
336 std::vector<char>
const formatted_part = _safe_strftime(format_part.data(), _cached_timestamp, _time_zone);
337 std::string_view
const formatted_part_sv{formatted_part.data()};
338 size_t const formatted_part_size = formatted_part_sv.size();
339 _pre_formatted_ts.append(formatted_part_sv);
343 if (format_part ==
"%H")
345 _cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::H);
347 else if (format_part ==
"%M")
349 _cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::M);
351 else if (format_part ==
"%S")
353 _cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::S);
355 else if (format_part ==
"%I")
357 _cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::I);
359 else if (format_part ==
"%k")
361 _cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::k);
363 else if (format_part ==
"%l")
365 _cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::l);
367 else if (format_part ==
"%s")
369 _cached_indexes.emplace_back(_pre_formatted_ts.size() - formatted_part_size, format_type::s);
370 _cached_epoch_seconds_width = formatted_part_size;
376 std::pair<std::string, std::string>
static _split_timestamp_format_once(std::string& timestamp_format)
379 std::array<std::string, 7>
const modifiers{
"%H",
"%M",
"%S",
"%I",
"%k",
"%l",
"%s"};
385 std::map<size_t, std::string> found_format_modifiers;
387 for (
auto const& modifier : modifiers)
389 if (
auto const search = _find_unescaped_modifier(timestamp_format, modifier); search != std::string::npos)
392 found_format_modifiers.emplace(search, modifier);
396 if (found_format_modifiers.empty())
400 return std::make_pair(std::string{}, std::string{});
408 std::string
const part_1 = found_format_modifiers.begin()->first > 0
409 ? std::string{timestamp_format.data(), found_format_modifiers.begin()->first}
413 std::string
const part_2 = found_format_modifiers.begin()->second;
417 timestamp_format = std::string{timestamp_format.data() + found_format_modifiers.begin()->first + 2};
419 return std::make_pair(part_1, part_2);
423 QUILL_NODISCARD
static std::vector<char> _safe_strftime(
char const* format_string, time_t timestamp, Timezone timezone)
425 if (format_string[0] ==
'\0')
427 std::vector<char> res;
434 if (timezone == Timezone::LocalTime)
436 localtime_rs(reinterpret_cast<time_t const*>(std::addressof(timestamp)), std::addressof(time_info));
438 else if (timezone == Timezone::GmtTime)
440 gmtime_rs(reinterpret_cast<time_t const*>(std::addressof(timestamp)), std::addressof(time_info));
444 static constexpr
size_t max_buffer_size{64 * 1024};
447 size_t res = strftime(&buffer[0], buffer.size(), format_string, std::addressof(time_info));
451 if (QUILL_UNLIKELY(buffer.size() >= max_buffer_size))
454 QuillError{
"strftime failed to format timestamp. The timestamp pattern may " 455 "contain an unsupported format specifier."});
459 buffer.resize(buffer.size() * 2);
460 res = strftime(&buffer[0], buffer.size(), format_string, std::addressof(time_info));
467 static void _replace_unescaped_modifier(std::string& str, std::string
const& old_value, std::string
const& new_value)
469 std::string::size_type pos = 0u;
470 while ((pos = str.find(old_value, pos)) != std::string::npos)
472 if (_is_escaped_percent(str, pos))
478 str.replace(pos, old_value.length(), new_value);
479 pos += new_value.length();
484 QUILL_NODISCARD
static time_t _nearest_quarter_hour_timestamp(time_t timestamp) noexcept
486 time_t
const nearest_quarter_hour_ts = (timestamp / 900) * 900;
487 return nearest_quarter_hour_ts;
491 QUILL_NODISCARD
static time_t _next_quarter_hour_timestamp(time_t timestamp) noexcept
493 time_t
const next_quarter_hour_ts = _nearest_quarter_hour_timestamp(timestamp) + 900;
494 return next_quarter_hour_ts;
498 QUILL_NODISCARD
static time_t _next_noon_or_midnight_timestamp(time_t timestamp) noexcept
504 if (time_info.tm_hour < 12)
507 time_info.tm_hour = 11;
508 time_info.tm_min = 59;
509 time_info.tm_sec = 59;
514 time_info.tm_hour = 23;
515 time_info.tm_min = 59;
516 time_info.tm_sec = 59;
520 std::chrono::system_clock::time_point
const next_midnight =
521 std::chrono::system_clock::from_time_t(
detail::timegm(&time_info));
524 return std::chrono::duration_cast<std::chrono::seconds>(next_midnight.time_since_epoch()).count() + 1;
528 QUILL_NODISCARD
static size_t _decimal_width(time_t timestamp) noexcept
532 using unsigned_time_t = std::make_unsigned_t<time_t>;
533 unsigned_time_t magnitude = 0;
535 if constexpr (std::is_signed_v<time_t>)
540 magnitude =
static_cast<unsigned_time_t
>(-(timestamp + 1)) + 1;
544 magnitude =
static_cast<unsigned_time_t
>(timestamp);
549 magnitude = timestamp;
556 }
while (magnitude != 0);
564 std::vector<std::string> _initial_parts;
567 std::vector<std::pair<size_t, format_type>> _cached_indexes;
570 std::string _timestamp_format;
573 std::string _pre_formatted_ts;
576 std::string _fallback_formatted;
579 time_t _next_recalculation_timestamp{0};
582 time_t _cached_timestamp{0};
585 uint32_t _cached_seconds{0};
588 uint32_t _prev_hours{0};
589 uint32_t _prev_minutes{0};
590 uint32_t _prev_seconds{0};
593 Timezone _time_zone{Timezone::GmtTime};
594 size_t _cached_epoch_seconds_width{0};
tm * localtime_rs(time_t const *timer, tm *buf)
Portable localtime_r or _s per operating system.
Definition: TimeUtilities.h:56
A class that converts a timestamp to a string based on the given format.
Definition: StringFromTime.h:49
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
tm * gmtime_rs(time_t const *timer, tm *buf)
Portable gmtime_r or _s per operating system.
Definition: TimeUtilities.h:28
time_t timegm(tm *tm)
inverses of gmtime
Definition: TimeUtilities.h:82
custom exception
Definition: QuillError.h:47
A contiguous memory buffer with an optional growing ability.
Definition: base.h:1779