lintel 0.2.0
Loading...
Searching...
No Matches
DateTimeFormat.h
Go to the documentation of this file.
1#ifndef LINTEL_CORE_UTILS_DATETIMEFORMAT_H
2#define LINTEL_CORE_UTILS_DATETIMEFORMAT_H
3
4#include <cctype>
5#include <charconv>
6#include <chrono>
7#include <cstdio>
8#include <ctime>
9#include <string>
10
12
13namespace lintel {
33namespace datetime {
34
36 using SystemTime = std::chrono::sys_time<std::chrono::microseconds>;
37
43 inline std::time_t timegmPortable(std::tm *tm) noexcept
44 {
45#if defined(_WIN32)
46 return ::_mkgmtime(tm);
47#else
48 return ::timegm(tm);
49#endif
50 }
51
57 inline long localOffsetSeconds(std::time_t time, const std::tm &local) noexcept
58 {
59 // offset = local_wall_clock - utc_wall_clock = mktime(local) - timegm(local)
60 // and mktime(local) == time, so the round trip through timegm() is enough.
61 std::tm asUtc = local;
62 return static_cast<long>(time - timegmPortable(&asUtc));
63 }
64
66 inline bool isEastOfUtc(long offsetSeconds) noexcept { return offsetSeconds >= 0; }
67
69 inline std::string formatOffset(long offsetSeconds)
70 {
72 char buffer[16];
73 const int written = std::snprintf(buffer,
74 sizeof(buffer),
75 "%c%02ld:%02ld",
76 isEastOfUtc(offsetSeconds) ? '+' : '-',
77 absolute / 3600,
78 (absolute % 3600) / 60);
79 if (written < 0) { return {}; }
80 return std::string(buffer, static_cast<std::size_t>(written));
81 }
82
91 inline std::string formatSystemTime(const SystemTime &value, char dateSeparator = '-')
92 {
93 const auto wholeSeconds = std::chrono::floor<std::chrono::seconds>(value);
94 const auto micros = std::chrono::duration_cast<std::chrono::microseconds>(value - wholeSeconds).count();
95 const std::time_t time = std::chrono::system_clock::to_time_t(
96 std::chrono::time_point_cast<std::chrono::system_clock::duration>(wholeSeconds));
97 std::tm local{};
98 if (!lintel::localTime(time, local)) { return {}; }
99 const long offset = localOffsetSeconds(time, local);
100
101 char buffer[64];
102 const int written = std::snprintf(buffer,
103 sizeof(buffer),
104 "%04d%c%02d%c%02d %02d:%02d:%02d.%06ld",
105 local.tm_year + 1900,
107 local.tm_mon + 1,
109 local.tm_mday,
110 local.tm_hour,
111 local.tm_min,
112 local.tm_sec,
113 static_cast<long>(micros));
114 if (written < 0) { return {}; }
115 return std::string(buffer, static_cast<std::size_t>(written)) + formatOffset(offset);
116 }
117
126 inline SystemTime parseSystemTime(const std::string &value)
127 {
128 int year = 0;
129 int month = 0;
130 int day = 0;
131 int hour = 0;
132 int minute = 0;
133 int second = 0;
134
135 // The date/time fields are converted with std::from_chars: std::sscanf cannot
136 // report invalid input or overflow (cert-err34-c).
137 const auto readInt = [&value](std::size_t &position, int &result) {
138 const auto parsed = std::from_chars(value.data() + position, value.data() + value.size(), result);
139 if (parsed.ec != std::errc{} || parsed.ptr == value.data() + position) { return false; }
140 position = static_cast<std::size_t>(parsed.ptr - value.data());
141 return true;
142 };
143 const auto readField = [&value, &readInt](std::size_t &position, char separator, int &result) {
144 if (!readInt(position, result)) { return false; }
145 if (position >= value.size() || value[position] != separator) { return false; }
146 ++position;
147 return true;
148 };
149
150 std::size_t index = 0;
151 const bool parsedDate = readField(index, '-', year) && readField(index, '-', month) && readField(index, ' ', day);
152 const bool parsedTime = readField(index, ':', hour) && readField(index, ':', minute) && readInt(index, second);
153 if (!parsedDate || !parsedTime) { return SystemTime{}; }
154 long micros = 0;
155 if (index < value.size() && value[index] == '.') {
156 ++index;
157 long scale = 100000;
158 while (index < value.size() && std::isdigit(static_cast<unsigned char>(value[index])) != 0) {
159 if (scale > 0) {
160 micros += static_cast<long>(value[index] - '0') * scale;
161 scale /= 10;
162 }
163 ++index;
164 }
165 }
166
167 // The offset is mandatory (PostgreSQL/SQLite always write one). "Z" means UTC.
168 long offsetSeconds = 0;
169 if (index < value.size() && (value[index] == 'Z' || value[index] == 'z')) {
170 ++index;
171 } else if (index < value.size() && (value[index] == '+' || value[index] == '-')) {
172 const bool negative = value[index] == '-';
173 ++index;
174 const auto readNumber = [&value, &index](int maxDigits) -> int {
175 int result = 0;
176 int digits = 0;
177 while (
179 result = result * 10 + (value[index] - '0');
180 ++index;
181 ++digits;
182 }
183 return result;
184 };
185 const int offsetHours = readNumber(2);
186 if (index < value.size() && value[index] == ':') { ++index; }
187 const int offsetMinutes = readNumber(2);
188 offsetSeconds = static_cast<long>(offsetHours) * 3600 + static_cast<long>(offsetMinutes) * 60;
190 } else {
191 return SystemTime{};
192 }
193
194 std::tm tm{};
195 tm.tm_year = year - 1900;
196 tm.tm_mon = month - 1;
197 tm.tm_mday = day;
198 tm.tm_hour = hour;
199 tm.tm_min = minute;
200 tm.tm_sec = second;
201 tm.tm_isdst = 0;
202 const std::time_t utcSeconds = timegmPortable(&tm) - offsetSeconds;
203 return SystemTime{ std::chrono::seconds{ utcSeconds } } + std::chrono::microseconds{ micros };
204 }
205
212 inline std::string formatUtcIsoSeconds(const std::chrono::sys_seconds &value)
213 {
214 const std::time_t time =
215 std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(value));
216 std::tm utc{};
217#if defined(_WIN32)
218 if (::gmtime_s(&utc, &time) != 0) { return {}; }
219#else
220 if (::gmtime_r(&time, &utc) == nullptr) { return {}; }
221#endif
222 char buffer[32];
223 const int written = std::snprintf(buffer,
224 sizeof(buffer),
225 "%04d-%02d-%02dT%02d:%02d:%02dZ",
226 utc.tm_year + 1900,
227 utc.tm_mon + 1,
228 utc.tm_mday,
229 utc.tm_hour,
230 utc.tm_min,
231 utc.tm_sec);
232 if (written < 0) { return {}; }
233 return std::string(buffer, static_cast<std::size_t>(written));
234 }
235
236}// namespace datetime
237}// namespace lintel
238
239#endif// LINTEL_CORE_UTILS_DATETIMEFORMAT_H
bool isEastOfUtc(long offsetSeconds) noexcept
Definition DateTimeFormat.h:66
std::string formatUtcIsoSeconds(const std::chrono::sys_seconds &value)
Definition DateTimeFormat.h:212
std::string formatOffset(long offsetSeconds)
Definition DateTimeFormat.h:69
SystemTime parseSystemTime(const std::string &value)
Definition DateTimeFormat.h:126
std::string formatSystemTime(const SystemTime &value, char dateSeparator='-')
Definition DateTimeFormat.h:91
long localOffsetSeconds(std::time_t time, const std::tm &local) noexcept
Definition DateTimeFormat.h:57
std::time_t timegmPortable(std::tm *tm) noexcept
Definition DateTimeFormat.h:43
std::chrono::sys_time< std::chrono::microseconds > SystemTime
Definition DateTimeFormat.h:36
Definition BaseFeature.h:6
bool localTime(std::time_t time, std::tm &out) noexcept
Definition Platform.h:54
constexpr std::string_view type_name()
Definition TypeName.h:9