quill
TimestampFormatter.h
1 
7 #pragma once
8 
9 #include "quill/backend/StringFromTime.h"
10 #include "quill/bundled/fmt/format.h"
11 #include "quill/core/Attributes.h"
12 #include "quill/core/Common.h"
13 #include "quill/core/QuillError.h"
14 
15 #include <array>
16 #include <chrono>
17 #include <cstddef>
18 #include <cstdint>
19 #include <cstring>
20 #include <string>
21 #include <string_view>
22 #include <utility>
23 
24 QUILL_BEGIN_NAMESPACE
25 
26 namespace detail
27 {
28 
39 {
40 private:
41  enum AdditionalSpecifier : uint8_t
42  {
43  None,
44  Qms,
45  Qus,
46  Qns
47  };
48 
49 public:
50  /***/
51  explicit TimestampFormatter(std::string time_format, Timezone timestamp_timezone = Timezone::LocalTime)
52  : _time_format(std::move(time_format)), _timestamp_timezone(timestamp_timezone)
53  {
54  QUILL_ASSERT(
55  _timestamp_timezone == Timezone::LocalTime || _timestamp_timezone == Timezone::GmtTime,
56  "Invalid timezone type in TimestampFormatter constructor, must be LocalTime or GmtTime");
57 
58  // store the beginning of the found specifier
59  size_t specifier_begin{std::string::npos};
60 
61  // look for all three special specifiers
62 
63  if (size_t const search_qms = StringFromTime::_find_unescaped_modifier(
64  _time_format, specifier_name[AdditionalSpecifier::Qms]);
65  search_qms != std::string::npos)
66  {
67  _additional_format_specifier = AdditionalSpecifier::Qms;
68  specifier_begin = search_qms;
69  }
70 
71  if (size_t const search_qus = StringFromTime::_find_unescaped_modifier(
72  _time_format, specifier_name[AdditionalSpecifier::Qus]);
73  search_qus != std::string::npos)
74  {
75  if (specifier_begin != std::string::npos)
76  {
77  QUILL_THROW(QuillError{"format specifiers %Qms, %Qus and %Qns are mutually exclusive"});
78  }
79 
80  _additional_format_specifier = AdditionalSpecifier::Qus;
81  specifier_begin = search_qus;
82  }
83 
84  if (size_t const search_qns = StringFromTime::_find_unescaped_modifier(
85  _time_format, specifier_name[AdditionalSpecifier::Qns]);
86  search_qns != std::string::npos)
87  {
88  if (specifier_begin != std::string::npos)
89  {
90  QUILL_THROW(QuillError{"format specifiers %Qms, %Qus and %Qns are mutually exclusive"});
91  }
92 
93  _additional_format_specifier = AdditionalSpecifier::Qns;
94  specifier_begin = search_qns;
95  }
96 
97  if (specifier_begin == std::string::npos)
98  {
99  // If no additional specifier was found then we can simply store the whole format string
100  QUILL_ASSERT(_additional_format_specifier == AdditionalSpecifier::None,
101  "Unexpected specifier state in TimestampFormatter constructor, should be None "
102  "when no specifier found");
103  _strftime_part_1.init(_time_format, _timestamp_timezone);
104  }
105  else
106  {
107  // We now the index where the specifier begins so copy everything until there from beginning
108  std::string const format_part_1 = _time_format.substr(0, specifier_begin);
109  _strftime_part_1.init(format_part_1, _timestamp_timezone);
110 
111  // Now copy the remaining format string, ignoring the specifier
112  size_t const specifier_end = specifier_begin + specifier_length;
113  std::string const format_part_2 =
114  _time_format.substr(specifier_end, _time_format.length() - specifier_end);
115 
116  if (!format_part_2.empty())
117  {
118  _strftime_part_2.init(format_part_2, _timestamp_timezone);
119  _has_format_part_2 = true;
120  }
121  }
122  }
123 
124  /***/
125  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format_timestamp(std::chrono::nanoseconds time_since_epoch)
126  {
127  int64_t const timestamp_ns = time_since_epoch.count();
128 
129  // convert timestamp to seconds
130  int64_t const timestamp_secs = timestamp_ns / 1'000'000'000;
131 
132  // First always clear our cached string
133  _formatted_date.clear();
134 
135  // 1. we always format part 1
136  _formatted_date.append(_strftime_part_1.format_timestamp(timestamp_secs));
137 
138  // 2. We add any special ms/us/ns specifier if any
139  // Note: assumes timestamp_ns >= 0 (post-epoch). Pre-epoch timestamps are not supported.
140  auto const extracted_ns = static_cast<uint32_t>(timestamp_ns - (timestamp_secs * 1'000'000'000));
141 
142  if (_additional_format_specifier == AdditionalSpecifier::Qms)
143  {
144  uint32_t const extracted_ms = extracted_ns / 1'000'000;
145 
146  // Add as many zeros as the extracted_fractional_seconds_length
147  static constexpr std::string_view zeros{"000"};
148  _formatted_date.append(zeros);
149 
150  _write_fractional_seconds(extracted_ms);
151  }
152  else if (_additional_format_specifier == AdditionalSpecifier::Qus)
153  {
154  uint32_t const extracted_us = extracted_ns / 1'000;
155 
156  // Add as many zeros as the extracted_fractional_seconds_length
157  static constexpr std::string_view zeros{"000000"};
158  _formatted_date.append(zeros);
159 
160  _write_fractional_seconds(extracted_us);
161  }
162  else if (_additional_format_specifier == AdditionalSpecifier::Qns)
163  {
164  // Add as many zeros as the extracted_fractional_seconds_length
165  static constexpr std::string_view zeros{"000000000"};
166  _formatted_date.append(zeros);
167 
168  _write_fractional_seconds(extracted_ns);
169  }
170 
171  // 3. format part 2 after fractional seconds - if any
172  if (_has_format_part_2)
173  {
174  _formatted_date.append(_strftime_part_2.format_timestamp(timestamp_secs));
175  }
176 
177  return std::string_view{_formatted_date.data(), _formatted_date.size()};
178  }
179 
180  /***/
181  QUILL_NODISCARD std::string const& time_format() const noexcept { return _time_format; }
182 
183  /***/
184  QUILL_NODISCARD Timezone timestamp_timezone() const noexcept { return _timestamp_timezone; }
185 
186 private:
187  /***/
188  void _write_fractional_seconds(uint32_t extracted_fractional_seconds)
189  {
190  uint32_t digits = 0;
191  switch (_additional_format_specifier)
192  {
193  case AdditionalSpecifier::Qms:
194  digits = 3;
195  break;
196  case AdditionalSpecifier::Qus:
197  digits = 6;
198  break;
199  case AdditionalSpecifier::Qns:
200  digits = 9;
201  break;
202  default:
203  return;
204  }
205 
206  char* write_pos = _formatted_date.data() + _formatted_date.size();
207  for (uint32_t i = 0; i < digits; ++i)
208  {
209  *--write_pos = static_cast<char>('0' + (extracted_fractional_seconds % 10));
210  extracted_fractional_seconds /= 10;
211  }
212  }
213 
214 private:
216  static constexpr std::array<char const*, 4> specifier_name{"", "%Qms", "%Qus", "%Qns"};
217 
219  static constexpr size_t specifier_length = 4u;
220 
221  std::string _time_format;
222  fmtquill::basic_memory_buffer<char, 32> _formatted_date;
223 
225  StringFromTime _strftime_part_1;
226  StringFromTime _strftime_part_2;
227 
229  Timezone _timestamp_timezone;
230 
232  AdditionalSpecifier _additional_format_specifier{AdditionalSpecifier::None};
233  bool _has_format_part_2{false};
234 };
235 
236 } // namespace detail
237 
238 QUILL_END_NAMESPACE
Formats a timestamp given a format string as a pattern.
Definition: TimestampFormatter.h:38
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
custom exception
Definition: QuillError.h:47