@@ -452,11 +452,43 @@ do {\
452452static int c_valid_civil_p (int , int , int , double ,
453453 int * , int * , int * , int * );
454454
455+ /* Check if using pure Gregorian calendar (sg == -Infinity) */
456+ #define c_gregorian_only_p (sg ) (isinf(sg) && (sg) < 0)
457+
458+ /*
459+ * Fast path macros for pure Gregorian calendar.
460+ * Sets *rjd to the JD value, *ns to 1 (new style), and returns.
461+ */
462+ #define GREGORIAN_JD_FAST_PATH_RET (sg , jd_expr , rjd , ns ) \
463+ if (c_gregorian_only_p(sg)) { \
464+ *(rjd) = (jd_expr); \
465+ *(ns) = 1; \
466+ return 1; \
467+ }
468+
469+ #define GREGORIAN_JD_FAST_PATH (sg , jd_expr , rjd , ns ) \
470+ if (c_gregorian_only_p(sg)) { \
471+ *(rjd) = (jd_expr); \
472+ *(ns) = 1; \
473+ return; \
474+ }
475+
476+ /* Forward declarations for Neri-Schneider optimized functions */
477+ static int c_gregorian_civil_to_jd (int y , int m , int d );
478+ static void c_gregorian_jd_to_civil (int jd , int * ry , int * rm , int * rd );
479+ static int c_gregorian_fdoy (int y );
480+ static int c_gregorian_ldoy (int y );
481+ static int c_gregorian_ldom_jd (int y , int m );
482+ static int ns_jd_in_range (int jd );
483+
455484static int
456485c_find_fdoy (int y , double sg , int * rjd , int * ns )
457486{
458487 int d , rm , rd ;
459488
489+ GREGORIAN_JD_FAST_PATH_RET (sg , c_gregorian_fdoy (y ), rjd , ns );
490+
491+ /* Keep existing loop for Julian/reform period */
460492 for (d = 1 ; d < 31 ; d ++ )
461493 if (c_valid_civil_p (y , 1 , d , sg , & rm , & rd , rjd , ns ))
462494 return 1 ;
@@ -468,6 +500,9 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns)
468500{
469501 int i , rm , rd ;
470502
503+ GREGORIAN_JD_FAST_PATH_RET (sg , c_gregorian_ldoy (y ), rjd , ns );
504+
505+ /* Keep existing loop for Julian/reform period */
471506 for (i = 0 ; i < 30 ; i ++ )
472507 if (c_valid_civil_p (y , 12 , 31 - i , sg , & rm , & rd , rjd , ns ))
473508 return 1 ;
@@ -493,6 +528,9 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns)
493528{
494529 int i , rm , rd ;
495530
531+ GREGORIAN_JD_FAST_PATH_RET (sg , c_gregorian_ldom_jd (y , m ), rjd , ns );
532+
533+ /* Keep existing loop for Julian/reform period */
496534 for (i = 0 ; i < 30 ; i ++ )
497535 if (c_valid_civil_p (y , m , 31 - i , sg , & rm , & rd , rjd , ns ))
498536 return 1 ;
@@ -502,55 +540,69 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns)
502540static void
503541c_civil_to_jd (int y , int m , int d , double sg , int * rjd , int * ns )
504542{
505- double a , b , jd ;
543+ int jd ;
544+
545+ GREGORIAN_JD_FAST_PATH (sg , c_gregorian_civil_to_jd (y , m , d ), rjd , ns );
546+
547+ /* Calculate Gregorian JD using optimized algorithm */
548+ jd = c_gregorian_civil_to_jd (y , m , d );
506549
507- if (m <= 2 ) {
508- y -= 1 ;
509- m += 12 ;
510- }
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 ;
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 ((c_gregorian_only_p (sg ) || 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,147 @@ 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_EPOCH 1721120
793+
794+ /* Days in a 4-year cycle (3 normal years + 1 leap year) */
795+ #define NS_DAYS_IN_4_YEARS 1461
796+
797+ /* Days in a 400-year Gregorian cycle (97 leap years in 400 years) */
798+ #define NS_DAYS_IN_400_YEARS 146097
799+
800+ /* Years per century */
801+ #define NS_YEARS_PER_CENTURY 100
802+
803+ /*
804+ * Multiplier for extracting year within century using fixed-point arithmetic.
805+ * This is ceil(2^32 / NS_DAYS_IN_4_YEARS) for the Euclidean affine function.
806+ */
807+ #define NS_YEAR_MULTIPLIER 2939745
808+
809+ /*
810+ * Coefficients for month calculation from day-of-year.
811+ * Maps day-of-year to month using: month = (NS_MONTH_COEFF * doy + NS_MONTH_OFFSET) >> 16
812+ */
813+ #define NS_MONTH_COEFF 2141
814+ #define NS_MONTH_OFFSET 197913
815+
816+ /*
817+ * Coefficients for civil date to JDN month contribution.
818+ * Maps month to accumulated days: days = (NS_CIVIL_MONTH_COEFF * m - NS_CIVIL_MONTH_OFFSET) / 32
819+ */
820+ #define NS_CIVIL_MONTH_COEFF 979
821+ #define NS_CIVIL_MONTH_OFFSET 2919
822+ #define NS_CIVIL_MONTH_DIVISOR 32
823+
824+ /* Days from March 1 to December 31 (for Jan/Feb year adjustment) */
825+ #define NS_DAYS_BEFORE_NEW_YEAR 306
826+
827+ /*
828+ * Safe bounds for Neri-Schneider algorithm to avoid integer overflow.
829+ * These correspond to approximately years -1,000,000 to +1,000,000.
830+ */
831+ #define NS_JD_MIN -364000000
832+ #define NS_JD_MAX 538000000
833+
834+ inline static int
835+ ns_jd_in_range (int jd )
836+ {
837+ return jd >= NS_JD_MIN && jd <= NS_JD_MAX ;
838+ }
839+
840+ /* Optimized: Gregorian date -> Julian Day Number */
841+ static int
842+ c_gregorian_civil_to_jd (int y , int m , int d )
843+ {
844+ /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */
845+ int j = (m < 3 ) ? 1 : 0 ;
846+ int y0 = y - j ;
847+ int m0 = j ? m + 12 : m ;
848+ int d0 = d - 1 ;
849+
850+ /* Calculate year contribution with leap year correction */
851+ int q1 = DIV (y0 , NS_YEARS_PER_CENTURY );
852+ int yc = DIV (NS_DAYS_IN_4_YEARS * y0 , 4 ) - q1 + DIV (q1 , 4 );
853+
854+ /* Calculate month contribution using integer arithmetic */
855+ int mc = (NS_CIVIL_MONTH_COEFF * m0 - NS_CIVIL_MONTH_OFFSET ) / NS_CIVIL_MONTH_DIVISOR ;
856+
857+ /* Combine and add epoch offset to get JDN */
858+ return yc + mc + d0 + NS_EPOCH ;
859+ }
860+
861+ /* Optimized: Julian Day Number -> Gregorian date */
862+ static void
863+ c_gregorian_jd_to_civil (int jd , int * ry , int * rm , int * rd )
864+ {
865+ int r0 , n1 , q1 , r1 , n2 , q2 , r2 , n3 , q3 , r3 , y0 , j ;
866+ uint64_t u2 ;
867+
868+ /* Convert JDN to rata die (March 1, Year 0 epoch) */
869+ r0 = jd - NS_EPOCH ;
870+
871+ /* Extract century and day within 400-year cycle */
872+ /* Use Euclidean (floor) division for negative values */
873+ n1 = 4 * r0 + 3 ;
874+ q1 = DIV (n1 , NS_DAYS_IN_400_YEARS );
875+ r1 = MOD (n1 , NS_DAYS_IN_400_YEARS ) / 4 ;
876+
877+ /* Calculate year within century and day of year */
878+ n2 = 4 * r1 + 3 ;
879+ /* Use 64-bit arithmetic to avoid overflow */
880+ u2 = (uint64_t )NS_YEAR_MULTIPLIER * (uint64_t )n2 ;
881+ q2 = (int )(u2 >> 32 );
882+ r2 = (int )((uint32_t )u2 / NS_YEAR_MULTIPLIER / 4 );
883+
884+ /* Calculate month and day using integer arithmetic */
885+ n3 = NS_MONTH_COEFF * r2 + NS_MONTH_OFFSET ;
886+ q3 = n3 >> 16 ;
887+ r3 = (n3 & 0xFFFF ) / NS_MONTH_COEFF ;
888+
889+ /* Combine century and year */
890+ y0 = NS_YEARS_PER_CENTURY * q1 + q2 ;
891+
892+ /* Adjust for January/February (shift from fiscal year) */
893+ j = (r2 >= NS_DAYS_BEFORE_NEW_YEAR ) ? 1 : 0 ;
894+
895+ * ry = y0 + j ;
896+ * rm = j ? q3 - 12 : q3 ;
897+ * rd = r3 + 1 ;
898+ }
899+
900+ /* O(1) first day of year for Gregorian calendar */
901+ inline static int
902+ c_gregorian_fdoy (int y )
903+ {
904+ return c_gregorian_civil_to_jd (y , 1 , 1 );
905+ }
906+
907+ /* O(1) last day of year for Gregorian calendar */
908+ inline static int
909+ c_gregorian_ldoy (int y )
910+ {
911+ return c_gregorian_civil_to_jd (y , 12 , 31 );
912+ }
913+
914+ /* O(1) last day of month (JDN) for Gregorian calendar */
915+ inline static int
916+ c_gregorian_ldom_jd (int y , int m )
917+ {
918+ return c_gregorian_civil_to_jd (y , m , c_gregorian_last_day_of_month (y , m ));
919+ }
920+
728921static int
729922c_valid_julian_p (int y , int m , int d , int * rm , int * rd )
730923{
0 commit comments