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 <cassert>
17 #include <chrono>
18 #include <cstddef>
19 #include <cstdint>
20 #include <cstring>
21 #include <string>
22 #include <string_view>
23 #include <utility>
24 
25 QUILL_BEGIN_NAMESPACE
26 
27 namespace detail
28 {
29 
40 {
41 private:
42  enum AdditionalSpecifier : uint8_t
43  {
44  None,
45  Qms,
46  Qus,
47  Qns
48  };
49 
50 public:
51  /***/
52  explicit TimestampFormatter(std::string time_format, Timezone timestamp_timezone = Timezone::LocalTime)
53  : _time_format(std::move(time_format)), _timestamp_timezone(timestamp_timezone)
54  {
55  assert((_timestamp_timezone == Timezone::LocalTime || _timestamp_timezone == Timezone::GmtTime) &&
56  "Invalid timezone type");
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  assert(_additional_format_specifier == AdditionalSpecifier::None);
98  _strftime_part_1.init(_time_format, _timestamp_timezone);
99  }
100  else
101  {
102  // We now the index where the specifier begins so copy everything until there from beginning
103  std::string const format_part_1 = _time_format.substr(0, specifier_begin);
104  _strftime_part_1.init(format_part_1, _timestamp_timezone);
105 
106  // Now copy the remaining format string, ignoring the specifier
107  size_t const specifier_end = specifier_begin + specifier_length;
108  std::string const format_part_2 =
109  _time_format.substr(specifier_end, _time_format.length() - specifier_end);
110 
111  if (!format_part_2.empty())
112  {
113  _strftime_part_2.init(format_part_2, _timestamp_timezone);
114  _has_format_part_2 = true;
115  }
116  }
117  }
118 
119  /***/
120  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format_timestamp(std::chrono::nanoseconds time_since_epoch)
121  {
122  int64_t const timestamp_ns = time_since_epoch.count();
123 
124  // convert timestamp to seconds
125  int64_t const timestamp_secs = timestamp_ns / 1'000'000'000;
126 
127  // First always clear our cached string
128  _formatted_date.clear();
129 
130  // 1. we always format part 1
131  _formatted_date.append(_strftime_part_1.format_timestamp(timestamp_secs));
132 
133  // 2. We add any special ms/us/ns specifier if any
134  auto const extracted_ns = static_cast<uint32_t>(timestamp_ns - (timestamp_secs * 1'000'000'000));
135 
136  if (_additional_format_specifier == AdditionalSpecifier::Qms)
137  {
138  uint32_t const extracted_ms = extracted_ns / 1'000'000;
139 
140  // Add as many zeros as the extracted_fractional_seconds_length
141  static constexpr std::string_view zeros{"000"};
142  _formatted_date.append(zeros);
143 
144  _write_fractional_seconds(extracted_ms);
145  }
146  else if (_additional_format_specifier == AdditionalSpecifier::Qus)
147  {
148  uint32_t const extracted_us = extracted_ns / 1'000;
149 
150  // Add as many zeros as the extracted_fractional_seconds_length
151  static constexpr std::string_view zeros{"000000"};
152  _formatted_date.append(zeros);
153 
154  _write_fractional_seconds(extracted_us);
155  }
156  else if (_additional_format_specifier == AdditionalSpecifier::Qns)
157  {
158  // Add as many zeros as the extracted_fractional_seconds_length
159  static constexpr std::string_view zeros{"000000000"};
160  _formatted_date.append(zeros);
161 
162  _write_fractional_seconds(extracted_ns);
163  }
164 
165  // 3. format part 2 after fractional seconds - if any
166  if (_has_format_part_2)
167  {
168  _formatted_date.append(_strftime_part_2.format_timestamp(timestamp_secs));
169  }
170 
171  return std::string_view{_formatted_date.data(), _formatted_date.size()};
172  }
173 
174  /***/
175  QUILL_NODISCARD std::string const& time_format() const noexcept { return _time_format; }
176 
177  /***/
178  QUILL_NODISCARD Timezone timestamp_timezone() const noexcept { return _timestamp_timezone; }
179 
180 private:
181  /***/
182  void _write_fractional_seconds(uint32_t extracted_fractional_seconds)
183  {
184  // Format the seconds and add them
185  fmtquill::format_int const extracted_ms_string{extracted_fractional_seconds};
186 
187  // _formatted_date.size() - extracted_ms_string.size() is where we want to begin placing the fractional seconds
188  memcpy(&_formatted_date[_formatted_date.size() - extracted_ms_string.size()],
189  extracted_ms_string.data(), extracted_ms_string.size());
190  }
191 
192 private:
194  static constexpr std::array<char const*, 4> specifier_name{"", "%Qms", "%Qus", "%Qns"};
195 
197  static constexpr size_t specifier_length = 4u;
198 
199  std::string _time_format;
200  fmtquill::basic_memory_buffer<char, 32> _formatted_date;
201 
203  StringFromTime _strftime_part_1;
204  StringFromTime _strftime_part_2;
205 
207  Timezone _timestamp_timezone;
208 
210  AdditionalSpecifier _additional_format_specifier{AdditionalSpecifier::None};
211  bool _has_format_part_2{false};
212 };
213 
214 } // namespace detail
215 
216 QUILL_END_NAMESPACE
Formats a timestamp given a format string as a pattern.
Definition: TimestampFormatter.h:39
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:24
custom exception
Definition: QuillError.h:45