@@ -124,7 +124,7 @@ def self.year_and_day(day_count)
124
124
year = self . guess_year ( day_count )
125
125
year_days = self . year_days ( year - 1 )
126
126
127
- # rewind the guess as needed
127
+ # rewind the guess as needed, typically 0 or 1x, rarely 2x
128
128
while year > MIN_Y and year_days >= day_count
129
129
year -= 1
130
130
year_days -= self . annual_days ( year )
@@ -146,12 +146,61 @@ def self.to_ordinal(year, month, day)
146
146
147
147
# convert days since epoch back to Date
148
148
def self . from_ordinal ( day_count )
149
+ year , month , day = Date . to_ymd_flt ( day_count )
150
+ Date . new ( year :, month :, day :, ordinal_day : day_count )
151
+ end
152
+
153
+ # use floating point and heuristic, very efficient
154
+ def self . to_ymd_flt ( day_count )
149
155
unless day_count > 0
150
156
raise ( RuntimeError , "day_count should be positive: #{ day_count } " )
151
157
end
152
- year , annual_days = self . year_and_day ( day_count )
153
- month , day = self . month_and_day ( annual_days , year )
154
- Date . new ( year :, month :, day :, ordinal_day : day_count )
158
+ year = self . guess_year ( day_count )
159
+ year_days = self . year_days ( year - 1 )
160
+
161
+ # rewind the guess as needed, typically 0 or 1x, rarely 2x
162
+ while year > MIN_Y and year_days >= day_count
163
+ year -= 1
164
+ year_days -= self . annual_days ( year )
165
+ end
166
+
167
+ month , day = self . month_and_day ( day_count - year_days , year )
168
+ [ year , month , day ]
169
+ end
170
+
171
+ # use pure divmod arithmetic and integers; constant time; ~same~ efficiency
172
+ def self . to_ymd_int ( day_count )
173
+ unless day_count > 0
174
+ raise ( RuntimeError , "day_count should be positive: #{ day_count } " )
175
+ end
176
+
177
+ # Convert to 0-based day count for easier math
178
+ days = day_count - 1
179
+
180
+ # 400 year cycle
181
+ n400 , days = days . divmod ( DAYS_400 )
182
+
183
+ # 100-year cycle
184
+ n100 = days / DAYS_100
185
+ n100 = 3 if n100 == 4
186
+ days -= ( n100 * DAYS_100 )
187
+
188
+ # 4-year cycle
189
+ n4 , days = days . divmod ( DAYS_4 )
190
+
191
+ # remaining years
192
+ n1 = days / ANNUAL_DAYS
193
+ n1 = 3 if n1 == 4
194
+
195
+ # remaining days
196
+ days -= ( n1 * ANNUAL_DAYS )
197
+
198
+ # determine the year
199
+ year = 1 + ( n400 * 400 ) + ( n100 * 100 ) + ( n4 * 4 ) + n1
200
+
201
+ # add 1 back to 1-based day_count to determine the month and day
202
+ month , day = self . month_and_day ( days + 1 , year )
203
+ [ year , month , day ]
155
204
end
156
205
157
206
#
@@ -205,14 +254,13 @@ def initialize(year:, month:, day:, ordinal_day: nil)
205
254
max_days = Date . month_days ( month , year )
206
255
raise ( InvalidDay , day ) unless ( MIN_D ..max_days ) . cover? ( day )
207
256
208
- @leap_year = Date . leap_year? ( year )
257
+ # initialize
209
258
@ordinal_day = ordinal_day || Date . to_ordinal ( year , month , day )
210
-
211
259
super ( year :, month :, day :)
212
260
end
213
261
214
262
def leap_year?
215
- @ leap_year
263
+ Date . leap_year? ( year )
216
264
end
217
265
218
266
def <=>( other )
0 commit comments