|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +# Constants |
| 4 | +class Date |
| 5 | + HAVE_JD = 0b00000001 # 1 |
| 6 | + HAVE_DF = 0b00000010 # 2 |
| 7 | + HAVE_CIVIL = 0b00000100 # 4 |
| 8 | + HAVE_TIME = 0b00001000 # 8 |
| 9 | + COMPLEX_DAT = 0b10000000 # 128 |
| 10 | + private_constant :HAVE_JD, :HAVE_DF, :HAVE_CIVIL, :HAVE_TIME, :COMPLEX_DAT |
| 11 | + |
| 12 | + MONTHNAMES = [nil, "January", "February", "March", "April", "May", "June", |
| 13 | + "July", "August", "September", "October", "November", "December"] |
| 14 | + .map { |s| s&.encode(Encoding::US_ASCII)&.freeze }.freeze |
| 15 | + ABBR_MONTHNAMES = [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| 16 | + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
| 17 | + .map { |s| s&.encode(Encoding::US_ASCII)&.freeze }.freeze |
| 18 | + DAYNAMES = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday] |
| 19 | + .map { |s| s.encode(Encoding::US_ASCII).freeze }.freeze |
| 20 | + ABBR_DAYNAMES = %w[Sun Mon Tue Wed Thu Fri Sat] |
| 21 | + .map { |s| s.encode(Encoding::US_ASCII).freeze }.freeze |
| 22 | + |
| 23 | + # Pattern constants for regex |
| 24 | + ABBR_DAYS_PATTERN = 'sun|mon|tue|wed|thu|fri|sat' |
| 25 | + DAYS_PATTERN = 'sunday|monday|tuesday|wednesday|thursday|friday|saturday' |
| 26 | + ABBR_MONTHS_PATTERN = 'jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec' |
| 27 | + private_constant :ABBR_DAYS_PATTERN, :DAYS_PATTERN, :ABBR_MONTHS_PATTERN |
| 28 | + |
| 29 | + ITALY = 2299161 # 1582-10-15 |
| 30 | + ENGLAND = 2361222 # 1752-09-14 |
| 31 | + JULIAN = Float::INFINITY |
| 32 | + GREGORIAN = -Float::INFINITY |
| 33 | + |
| 34 | + DEFAULT_SG = ITALY |
| 35 | + private_constant :DEFAULT_SG |
| 36 | + |
| 37 | + MINUTE_IN_SECONDS = 60 |
| 38 | + HOUR_IN_SECONDS = 3600 |
| 39 | + DAY_IN_SECONDS = 86400 |
| 40 | + HALF_DAYS_IN_SECONDS = DAY_IN_SECONDS / 2 |
| 41 | + SECOND_IN_MILLISECONDS = 1000 |
| 42 | + SECOND_IN_NANOSECONDS = 1_000_000_000 |
| 43 | + private_constant :MINUTE_IN_SECONDS, :HOUR_IN_SECONDS, :DAY_IN_SECONDS, :SECOND_IN_MILLISECONDS, :SECOND_IN_NANOSECONDS, :HALF_DAYS_IN_SECONDS |
| 44 | + |
| 45 | + JC_PERIOD0 = 1461 # 365.25 * 4 |
| 46 | + GC_PERIOD0 = 146097 # 365.2425 * 400 |
| 47 | + CM_PERIOD0 = 71149239 # (lcm 7 1461 146097) |
| 48 | + CM_PERIOD = (0xfffffff / CM_PERIOD0) * CM_PERIOD0 |
| 49 | + CM_PERIOD_JCY = (CM_PERIOD / JC_PERIOD0) * 4 |
| 50 | + CM_PERIOD_GCY = (CM_PERIOD / GC_PERIOD0) * 400 |
| 51 | + private_constant :JC_PERIOD0, :GC_PERIOD0, :CM_PERIOD0, :CM_PERIOD, :CM_PERIOD_JCY, :CM_PERIOD_GCY |
| 52 | + |
| 53 | + REFORM_BEGIN_YEAR = 1582 |
| 54 | + REFORM_END_YEAR = 1930 |
| 55 | + REFORM_BEGIN_JD = 2298874 # ns 1582-01-01 |
| 56 | + REFORM_END_JD = 2426355 # os 1930-12-31 |
| 57 | + private_constant :REFORM_BEGIN_YEAR, :REFORM_END_YEAR, :REFORM_BEGIN_JD, :REFORM_END_JD |
| 58 | + |
| 59 | + SEC_WIDTH = 6 |
| 60 | + MIN_WIDTH = 6 |
| 61 | + HOUR_WIDTH = 5 |
| 62 | + MDAY_WIDTH = 5 |
| 63 | + MON_WIDTH = 4 |
| 64 | + private_constant :SEC_WIDTH, :MIN_WIDTH, :HOUR_WIDTH, :MDAY_WIDTH, :MON_WIDTH |
| 65 | + |
| 66 | + SEC_SHIFT = 0 |
| 67 | + MIN_SHIFT = SEC_WIDTH |
| 68 | + HOUR_SHIFT = MIN_WIDTH + SEC_WIDTH |
| 69 | + MDAY_SHIFT = HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH |
| 70 | + MON_SHIFT = MDAY_WIDTH + HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH |
| 71 | + private_constant :SEC_SHIFT, :MIN_SHIFT, :HOUR_SHIFT, :MDAY_SHIFT, :MON_SHIFT |
| 72 | + |
| 73 | + PK_MASK = ->(x) { (1 << x) - 1 } |
| 74 | + private_constant :PK_MASK |
| 75 | + |
| 76 | + # Days in each month (non-leap and leap year) |
| 77 | + MONTH_DAYS = [ |
| 78 | + [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], # non-leap |
| 79 | + [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # leap |
| 80 | + ].freeze |
| 81 | + private_constant :MONTH_DAYS |
| 82 | + |
| 83 | + YEARTAB = [ |
| 84 | + [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], # non-leap |
| 85 | + [0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] # leap |
| 86 | + ].freeze |
| 87 | + private_constant :YEARTAB |
| 88 | + |
| 89 | + # Neri-Schneider algorithm constants |
| 90 | + # JDN of March 1, Year 0 in proleptic Gregorian calendar |
| 91 | + NS_EPOCH = 1721120 |
| 92 | + private_constant :NS_EPOCH |
| 93 | + |
| 94 | + # Days in a 4-year cycle (3 normal years + 1 leap year) |
| 95 | + NS_DAYS_IN_4_YEARS = 1461 |
| 96 | + private_constant :NS_DAYS_IN_4_YEARS |
| 97 | + |
| 98 | + # Days in a 400-year Gregorian cycle (97 leap years in 400 years) |
| 99 | + NS_DAYS_IN_400_YEARS = 146097 |
| 100 | + private_constant :NS_DAYS_IN_400_YEARS |
| 101 | + |
| 102 | + # Years per century |
| 103 | + NS_YEARS_PER_CENTURY = 100 |
| 104 | + private_constant :NS_YEARS_PER_CENTURY |
| 105 | + |
| 106 | + # Multiplier for extracting year within century using fixed-point arithmetic. |
| 107 | + # This is ceil(2^32 / NS_DAYS_IN_4_YEARS) for the Euclidean affine function. |
| 108 | + NS_YEAR_MULTIPLIER = 2939745 |
| 109 | + private_constant :NS_YEAR_MULTIPLIER |
| 110 | + |
| 111 | + # Coefficients for month calculation from day-of-year. |
| 112 | + # Maps day-of-year to month using: month = (NS_MONTH_COEFF * doy + NS_MONTH_OFFSET) >> 16 |
| 113 | + NS_MONTH_COEFF = 2141 |
| 114 | + NS_MONTH_OFFSET = 197913 |
| 115 | + private_constant :NS_MONTH_COEFF, :NS_MONTH_OFFSET |
| 116 | + |
| 117 | + # Coefficients for civil date to JDN month contribution. |
| 118 | + # Maps month to accumulated days: days = (NS_CIVIL_MONTH_COEFF * m - NS_CIVIL_MONTH_OFFSET) / 32 |
| 119 | + NS_CIVIL_MONTH_COEFF = 979 |
| 120 | + NS_CIVIL_MONTH_OFFSET = 2919 |
| 121 | + NS_CIVIL_MONTH_DIVISOR = 32 |
| 122 | + private_constant :NS_CIVIL_MONTH_COEFF, :NS_CIVIL_MONTH_OFFSET, :NS_CIVIL_MONTH_DIVISOR |
| 123 | + |
| 124 | + # Days from March 1 to December 31 (for Jan/Feb year adjustment) |
| 125 | + NS_DAYS_BEFORE_NEW_YEAR = 306 |
| 126 | + private_constant :NS_DAYS_BEFORE_NEW_YEAR |
| 127 | + |
| 128 | + # Safe bounds for Neri-Schneider algorithm to avoid integer overflow. |
| 129 | + # These correspond to approximately years -1,000,000 to +1,000,000. |
| 130 | + NS_JD_MIN = -364_000_000 |
| 131 | + NS_JD_MAX = 538_000_000 |
| 132 | + private_constant :NS_JD_MIN, :NS_JD_MAX |
| 133 | + |
| 134 | + JULIAN_EPOCH_DATE = "-4712-01-01" |
| 135 | + JULIAN_EPOCH_DATETIME = "-4712-01-01T00:00:00+00:00" |
| 136 | + JULIAN_EPOCH_DATETIME_RFC2822 = "Mon, 1 Jan -4712 00:00:00 +0000" |
| 137 | + JULIAN_EPOCH_DATETIME_HTTPDATE = "Mon, 01 Jan -4712 00:00:00 GMT" |
| 138 | + private_constant :JULIAN_EPOCH_DATE, :JULIAN_EPOCH_DATETIME, :JULIAN_EPOCH_DATETIME_RFC2822, :JULIAN_EPOCH_DATETIME_HTTPDATE |
| 139 | + |
| 140 | + JISX0301_ERA_INITIALS = 'mtshr' |
| 141 | + JISX0301_DEFAULT_ERA = 'H' # obsolete |
| 142 | + private_constant :JISX0301_ERA_INITIALS, :JISX0301_DEFAULT_ERA |
| 143 | + |
| 144 | + HAVE_ALPHA = 1 << 0 |
| 145 | + HAVE_DIGIT = 1 << 1 |
| 146 | + HAVE_DASH = 1 << 2 |
| 147 | + HAVE_DOT = 1 << 3 |
| 148 | + HAVE_SLASH = 1 << 4 |
| 149 | + private_constant :HAVE_ALPHA, :HAVE_DIGIT, :HAVE_DASH, :HAVE_DOT, :HAVE_SLASH |
| 150 | + |
| 151 | + # C: default strftime format is US-ASCII |
| 152 | + STRFTIME_DEFAULT_FMT = '%F'.encode(Encoding::US_ASCII) |
| 153 | + private_constant :STRFTIME_DEFAULT_FMT |
| 154 | + |
| 155 | + # strftime spec categories |
| 156 | + NUMERIC_SPECS = %w[Y C y m d j H I M S L N G g U W V u w s Q].freeze |
| 157 | + SPACE_PAD_SPECS = %w[e k l].freeze |
| 158 | + CHCASE_UPPER_SPECS = %w[A a B b h].freeze |
| 159 | + CHCASE_LOWER_SPECS = %w[Z p].freeze |
| 160 | + private_constant :NUMERIC_SPECS, :SPACE_PAD_SPECS, |
| 161 | + :CHCASE_UPPER_SPECS, :CHCASE_LOWER_SPECS |
| 162 | + |
| 163 | + # strptime digit-consuming specs |
| 164 | + NUM_PATTERN_SPECS = "CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy" |
| 165 | + private_constant :NUM_PATTERN_SPECS |
| 166 | + |
| 167 | + # Fragment completion table for DateTime parsing |
| 168 | + COMPLETE_FRAGS_TABLE = [ |
| 169 | + [:time, [:hour, :min, :sec].freeze], |
| 170 | + [nil, [:jd].freeze], |
| 171 | + [:ordinal, [:year, :yday, :hour, :min, :sec].freeze], |
| 172 | + [:civil, [:year, :mon, :mday, :hour, :min, :sec].freeze], |
| 173 | + [:commercial, [:cwyear, :cweek, :cwday, :hour, :min, :sec].freeze], |
| 174 | + [:wday, [:wday, :hour, :min, :sec].freeze], |
| 175 | + [:wnum0, [:year, :wnum0, :wday, :hour, :min, :sec].freeze], |
| 176 | + [:wnum1, [:year, :wnum1, :wday, :hour, :min, :sec].freeze], |
| 177 | + [nil, [:cwyear, :cweek, :wday, :hour, :min, :sec].freeze], |
| 178 | + [nil, [:year, :wnum0, :cwday, :hour, :min, :sec].freeze], |
| 179 | + [nil, [:year, :wnum1, :cwday, :hour, :min, :sec].freeze], |
| 180 | + ].each { |a| a.freeze }.freeze |
| 181 | + private_constant :COMPLETE_FRAGS_TABLE |
| 182 | +end |
0 commit comments