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 = _time_format.find(specifier_name[AdditionalSpecifier::Qms]);
64  search_qms != std::string::npos)
65  {
66  _additional_format_specifier = AdditionalSpecifier::Qms;
67  specifier_begin = search_qms;
68  }
69 
70  if (size_t const search_qus = _time_format.find(specifier_name[AdditionalSpecifier::Qus]);
71  search_qus != std::string::npos)
72  {
73  if (specifier_begin != std::string::npos)
74  {
75  QUILL_THROW(QuillError{"format specifiers %Qms, %Qus and %Qns are mutually exclusive"});
76  }
77 
78  _additional_format_specifier = AdditionalSpecifier::Qus;
79  specifier_begin = search_qus;
80  }
81 
82  if (size_t const search_qns = _time_format.find(specifier_name[AdditionalSpecifier::Qns]);
83  search_qns != std::string::npos)
84  {
85  if (specifier_begin != std::string::npos)
86  {
87  QUILL_THROW(QuillError{"format specifiers %Qms, %Qus and %Qns are mutually exclusive"});
88  }
89 
90  _additional_format_specifier = AdditionalSpecifier::Qns;
91  specifier_begin = search_qns;
92  }
93 
94  if (specifier_begin == std::string::npos)
95  {
96  // If no additional specifier was found then we can simply store the whole format string
97  QUILL_ASSERT(_additional_format_specifier == AdditionalSpecifier::None,
98  "Unexpected specifier state in TimestampFormatter constructor, should be None "
99  "when no specifier found");
100  _strftime_part_1.init(_time_format, _timestamp_timezone);
101  }
102  else
103  {
104  // We now the index where the specifier begins so copy everything until there from beginning
105  std::string const format_part_1 = _time_format.substr(0, specifier_begin);
106  _strftime_part_1.init(format_part_1, _timestamp_timezone);
107 
108  // Now copy the remaining format string, ignoring the specifier
109  size_t const specifier_end = specifier_begin + specifier_length;
110  std::string const format_part_2 =
111  _time_format.substr(specifier_end, _time_format.length() - specifier_end);
112 
113  if (!format_part_2.empty())
114  {
115  _strftime_part_2.init(format_part_2, _timestamp_timezone);
116  _has_format_part_2 = true;
117  }
118  }
119  }
120 
121  /***/
122  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format_timestamp(std::chrono::nanoseconds time_since_epoch)
123  {
124  int64_t const timestamp_ns = time_since_epoch.count();
125 
126  // convert timestamp to seconds
127  int64_t const timestamp_secs = timestamp_ns / 1'000'000'000;
128 
129  // First always clear our cached string
130  _formatted_date.clear();
131 
132  // 1. we always format part 1
133  _formatted_date.append(_strftime_part_1.format_timestamp(timestamp_secs));
134 
135  // 2. We add any special ms/us/ns specifier if any
136  auto const extracted_ns = static_cast<uint32_t>(timestamp_ns - (timestamp_secs * 1'000'000'000));
137 
138  if (_additional_format_specifier == AdditionalSpecifier::Qms)
139  {
140  uint32_t const extracted_ms = extracted_ns / 1'000'000;
141 
142  // Add as many zeros as the extracted_fractional_seconds_length
143  static constexpr std::string_view zeros{"000"};
144  _formatted_date.append(zeros);
145 
146  _write_fractional_seconds(extracted_ms);
147  }
148  else if (_additional_format_specifier == AdditionalSpecifier::Qus)
149  {
150  uint32_t const extracted_us = extracted_ns / 1'000;
151 
152  // Add as many zeros as the extracted_fractional_seconds_length
153  static constexpr std::string_view zeros{"000000"};
154  _formatted_date.append(zeros);
155 
156  _write_fractional_seconds(extracted_us);
157  }
158  else if (_additional_format_specifier == AdditionalSpecifier::Qns)
159  {
160  // Add as many zeros as the extracted_fractional_seconds_length
161  static constexpr std::string_view zeros{"000000000"};
162  _formatted_date.append(zeros);
163 
164  _write_fractional_seconds(extracted_ns);
165  }
166 
167  // 3. format part 2 after fractional seconds - if any
168  if (_has_format_part_2)
169  {
170  _formatted_date.append(_strftime_part_2.format_timestamp(timestamp_secs));
171  }
172 
173  return std::string_view{_formatted_date.data(), _formatted_date.size()};
174  }
175 
176  /***/
177  QUILL_NODISCARD std::string const& time_format() const noexcept { return _time_format; }
178 
179  /***/
180  QUILL_NODISCARD Timezone timestamp_timezone() const noexcept { return _timestamp_timezone; }
181 
182 private:
183  /***/
184  void _write_fractional_seconds(uint32_t extracted_fractional_seconds)
185  {
186  // Format the seconds and add them
187  fmtquill::format_int const extracted_ms_string{extracted_fractional_seconds};
188 
189  // _formatted_date.size() - extracted_ms_string.size() is where we want to begin placing the fractional seconds
190  std::memcpy(&_formatted_date[_formatted_date.size() - extracted_ms_string.size()],
191  extracted_ms_string.data(), extracted_ms_string.size());
192  }
193 
194 private:
196  static constexpr std::array<char const*, 4> specifier_name{"", "%Qms", "%Qus", "%Qns"};
197 
199  static constexpr size_t specifier_length = 4u;
200 
201  std::string _time_format;
202  fmtquill::basic_memory_buffer<char, 32> _formatted_date;
203 
205  StringFromTime _strftime_part_1;
206  StringFromTime _strftime_part_2;
207 
209  Timezone _timestamp_timezone;
210 
212  AdditionalSpecifier _additional_format_specifier{AdditionalSpecifier::None};
213  bool _has_format_part_2{false};
214 };
215 
216 } // namespace detail
217 
218 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:24
custom exception
Definition: QuillError.h:45