77#include "ruby/util.h"
88#include <math.h>
99#include <time.h>
10+ #include <stdint.h> /* For uint64_t in Neri-Schneider algorithm */
1011#if defined(HAVE_SYS_TIME_H )
1112#include <sys/time.h>
1213#endif
@@ -452,11 +453,27 @@ do {\
452453static int c_valid_civil_p (int , int , int , double ,
453454 int * , int * , int * , int * );
454455
456+ /* Forward declarations for Neri-Schneider optimized functions */
457+ static int c_gregorian_civil_to_jd (int y , int m , int d );
458+ static void c_gregorian_jd_to_civil (int jd , int * ry , int * rm , int * rd );
459+ inline static int c_gregorian_fdoy (int y );
460+ inline static int c_gregorian_ldoy (int y );
461+ inline static int c_gregorian_ldom_jd (int y , int m );
462+ inline static int ns_jd_in_range (int jd );
463+
455464static int
456465c_find_fdoy (int y , double sg , int * rjd , int * ns )
457466{
458467 int d , rm , rd ;
459468
469+ /* Fast path: pure Gregorian calendar */
470+ if (isinf (sg ) && sg < 0 ) {
471+ * rjd = c_gregorian_fdoy (y );
472+ * ns = 1 ;
473+ return 1 ;
474+ }
475+
476+ /* Keep existing loop for Julian/reform period */
460477 for (d = 1 ; d < 31 ; d ++ )
461478 if (c_valid_civil_p (y , 1 , d , sg , & rm , & rd , rjd , ns ))
462479 return 1 ;
@@ -468,6 +485,14 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns)
468485{
469486 int i , rm , rd ;
470487
488+ /* Fast path: pure Gregorian calendar */
489+ if (isinf (sg ) && sg < 0 ) {
490+ * rjd = c_gregorian_ldoy (y );
491+ * ns = 1 ;
492+ return 1 ;
493+ }
494+
495+ /* Keep existing loop for Julian/reform period */
471496 for (i = 0 ; i < 30 ; i ++ )
472497 if (c_valid_civil_p (y , 12 , 31 - i , sg , & rm , & rd , rjd , ns ))
473498 return 1 ;
@@ -493,6 +518,14 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns)
493518{
494519 int i , rm , rd ;
495520
521+ /* Fast path: pure Gregorian calendar */
522+ if (isinf (sg ) && sg < 0 ) {
523+ * rjd = c_gregorian_ldom_jd (y , m );
524+ * ns = 1 ;
525+ return 1 ;
526+ }
527+
528+ /* Keep existing loop for Julian/reform period */
496529 for (i = 0 ; i < 30 ; i ++ )
497530 if (c_valid_civil_p (y , m , 31 - i , sg , & rm , & rd , rjd , ns ))
498531 return 1 ;
@@ -502,55 +535,74 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns)
502535static void
503536c_civil_to_jd (int y , int m , int d , double sg , int * rjd , int * ns )
504537{
505- double a , b , jd ;
538+ int jd ;
506539
507- if (m <= 2 ) {
508- y -= 1 ;
509- m += 12 ;
540+ /* Fast path: pure Gregorian calendar (sg == -infinity) */
541+ if (isinf (sg ) && sg < 0 ) {
542+ * rjd = c_gregorian_civil_to_jd (y , m , d );
543+ * ns = 1 ;
544+ return ;
510545 }
511- a = floor (y / 100.0 );
512- b = 2 - a + floor (a / 4.0 );
513- jd = floor (365.25 * (y + 4716 )) +
514- floor (30.6001 * (m + 1 )) +
515- d + b - 1524 ;
546+
547+ /* Calculate Gregorian JD using optimized algorithm */
548+ jd = c_gregorian_civil_to_jd (y , m , d );
549+
516550 if (jd < sg ) {
517- jd -= b ;
551+ /* Before Gregorian switchover - use Julian calendar */
552+ int y2 = y , m2 = m ;
553+ if (m2 <= 2 ) {
554+ y2 -= 1 ;
555+ m2 += 12 ;
556+ }
557+ jd = (int )(floor (365.25 * (y2 + 4716 )) +
558+ floor (30.6001 * (m2 + 1 )) +
559+ d - 1524 );
518560 * ns = 0 ;
519561 }
520- else
562+ else {
521563 * ns = 1 ;
564+ }
522565
523- * rjd = ( int ) jd ;
566+ * rjd = jd ;
524567}
525568
526569static void
527570c_jd_to_civil (int jd , double sg , int * ry , int * rm , int * rdom )
528571{
529- double x , a , b , c , d , e , y , m , dom ;
530-
531- if (jd < sg )
532- a = jd ;
533- else {
534- x = floor ((jd - 1867216.25 ) / 36524.25 );
535- a = jd + 1 + x - floor (x / 4.0 );
536- }
537- b = a + 1524 ;
538- c = floor ((b - 122.1 ) / 365.25 );
539- d = floor (365.25 * c );
540- e = floor ((b - d ) / 30.6001 );
541- dom = b - d - floor (30.6001 * e );
542- if (e <= 13 ) {
543- m = e - 1 ;
544- y = c - 4716 ;
545- }
546- else {
547- m = e - 13 ;
548- y = c - 4715 ;
572+ /* Fast path: pure Gregorian or date after switchover, within safe range */
573+ if (((isinf (sg ) && sg < 0 ) || jd >= sg ) && ns_jd_in_range (jd )) {
574+ c_gregorian_jd_to_civil (jd , ry , rm , rdom );
575+ return ;
549576 }
550577
551- * ry = (int )y ;
552- * rm = (int )m ;
553- * rdom = (int )dom ;
578+ /* Original algorithm for Julian calendar or extreme dates */
579+ {
580+ double x , a , b , c , d , e , y , m , dom ;
581+
582+ if (jd < sg )
583+ a = jd ;
584+ else {
585+ x = floor ((jd - 1867216.25 ) / 36524.25 );
586+ a = jd + 1 + x - floor (x / 4.0 );
587+ }
588+ b = a + 1524 ;
589+ c = floor ((b - 122.1 ) / 365.25 );
590+ d = floor (365.25 * c );
591+ e = floor ((b - d ) / 30.6001 );
592+ dom = b - d - floor (30.6001 * e );
593+ if (e <= 13 ) {
594+ m = e - 1 ;
595+ y = c - 4716 ;
596+ }
597+ else {
598+ m = e - 13 ;
599+ y = c - 4715 ;
600+ }
601+
602+ * ry = (int )y ;
603+ * rm = (int )m ;
604+ * rdom = (int )dom ;
605+ }
554606}
555607
556608static void
@@ -725,6 +777,113 @@ c_gregorian_last_day_of_month(int y, int m)
725777 return monthtab [c_gregorian_leap_p (y ) ? 1 : 0 ][m ];
726778}
727779
780+ /*
781+ * Neri-Schneider algorithm for optimized Gregorian date conversion.
782+ * Reference: Neri & Schneider, "Euclidean Affine Functions and Applications
783+ * to Calendar Algorithms", Software: Practice and Experience, 2023.
784+ * https://arxiv.org/abs/2102.06959
785+ *
786+ * This algorithm provides ~2-3x speedup over traditional floating-point
787+ * implementations by using pure integer arithmetic with multiplication
788+ * and bit-shifts instead of expensive division operations.
789+ */
790+
791+ /* JDN of March 1, Year 0 in proleptic Gregorian calendar */
792+ #define NS_GREGORIAN_EPOCH 1721120
793+
794+ /*
795+ * Safe bounds for Neri-Schneider algorithm to avoid integer overflow.
796+ * These correspond to approximately years -1,000,000 to +1,000,000.
797+ */
798+ #define NS_JD_MIN -364000000
799+ #define NS_JD_MAX 538000000
800+
801+ inline static int
802+ ns_jd_in_range (int jd )
803+ {
804+ return jd >= NS_JD_MIN && jd <= NS_JD_MAX ;
805+ }
806+
807+ /* Optimized: Gregorian date -> Julian Day Number */
808+ static int
809+ c_gregorian_civil_to_jd (int y , int m , int d )
810+ {
811+ /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */
812+ int j = (m < 3 ) ? 1 : 0 ;
813+ int y0 = y - j ;
814+ int m0 = j ? m + 12 : m ;
815+ int d0 = d - 1 ;
816+
817+ /* Calculate year contribution with leap year correction */
818+ int q1 = DIV (y0 , 100 );
819+ int yc = DIV (1461 * y0 , 4 ) - q1 + DIV (q1 , 4 );
820+
821+ /* Calculate month contribution using integer arithmetic */
822+ int mc = (979 * m0 - 2919 ) / 32 ;
823+
824+ /* Combine and add epoch offset to get JDN */
825+ return yc + mc + d0 + NS_GREGORIAN_EPOCH ;
826+ }
827+
828+ /* Optimized: Julian Day Number -> Gregorian date */
829+ static void
830+ c_gregorian_jd_to_civil (int jd , int * ry , int * rm , int * rd )
831+ {
832+ int r0 , n1 , q1 , r1 , n2 , q2 , r2 , n3 , q3 , r3 , y0 , j ;
833+ uint64_t u2 ;
834+
835+ /* Convert JDN to rata die (March 1, Year 0 epoch) */
836+ r0 = jd - NS_GREGORIAN_EPOCH ;
837+
838+ /* Extract century and day within 400-year cycle */
839+ /* Use Euclidean (floor) division for negative values */
840+ n1 = 4 * r0 + 3 ;
841+ q1 = DIV (n1 , 146097 ); /* Century */
842+ r1 = MOD (n1 , 146097 ) / 4 ; /* Day within 400-year cycle */
843+
844+ /* Use 64-bit arithmetic for year calculation within century */
845+ n2 = 4 * r1 + 3 ;
846+ u2 = (uint64_t )2939745 * (uint64_t )n2 ;
847+ q2 = (int )(u2 >> 32 ); /* Year within century */
848+ r2 = (int )((uint32_t )u2 / 2939745 / 4 ); /* Day of year */
849+
850+ /* Calculate month and day using integer arithmetic */
851+ n3 = 2141 * r2 + 197913 ;
852+ q3 = n3 >> 16 ; /* Month (3-14) */
853+ r3 = (n3 & 0xFFFF ) / 2141 ; /* Day of month (0-based) */
854+
855+ /* Combine century and year */
856+ y0 = 100 * q1 + q2 ;
857+
858+ /* Adjust for January/February (shift from fiscal year) */
859+ j = (r2 >= 306 ) ? 1 : 0 ;
860+
861+ * ry = y0 + j ;
862+ * rm = j ? q3 - 12 : q3 ;
863+ * rd = r3 + 1 ;
864+ }
865+
866+ /* O(1) first day of year for Gregorian calendar */
867+ inline static int
868+ c_gregorian_fdoy (int y )
869+ {
870+ return c_gregorian_civil_to_jd (y , 1 , 1 );
871+ }
872+
873+ /* O(1) last day of year for Gregorian calendar */
874+ inline static int
875+ c_gregorian_ldoy (int y )
876+ {
877+ return c_gregorian_civil_to_jd (y , 12 , 31 );
878+ }
879+
880+ /* O(1) last day of month (JDN) for Gregorian calendar */
881+ inline static int
882+ c_gregorian_ldom_jd (int y , int m )
883+ {
884+ return c_gregorian_civil_to_jd (y , m , c_gregorian_last_day_of_month (y , m ));
885+ }
886+
728887static int
729888c_valid_julian_p (int y , int m , int d , int * rm , int * rd )
730889{
0 commit comments