@@ -221,6 +221,193 @@ namespace nfx::time
221221 }
222222 } // namespace internal
223223
224+ // =====================================================================
225+ // Fast parsing helpers
226+ // =====================================================================
227+
228+ namespace
229+ {
230+ /* * @brief Fast parse 2 digits without validation (inline-optimized) */
231+ [[nodiscard]] constexpr std::int32_t parse2Digits ( const char * p ) noexcept
232+ {
233+ return ( p[0 ] - ' 0' ) * 10 + ( p[1 ] - ' 0' );
234+ }
235+
236+ /* * @brief Fast parse 4 digits without validation (inline-optimized) */
237+ [[nodiscard]] constexpr std::int32_t parse4Digits ( const char * p ) noexcept
238+ {
239+ return ( p[0 ] - ' 0' ) * 1000 + ( p[1 ] - ' 0' ) * 100 + ( p[2 ] - ' 0' ) * 10 + ( p[3 ] - ' 0' );
240+ }
241+
242+ /* * @brief Check if character is a digit */
243+ [[nodiscard]] constexpr bool isDigit ( char c ) noexcept
244+ {
245+ return c >= ' 0' && c <= ' 9' ;
246+ }
247+
248+ /* * @brief Validate that all positions contain digits */
249+ [[nodiscard]] constexpr bool areDigits ( const char * p, std::size_t count ) noexcept
250+ {
251+ for ( std::size_t i = 0 ; i < count; ++i )
252+ {
253+ if ( !isDigit ( p[i] ) )
254+ {
255+ return false ;
256+ }
257+ }
258+ return true ;
259+ }
260+
261+ /* *
262+ * @brief Fast-path parser for standard ISO 8601 formats
263+ * @details Handles the most common formats with fixed positions:
264+ * - "YYYY-MM-DD" (10 chars)
265+ * - "YYYY-MM-DDTHH:mm:ss" (19 chars)
266+ * - "YYYY-MM-DDTHH:mm:ssZ" (20 chars)
267+ * - "YYYY-MM-DDTHH:mm:ss.f" (21-27 chars)
268+ * - "YYYY-MM-DDTHH:mm:ss.fZ" (22-28 chars)
269+ * @return true if parsed successfully via fast path, false if fallback needed
270+ */
271+ [[nodiscard]] bool tryParseFastPath ( std::string_view str, DateTime& result ) noexcept
272+ {
273+ const std::size_t len = str.length ();
274+
275+ // Fast path requirements: minimum 10 chars (YYYY-MM-DD)
276+ if ( len < 10 )
277+ {
278+ return false ;
279+ }
280+
281+ const char * data = str.data ();
282+
283+ // Validate fixed separators and digit positions for date part
284+ if ( data[4 ] != ' -' || data[7 ] != ' -' || !areDigits ( data, 4 ) || !areDigits ( data + 5 , 2 ) ||
285+ !areDigits ( data + 8 , 2 ) )
286+ {
287+ return false ;
288+ }
289+
290+ // Parse date components using fast digit parsing
291+ const std::int32_t year = parse4Digits ( data );
292+ const std::int32_t month = parse2Digits ( data + 5 );
293+ const std::int32_t day = parse2Digits ( data + 8 );
294+
295+ // Validate date components
296+ if ( !internal::isValidDate ( year, month, day ) )
297+ {
298+ return false ;
299+ }
300+
301+ // Date-only format: "YYYY-MM-DD"
302+ if ( len == 10 )
303+ {
304+ const std::int64_t ticks = internal::dateToTicks ( year, month, day );
305+ result = DateTime{ ticks };
306+ return true ;
307+ }
308+
309+ // Time part must start with 'T'
310+ if ( data[10 ] != ' T' )
311+ {
312+ return false ;
313+ }
314+
315+ // Need at least "YYYY-MM-DDTHH:mm:ss" (19 chars)
316+ if ( len < 19 )
317+ {
318+ return false ;
319+ }
320+
321+ // Validate time separators and digits
322+ if ( data[13 ] != ' :' || data[16 ] != ' :' || !areDigits ( data + 11 , 2 ) || !areDigits ( data + 14 , 2 ) ||
323+ !areDigits ( data + 17 , 2 ) )
324+ {
325+ return false ;
326+ }
327+
328+ // Parse time components
329+ const std::int32_t hour = parse2Digits ( data + 11 );
330+ const std::int32_t minute = parse2Digits ( data + 14 );
331+ const std::int32_t second = parse2Digits ( data + 17 );
332+
333+ // Validate time components
334+ if ( !internal::isValidTime ( hour, minute, second, 0 ) )
335+ {
336+ return false ;
337+ }
338+
339+ std::int32_t fractionalTicks = 0 ;
340+ std::size_t pos = 19 ;
341+
342+ // Handle optional 'Z' at position 19
343+ if ( len == 20 && data[19 ] == ' Z' )
344+ {
345+ // "YYYY-MM-DDTHH:mm:ssZ" - no fractional seconds
346+ pos = 20 ;
347+ }
348+ // Handle fractional seconds
349+ else if ( len > 19 && data[19 ] == ' .' )
350+ {
351+ pos = 20 ; // Start after '.'
352+
353+ // Parse up to 7 fractional digits (100ns precision)
354+ std::int32_t fractionValue = 0 ;
355+ std::int32_t fractionDigits = 0 ;
356+
357+ while ( pos < len && isDigit ( data[pos] ) && fractionDigits < 7 )
358+ {
359+ fractionValue = fractionValue * 10 + ( data[pos] - ' 0' );
360+ ++fractionDigits;
361+ ++pos;
362+ }
363+
364+ if ( fractionDigits == 0 )
365+ {
366+ return false ; // '.' must be followed by at least one digit
367+ }
368+
369+ // Pad to 7 digits (convert to 100ns ticks)
370+ while ( fractionDigits < 7 )
371+ {
372+ fractionValue *= 10 ;
373+ ++fractionDigits;
374+ }
375+
376+ fractionalTicks = fractionValue;
377+
378+ // Skip remaining fractional digits beyond our precision
379+ while ( pos < len && isDigit ( data[pos] ) )
380+ {
381+ ++pos;
382+ }
383+
384+ // Optional 'Z' after fractional seconds
385+ if ( pos < len && data[pos] == ' Z' )
386+ {
387+ ++pos;
388+ }
389+ }
390+ else if ( len != 19 )
391+ {
392+ // Unexpected format - not a standard ISO 8601 we can fast-path
393+ return false ;
394+ }
395+
396+ // Should have consumed the entire string
397+ if ( pos != len )
398+ {
399+ return false ;
400+ }
401+
402+ // Calculate total ticks
403+ const std::int64_t ticks = internal::dateToTicks ( year, month, day ) +
404+ internal::timeToTicks ( hour, minute, second, 0 ) + fractionalTicks;
405+
406+ result = DateTime{ ticks };
407+ return true ;
408+ }
409+ } // namespace
410+
224411 // =====================================================================
225412 // DateTime class
226413 // =====================================================================
@@ -700,15 +887,24 @@ namespace nfx::time
700887
701888 bool DateTime::fromString ( std::string_view iso8601String, DateTime& result ) noexcept
702889 {
703- // ISO 8601 parser using std::from_chars
704- // Supports: YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss, YYYY-MM-DDTHH:mm:ssZ,
705- // YYYY-MM-DDTHH:mm:ss.f, YYYY-MM-DDTHH:mm:ss.fffffffZ, etc.
890+ // Fast empty/length check
706891 if ( iso8601String.empty () || iso8601String.length () < 10 )
707892 {
708893 return false ;
709894 }
710895
711- // Remove trailing 'Z' if present
896+ // Try fast-path parser first (handles 95% of real-world cases)
897+ // Supports: YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss, YYYY-MM-DDTHH:mm:ssZ,
898+ // YYYY-MM-DDTHH:mm:ss.f, YYYY-MM-DDTHH:mm:ss.fffffffZ
899+ if ( tryParseFastPath ( iso8601String, result ) )
900+ {
901+ return true ;
902+ }
903+
904+ // Fallback to flexible parser for non-standard formats
905+ // (handles timezone offsets, variable digit counts, etc.)
906+
907+ // Remove trailing 'Z' if present (for flexible parser compatibility)
712908 if ( iso8601String.back () == ' Z' )
713909 {
714910 iso8601String.remove_suffix ( 1 );
0 commit comments