38#include <glaze/glaze.hpp>
39#include <morph/attributes.hpp>
47namespace morph::time {
73 std::chrono::sys_time<std::chrono::milliseconds>
value;
80 constexpr
DateTime(std::chrono::sys_time<std::chrono::milliseconds> instant) noexcept :
value{instant} {}
90 constexpr DateTime(std::chrono::year year, std::chrono::month month, std::chrono::day day, std::chrono::hours hour,
91 std::chrono::minutes minute, std::chrono::seconds second,
92 std::chrono::milliseconds millisecond = std::chrono::milliseconds{0})
noexcept
93 :
value{static_cast<std::chrono::sys_days>(std::chrono::year_month_day{year, month, day}) + hour + minute +
94 second + millisecond} {}
117 std::scoped_lock
const lock{mtx};
122 return DateTime{std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now())};
128 auto const wholeDays = std::chrono::floor<std::chrono::days>(
value);
129 auto const calendar = std::chrono::year_month_day{wholeDays};
130 auto const clock = std::chrono::hh_mm_ss{
value - wholeDays};
131 return std::format(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
static_cast<int>(calendar.year()),
132 static_cast<unsigned>(calendar.month()),
static_cast<unsigned>(calendar.day()),
133 clock.hours().count(), clock.minutes().count(), clock.seconds().count(),
134 clock.subseconds().count());
148 [[nodiscard]]
static std::optional<DateTime>
fromIso8601(std::string_view text)
noexcept {
156 auto const number = [&text](std::size_t offset, std::size_t count,
157 bool allowSign)
noexcept -> std::optional<int> {
158 if (!allowSign && count > 0 && offset < text.size() && (text[offset] ==
'-' || text[offset] ==
'+')) {
162 auto const* first = text.data() + offset;
163 auto const [end, errc] = std::from_chars(first, first + count, parsed);
164 if (errc != std::errc{} || end != first + count) {
169 auto const separator = [&text](std::size_t offset,
char expected)
noexcept {
170 return offset < text.size() && text[offset] == expected;
173 if (text.size() < 19 || !separator(4,
'-') || !separator(7,
'-') || !separator(10,
'T') ||
174 !separator(13,
':') || !separator(16,
':')) {
177 auto const year = number(0, 4,
true);
178 auto const month = number(5, 2,
false);
179 auto const day = number(8, 2,
false);
180 auto const hour = number(11, 2,
false);
181 auto const minute = number(14, 2,
false);
182 auto const second = number(17, 2,
false);
183 if (!year || !month || !day || !hour || !minute || !second) {
187 std::size_t cursor = 19;
188 int milliseconds = 0;
189 if (separator(cursor,
'.')) {
191 std::size_t digits = 0;
193 while (digits < 3 && cursor < text.size() && text[cursor] >=
'0' && text[cursor] <=
'9') {
194 fraction = (fraction * 10) + (text[cursor] -
'0');
201 for (std::size_t pad = digits; pad < 3; ++pad) {
204 milliseconds = fraction;
206 if (separator(cursor,
'Z')) {
209 if (cursor != text.size()) {
213 auto const calendar =
214 std::chrono::year_month_day{std::chrono::year{*year}, std::chrono::month{
static_cast<unsigned>(*month)},
215 std::chrono::day{
static_cast<unsigned>(*day)}};
216 if (!calendar.ok() || *hour > 23 || *minute > 59 || *second > 59) {
219 return DateTime{std::chrono::year{*year},
220 std::chrono::month{
static_cast<unsigned>(*month)},
221 std::chrono::day{
static_cast<unsigned>(*day)},
222 std::chrono::hours{*hour},
223 std::chrono::minutes{*minute},
224 std::chrono::seconds{*second},
225 std::chrono::milliseconds{milliseconds}};
237 template <
typename Rep,
typename Period>
239 value += std::chrono::duration_cast<std::chrono::milliseconds>(delta);
246 template <
typename Rep,
typename Period>
248 value -= std::chrono::duration_cast<std::chrono::milliseconds>(delta);
256 template <
typename Rep,
typename Period>
258 std::chrono::duration<Rep, Period> delta)
noexcept {
267 template <
typename Rep,
typename Period>
269 std::chrono::duration<Rep, Period> delta)
noexcept {
279 return lhs.value - rhs.value;
292 static std::pair<std::mutex, std::function<
DateTime()>> state;
321 std::scoped_lock
const lock{mtx};
322 slot = std::move(clock);
353 std::scoped_lock
const lock{mtx};
354 _previous = std::move(slot);
355 slot = std::move(clock);
367 std::function<
DateTime()> _previous;
389 constexpr Timestamp(std::optional<DateTime> payload) noexcept :
value{payload} {}
397 [[nodiscard]]
constexpr bool hasValue() const noexcept {
return value.has_value(); }
418 if (!lhs.value || !rhs.value) {
421 return *lhs.value - *rhs.value;
435struct from<JSON, morph::time::DateTime> {
437 static void op(
auto&& value, is_context
auto&& ctx,
auto&& iter,
auto end) {
439 parse<JSON>::op<Opts>(str, ctx, iter, end);
440 if (
bool(ctx.error)) [[unlikely]] {
447 ctx.error = error_code::syntax_error;
453struct to<JSON, morph::time::DateTime> {
455 static void op(
auto&& value, is_context
auto&& ctx,
auto&&... args) {
456 serialize<JSON>::op<Opts>(value.toIso8601(), ctx, args...);
462struct meta<morph::time::Timestamp> {
464 static constexpr std::string_view name =
"Timestamp";
469namespace glz::detail {
474struct to_json_schema<morph::time::DateTime> {
476 static void op(
auto& outSchema,
auto& defs) {
477 to_json_schema<std::string>::template op<Opts>(outSchema, defs);
478 outSchema.format = defined_formats::datetime;
496 static constexpr std::string_view
name() noexcept {
return "datetime"; }
509 static constexpr std::string_view
name() noexcept {
return "timestamp"; }
521 constexpr auto parse(std::format_parse_context& ctx) {
524 if (*ctx.begin() !=
'}') {
525 throw std::format_error{
"morph::time::DateTime accepts only the empty format spec '{}'"};
530 auto format(
const morph::time::DateTime& value, std::format_context& ctx)
const {
531 return std::format_to(ctx.out(),
"{}", value.
toIso8601());
Handle< std::string > format(AnyHandle const &pattern, std::vector< AnyHandle > args)
format: a template with {0}, {1}, … placeholders.
RAII helper that fixes DateTime::now() to a constant instant (or a custom callable) for its lifetime ...
~ScopedNowOverride()
Restores the saved override (or "no override", if none was installed before).
ScopedNowOverride(DateTime fixedInstant)
Installs a clock that always returns fixedInstant.
ScopedNowOverride(std::function< DateTime()> clock)
Installs an arbitrary clock callable, saving whatever override was there before.
std::pair< std::mutex, std::function< DateTime()> > & nowOverrideState()
Process-wide override for DateTime::now(), guarded by a mutex.
constexpr std::optional< std::chrono::milliseconds > operator-(const Timestamp &lhs, const Timestamp &rhs) noexcept
The signed duration between two engaged timestamps.
void setNowOverride(std::function< DateTime()> clock)
Installs clock as the process-wide override for DateTime::now() (and therefore Timestamp::now(),...
@ DateTime
An instant, entered in the display zone, sent as ISO-8601 UTC.
Opt-in, compiler-independent shape tag for a type with a custom Glaze codec.
static constexpr std::string_view name() noexcept
This type's stable shape name.
static constexpr std::string_view name() noexcept
This type's stable shape name.
Declares a stable shape tag for a type T whose JSON shape payloadShape cannot decompose.
A UTC instant with millisecond precision.
static DateTime now() noexcept
The current UTC instant, truncated to milliseconds.
constexpr DateTime() noexcept=default
Constructs the Unix epoch (1970-01-01T00:00:00.000Z).
friend constexpr DateTime operator+(DateTime lhs, std::chrono::duration< Rep, Period > delta) noexcept
An instant delta after lhs.
friend constexpr auto operator-(const DateTime &lhs, const DateTime &rhs) noexcept
The signed difference between two instants.
std::string toIso8601() const
Renders as ISO-8601 UTC: YYYY-MM-DDTHH:MM:SS.mmmZ.
constexpr DateTime & operator-=(std::chrono::duration< Rep, Period > delta) noexcept
Shifts this instant backwards by an arbitrary chrono duration.
friend constexpr DateTime operator-(DateTime lhs, std::chrono::duration< Rep, Period > delta) noexcept
An instant delta before lhs.
constexpr std::strong_ordering operator<=>(const DateTime &other) const noexcept=default
Ordering on the underlying instant.
static std::optional< DateTime > fromIso8601(std::string_view text) noexcept
Strictly parses ISO-8601 UTC: YYYY-MM-DDTHH:MM:SS, optionally followed by .m/.mm/....
std::chrono::sys_time< std::chrono::milliseconds > value
The underlying UTC instant (Unix time, leap seconds ignored).
constexpr DateTime(std::chrono::year year, std::chrono::month month, std::chrono::day day, std::chrono::hours hour, std::chrono::minutes minute, std::chrono::seconds second, std::chrono::milliseconds millisecond=std::chrono::milliseconds{0}) noexcept
Composes a UTC instant from calendar/clock components.
constexpr DateTime & operator+=(std::chrono::duration< Rep, Period > delta) noexcept
Shifts this instant by an arbitrary chrono duration.
An optionally-empty timestamp field.
std::optional< DateTime > value
The payload; std::nullopt means "not entered".
static Timestamp now() noexcept
An engaged timestamp holding the current UTC instant.
constexpr const DateTime & operator*() const noexcept
Unchecked access to the engaged instant (UB when empty, exactly like std::optional — the unchecked co...
constexpr bool hasValue() const noexcept
Whether an instant has been entered.
constexpr Timestamp(std::optional< DateTime > payload) noexcept
Adopts an optional payload as-is.
constexpr Timestamp() noexcept=default
Constructs the empty state.
constexpr auto operator<=>(const Timestamp &other) const noexcept=default
Ordering/equality on the payload; empty sorts before engaged.