88#include < type_traits>
99
1010#include " glaze/core/chrono.hpp"
11- #include " glaze/core/format_str.hpp"
1211#include " glaze/json/read.hpp"
1312#include " glaze/json/write.hpp"
1413
1514// Per-field chrono customization wrappers.
1615//
1716// These decouple the *format* from the chrono *type*, addressing the limitation
18- // that the clock type alone dictates the wire representation. They follow the
19- // established field-wrapper precedents (glz::float_format for the NTTP format
20- // string, glz::quoted for the fully-custom read+write pair) and are applied
17+ // that the clock type alone dictates the wire representation. They are applied
2118// inside glz::object(...) within a glz::meta<T> specialization:
2219//
2320// template <> struct glz::meta<event> {
2421// using T = event;
2522// static constexpr auto value = glz::object(
26- // "start", glz::date_format< &T::start, "%Y-%m-%d %H:%M:%S"> ,
27- // "logged", glz::epoch_count<&T::logged, std::chrono::milliseconds>);
23+ // "start", glz::date_format( &T::start, "%Y-%m-%d %H:%M:%S") ,
24+ // "logged", glz::epoch_count<std::chrono::milliseconds>(&T::logged) );
2825// };
2926//
27+ // Both take the member pointer as a value argument. The format string (date_format)
28+ // and member pointer live as data rather than as non-type template parameters, so a
29+ // struct with many distinct formats does not multiply the wrapper's template
30+ // instantiations: the view type is keyed on the member type alone, and the format is
31+ // carried as a runtime std::string_view. The strftime pattern is still validated at
32+ // compile time — a consteval guard in the date_format() factory rejects unsupported
33+ // tokens, a missing calendar date, or time tokens on a year_month_day field. (glz::
34+ // float_format keeps its NTTP form because it leans on std::format's compile-time
35+ // format checking, which date_format's hand-rolled runtime walk does not need.)
36+ //
3037// Scope (MVP): JSON only. date_format supports system_clock time_points and
3138// year_month_day; epoch_count supports system_clock time_points. utc_time and
3239// the binary backends are intentionally out of scope here.
@@ -37,64 +44,36 @@ namespace glz
3744 // glz::date_format — per-field strftime-subset format string
3845 // ============================================
3946
40- // Wrapper carrying the compile-time format string and a reference to the field.
41- // Both directions are fully custom (no meta<>::value indirection) because the
42- // underlying chrono parser only understands ISO 8601 and cannot honor Fmt .
47+ // View carrying the bound member and the (runtime) format string. Parameterized on the
48+ // member type alone, so two fields that differ only in their format string share one set
49+ // of to<>/from<> instantiations .
4350 //
4451 // glaze_reflect = false keeps the binary backends from silently reflecting the
4552 // wrapper as an aggregate: date_format is a textual, JSON-only wrapper, so a
4653 // binary serialization should fail loudly (undefined to<BEVE, ...>) rather than
4754 // emit a meaningless encoding of the wrapper struct.
48- template <format_str Fmt, class T >
55+ template <class T >
4956 struct date_format_t
5057 {
5158 static constexpr bool glaze_wrapper = true ;
5259 static constexpr bool glaze_reflect = false ;
5360 using value_type = T;
5461 T& val;
62+ std::string_view fmt;
5563 };
5664
57- namespace chrono_detail
58- {
59- // Compile-time guards shared by the date_format read and write paths.
60- template <format_str Fmt, class V >
61- consteval void validate_date_format () noexcept
62- {
63- static_assert (is_system_time_point<V> || is_year_month_day<V>,
64- " glz::date_format requires a std::chrono::system_clock time_point or year_month_day member" );
65- static_assert (date_format_tokens_valid (std::string_view (Fmt)),
66- " glz::date_format: unsupported format token (supported: %Y %m %d %H %M %S %F %T %%)" );
67- static_assert (date_format_has_full_date (std::string_view (Fmt)),
68- " glz::date_format: format must include a full date (%Y %m %d, or %F)" );
69- if constexpr (is_year_month_day<V>) {
70- static_assert (!date_format_has_time (std::string_view (Fmt)),
71- " glz::date_format: a year_month_day field must not include time tokens (%H %M %S %T)" );
72- }
73- }
74-
75- // Compile-time guards shared by the epoch_count read and write paths. Validates both
76- // axes (the member type and the Duration unit) so a misuse on either surfaces as a
77- // friendly message rather than a deep cascade out of `typename Duration::rep`.
78- template <class Duration , class V >
79- consteval void validate_epoch_count () noexcept
80- {
81- static_assert (is_system_time_point<V>,
82- " glz::epoch_count requires a std::chrono::system_clock time_point member" );
83- static_assert (is_duration<Duration>,
84- " glz::epoch_count requires a std::chrono::duration unit (e.g. std::chrono::milliseconds)" );
85- }
86- }
87-
88- template <format_str Fmt, class T >
89- struct to <JSON , date_format_t <Fmt, T>>
65+ template <class T >
66+ struct to <JSON , date_format_t <T>>
9067 {
9168 template <auto Opts, class B >
9269 static void op (auto && wrapper, is_context auto && ctx, B&& b, auto & ix) noexcept
9370 {
9471 using namespace std ::chrono;
9572 using V = std::remove_cvref_t <T>;
96- chrono_detail::validate_date_format<Fmt, V>();
73+ static_assert (is_system_time_point<V> || is_year_month_day<V>,
74+ " glz::date_format requires a std::chrono::system_clock time_point or year_month_day member" );
9775
76+ const std::string_view fmt = wrapper.fmt ;
9877 chrono_detail::date_time_fields f{};
9978
10079 if constexpr (is_year_month_day<V>) {
@@ -131,12 +110,11 @@ namespace glz
131110 }
132111
133112 // Upper bound on rendered size: the widest token is %F, which expands two source
134- // characters to "YYYY-MM-DD" (10 bytes, 5 per source char); %T expands two to
135- // "HH:MM:SS" (8 bytes, 4 per source char). *6 bounds both with room to spare, plus a
136- // small constant for the surrounding quotes. Skipped on the write_unchecked fast path,
137- // where the caller has already reserved the space (matches to<JSON, num_t>).
113+ // characters to "YYYY-MM-DD" (10 bytes, 5 per source char). *6 bounds it with room
114+ // to spare, plus a small constant for the surrounding quotes. Skipped on the
115+ // write_unchecked fast path, where the caller has already reserved the space.
138116 if constexpr (not check_write_unchecked (Opts)) {
139- const size_t max_size = std::string_view (Fmt) .size () * 6 + 4 ;
117+ const size_t max_size = fmt .size () * 6 + 4 ;
140118 if (!ensure_space (ctx, b, ix + max_size)) [[unlikely]] {
141119 return ;
142120 }
@@ -145,30 +123,31 @@ namespace glz
145123 if constexpr (not check_unquoted (Opts)) {
146124 b[ix++] = ' "' ;
147125 }
148- chrono_detail::write_date_format (std::string_view (Fmt) , f, b, ix);
126+ chrono_detail::write_date_format (fmt , f, b, ix);
149127 if constexpr (not check_unquoted (Opts)) {
150128 b[ix++] = ' "' ;
151129 }
152130 }
153131 };
154132
155- template <format_str Fmt, class T >
156- struct from <JSON , date_format_t <Fmt, T>>
133+ template <class T >
134+ struct from <JSON , date_format_t <T>>
157135 {
158136 template <auto Opts>
159137 static void op (auto && wrapper, is_context auto && ctx, auto &&... args) noexcept
160138 {
161139 using namespace std ::chrono;
162140 using V = std::remove_cvref_t <T>;
163- chrono_detail::validate_date_format<Fmt, V>();
141+ static_assert (is_system_time_point<V> || is_year_month_day<V>,
142+ " glz::date_format requires a std::chrono::system_clock time_point or year_month_day member" );
164143
165144 std::string_view str;
166145 from<JSON , std::string_view>::template op<Opts>(str, ctx, args...);
167146 if (bool (ctx.error )) [[unlikely]]
168147 return ;
169148
170149 chrono_detail::date_time_fields f{};
171- chrono_detail::parse_date_format (std::string_view (Fmt) , str, f, ctx.error );
150+ chrono_detail::parse_date_format (wrapper. fmt , str, f, ctx.error );
172151 if (bool (ctx.error )) [[unlikely]]
173152 return ;
174153
@@ -196,23 +175,65 @@ namespace glz
196175 }
197176 };
198177
199- template <auto MemPtr, format_str Fmt>
200- inline constexpr decltype (auto ) date_format_impl() noexcept
178+ namespace chrono_detail
201179 {
202- return [](auto && val) { return date_format_t <Fmt, std::remove_reference_t <decltype (val.*MemPtr)>>{val.*MemPtr}; };
180+ // Compile-time validation for date_format(). Rejects unsupported tokens, a missing
181+ // calendar date, and time tokens applied to a year_month_day field. Runs inside the
182+ // consteval date_format() factory, where the literal is a constant expression, even
183+ // though the format is carried onward as a runtime value (a throw on the error path
184+ // makes the surrounding immediate invocation non-constant, i.e. a hard compile error).
185+ template <class Mem >
186+ consteval void validate_date_format (std::string_view fmt)
187+ {
188+ static_assert (is_system_time_point<Mem> || is_year_month_day<Mem>,
189+ " glz::date_format requires a std::chrono::system_clock time_point or year_month_day member" );
190+ if (!date_format_tokens_valid (fmt)) {
191+ throw " glz::date_format: unsupported format token (supported: %Y %m %d %H %M %S %F %T %%)" ;
192+ }
193+ if (!date_format_has_full_date (fmt)) {
194+ throw " glz::date_format: format must include a full date (%Y %m %d, or %F)" ;
195+ }
196+ if constexpr (is_year_month_day<Mem>) {
197+ if (date_format_has_time (fmt)) {
198+ throw " glz::date_format: a year_month_day field must not include time tokens (%H %M %S %T)" ;
199+ }
200+ }
201+ }
203202 }
204203
205- // Usage: glz::date_format<&T::member, "%Y-%m-%d %H:%M:%S">
206- template <auto MemPtr, format_str Fmt>
207- constexpr auto date_format = date_format_impl<MemPtr, Fmt>();
204+ // The object element: an invocable value carrying the member pointer + format. When the
205+ // serializer iterates the meta object it calls this with the live instance (the same
206+ // std::invocable dispatch every glaze field wrapper uses) to bind the member by reference.
207+ template <class MemPtr >
208+ struct date_format_spec
209+ {
210+ MemPtr ptr;
211+ std::string_view fmt;
212+
213+ constexpr auto operator ()(auto && parent) const
214+ {
215+ using Mem = std::remove_reference_t <decltype (parent.*ptr)>;
216+ return date_format_t <Mem>{parent.*ptr, fmt};
217+ }
218+ };
219+
220+ // Usage: glz::date_format(&T::member, "%Y-%m-%d %H:%M:%S")
221+ template <class MemPtr >
222+ consteval auto date_format (MemPtr ptr, const char * fmt)
223+ {
224+ using Mem = std::remove_cvref_t <typename unwrap_pointer<MemPtr>::type>;
225+ const std::string_view sv{fmt};
226+ chrono_detail::validate_date_format<Mem>(sv);
227+ return date_format_spec<MemPtr>{ptr, sv};
228+ }
208229
209230 // ============================================
210231 // glz::epoch_count — per-field Unix timestamp count
211232 // ============================================
212233
213- // Serializes a system_clock time_point field as a numeric Unix timestamp in
214- // units of Duration, the per-field counterpart to the glz::epoch_time storage
215- // wrapper (so one field can be an epoch count while others stay ISO 8601).
234+ // View serializing a system_clock time_point field as a numeric Unix timestamp in units
235+ // of Duration, the per-field counterpart to the glz::epoch_time storage wrapper (so one
236+ // field can be an epoch count while others stay ISO 8601).
216237 template <class Duration , class T >
217238 struct epoch_count_t
218239 {
@@ -229,7 +250,10 @@ namespace glz
229250 static void op (auto && wrapper, is_context auto && ctx, B&& b, auto & ix) noexcept
230251 {
231252 using V = std::remove_cvref_t <T>;
232- chrono_detail::validate_epoch_count<Duration, V>();
253+ static_assert (is_system_time_point<V>,
254+ " glz::epoch_count requires a std::chrono::system_clock time_point member" );
255+ static_assert (is_duration<Duration>,
256+ " glz::epoch_count requires a std::chrono::duration unit (e.g. std::chrono::milliseconds)" );
233257 using Rep = typename Duration::rep;
234258 const auto count = std::chrono::duration_cast<Duration>(wrapper.val .time_since_epoch ()).count ();
235259 to<JSON , Rep>::template op<Opts>(count, ctx, b, ix);
@@ -243,7 +267,10 @@ namespace glz
243267 static void op (auto && wrapper, is_context auto && ctx, auto &&... args) noexcept
244268 {
245269 using V = std::remove_cvref_t <T>;
246- chrono_detail::validate_epoch_count<Duration, V>();
270+ static_assert (is_system_time_point<V>,
271+ " glz::epoch_count requires a std::chrono::system_clock time_point member" );
272+ static_assert (is_duration<Duration>,
273+ " glz::epoch_count requires a std::chrono::duration unit (e.g. std::chrono::milliseconds)" );
247274 using Rep = typename Duration::rep;
248275 Rep count{};
249276 from<JSON , Rep>::template op<Opts>(count, ctx, args...);
@@ -254,15 +281,27 @@ namespace glz
254281 }
255282 };
256283
257- template <auto MemPtr , class Duration >
258- inline constexpr decltype ( auto ) epoch_count_impl() noexcept
284+ template <class Duration , class MemPtr >
285+ struct epoch_count_spec
259286 {
260- return [](auto && val) {
261- return epoch_count_t <Duration, std::remove_reference_t <decltype (val.*MemPtr)>>{val.*MemPtr};
262- };
263- }
287+ MemPtr ptr;
264288
265- // Usage: glz::epoch_count<&T::member, std::chrono::milliseconds>
266- template <auto MemPtr, class Duration >
267- constexpr auto epoch_count = epoch_count_impl<MemPtr, Duration>();
289+ constexpr auto operator ()(auto && parent) const
290+ {
291+ using Mem = std::remove_reference_t <decltype (parent.*ptr)>;
292+ return epoch_count_t <Duration, Mem>{parent.*ptr};
293+ }
294+ };
295+
296+ // Usage: glz::epoch_count<std::chrono::milliseconds>(&T::member)
297+ template <class Duration , class MemPtr >
298+ constexpr auto epoch_count (MemPtr ptr)
299+ {
300+ using Mem = std::remove_cvref_t <typename unwrap_pointer<MemPtr>::type>;
301+ static_assert (is_system_time_point<Mem>,
302+ " glz::epoch_count requires a std::chrono::system_clock time_point member" );
303+ static_assert (is_duration<Duration>,
304+ " glz::epoch_count requires a std::chrono::duration unit (e.g. std::chrono::milliseconds)" );
305+ return epoch_count_spec<Duration, MemPtr>{ptr};
306+ }
268307}
0 commit comments