lib/date.rb
DEFINITIONS
This source file includes following functions.
1 # date.rb: Written by Tadayoshi Funaba 1998-2002
2 # $Id: date.rb,v 2.8 2002-06-08 00:39:51+09 tadf Exp $
3
4 require 'rational'
5 require 'date/format'
6
7 class Date
8
9 include Comparable
10
11 MONTHNAMES = [nil] + %w(January February March April May June July
12 August September October November December)
13
14 DAYNAMES = %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
15
16 ABBR_MONTHNAMES = [nil] + %w(Jan Feb Mar Apr May Jun
17 Jul Aug Sep Oct Nov Dec)
18
19 ABBR_DAYNAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
20
21 ITALY = 2299161 # 1582-10-15
22 ENGLAND = 2361222 # 1752-09-14
23 JULIAN = false
24 GREGORIAN = true
25
26 def self.os? (jd, sg)
27 case sg
28 when Numeric; jd < sg
29 else; not sg
30 end
31 end
32
33 def self.ns? (jd, sg) not os?(jd, sg) end
34
35 def self.civil_to_jd(y, m, d, sg=GREGORIAN)
36 if m <= 2
37 y -= 1
38 m += 12
39 end
40 a = (y / 100.0).floor
41 b = 2 - a + (a / 4.0).floor
42 jd = (365.25 * (y + 4716)).floor +
43 (30.6001 * (m + 1)).floor +
44 d + b - 1524
45 if os?(jd, sg)
46 jd -= b
47 end
48 jd
49 end
50
51 def self.jd_to_civil(jd, sg=GREGORIAN)
52 if os?(jd, sg)
53 a = jd
54 else
55 x = ((jd - 1867216.25) / 36524.25).floor
56 a = jd + 1 + x - (x / 4.0).floor
57 end
58 b = a + 1524
59 c = ((b - 122.1) / 365.25).floor
60 d = (365.25 * c).floor
61 e = ((b - d) / 30.6001).floor
62 dom = b - d - (30.6001 * e).floor
63 if e <= 13
64 m = e - 1
65 y = c - 4716
66 else
67 m = e - 13
68 y = c - 4715
69 end
70 return y, m, dom
71 end
72
73 def self.ordinal_to_jd(y, d, sg=GREGORIAN)
74 civil_to_jd(y, 1, d, sg)
75 end
76
77 def self.jd_to_ordinal(jd, sg=GREGORIAN)
78 y = jd_to_civil(jd, sg)[0]
79 doy = jd - civil_to_jd(y - 1, 12, 31, ns?(jd, sg))
80 return y, doy
81 end
82
83 def self.jd_to_commercial(jd, sg=GREGORIAN)
84 ns = ns?(jd, sg)
85 a = jd_to_civil(jd - 3, ns)[0]
86 y = if jd >= commercial_to_jd(a + 1, 1, 1, ns) then a + 1 else a end
87 w = 1 + (jd - commercial_to_jd(y, 1, 1, ns)) / 7
88 d = (jd + 1) % 7
89 if d.zero? then d = 7 end
90 return y, w, d
91 end
92
93 def self.commercial_to_jd(y, w, d, ns=GREGORIAN)
94 jd = civil_to_jd(y, 1, 4, ns)
95 (jd - (((jd - 1) + 1) % 7)) +
96 7 * (w - 1) +
97 (d - 1)
98 end
99
100 %w(self.clfloor clfloor).each do |name|
101 module_eval <<-"end;"
102 def #{name}(x, y=1)
103 q, r = x.divmod(y)
104 q = q.to_i
105 return q, r
106 end
107 end;
108 end
109
110 private_class_method :clfloor
111 private :clfloor
112
113 def self.ajd_to_jd(ajd, of=0) clfloor(ajd + of + 1.to_r/2) end
114 def self.jd_to_ajd(jd, fr, of=0) jd + fr - of - 1.to_r/2 end
115
116 def self.day_fraction_to_time(fr)
117 h, fr = clfloor(fr, 1.to_r/24)
118 min, fr = clfloor(fr, 1.to_r/1440)
119 s, fr = clfloor(fr, 1.to_r/86400)
120 return h, min, s, fr
121 end
122
123 def self.time_to_day_fraction(h, min, s)
124 h.to_r/24 + min.to_r/1440 + s.to_r/86400
125 end
126
127 def self.amjd_to_ajd(amjd) amjd + 4800001.to_r/2 end
128 def self.ajd_to_amjd(ajd) ajd - 4800001.to_r/2 end
129 def self.mjd_to_jd(mjd) mjd + 2400001 end
130 def self.jd_to_mjd(jd) jd - 2400001 end
131 def self.ld_to_jd(ld) ld + 2299160 end
132 def self.jd_to_ld(jd) jd - 2299160 end
133
134 def self.jd_to_wday(jd) (jd + 1) % 7 end
135
136 def self.julian_leap? (y) y % 4 == 0 end
137 def self.gregorian_leap? (y) y % 4 == 0 and y % 100 != 0 or y % 400 == 0 end
138
139 class << self; alias_method :leap?, :gregorian_leap? end
140 class << self; alias_method :new0, :new end
141
142 def self.valid_jd? (jd, sg=ITALY) jd end
143
144 def self.jd(jd=0, sg=ITALY)
145 jd = valid_jd?(jd, sg)
146 new0(jd_to_ajd(jd, 0, 0), 0, sg)
147 end
148
149 def self.valid_ordinal? (y, d, sg=ITALY)
150 if d < 0
151 ny, = clfloor(y + 1, 1)
152 jd = ordinal_to_jd(ny, d + 1, sg)
153 ns = ns?(jd, sg)
154 return unless [y] == jd_to_ordinal(jd, sg)[0..0]
155 return unless [ny, 1] == jd_to_ordinal(jd - d, ns)
156 else
157 jd = ordinal_to_jd(y, d, sg)
158 return unless [y, d] == jd_to_ordinal(jd, sg)
159 end
160 jd
161 end
162
163 def self.ordinal(y=-4712, d=1, sg=ITALY)
164 unless jd = valid_ordinal?(y, d, sg)
165 raise ArgumentError, 'invalid date'
166 end
167 new0(jd_to_ajd(jd, 0, 0), 0, sg)
168 end
169
170 def self.valid_civil? (y, m, d, sg=ITALY)
171 if m < 0
172 m += 13
173 end
174 if d < 0
175 ny, nm = clfloor(y * 12 + m, 12)
176 nm, = clfloor(nm + 1, 1)
177 jd = civil_to_jd(ny, nm, d + 1, sg)
178 ns = ns?(jd, sg)
179 return unless [y, m] == jd_to_civil(jd, sg)[0..1]
180 return unless [ny, nm, 1] == jd_to_civil(jd - d, ns)
181 else
182 jd = civil_to_jd(y, m, d, sg)
183 return unless [y, m, d] == jd_to_civil(jd, sg)
184 end
185 jd
186 end
187
188 class << self; alias_method :valid_date?, :valid_civil? end
189
190 def self.civil(y=-4712, m=1, d=1, sg=ITALY)
191 unless jd = valid_civil?(y, m, d, sg)
192 raise ArgumentError, 'invalid date'
193 end
194 new0(jd_to_ajd(jd, 0, 0), 0, sg)
195 end
196
197 class << self; alias_method :new, :civil end
198
199 def self.valid_commercial? (y, w, d, sg=ITALY)
200 if d < 0
201 d += 8
202 end
203 if w < 0
204 w = jd_to_commercial(commercial_to_jd(y + 1, 1, 1) + w * 7)[1]
205 end
206 jd = commercial_to_jd(y, w, d)
207 return unless ns?(jd, sg)
208 return unless [y, w, d] == jd_to_commercial(jd)
209 jd
210 end
211
212 def self.commercial(y=1582, w=41, d=5, sg=ITALY)
213 unless jd = valid_commercial?(y, w, d, sg)
214 raise ArgumentError, 'invalid date'
215 end
216 new0(jd_to_ajd(jd, 0, 0), 0, sg)
217 end
218
219 def self.new_with_hash(elem, sg)
220 elem ||= {}
221 y, m, d = elem.select(:year, :mon, :mday)
222 if [y, m, d].include? nil
223 raise ArgumentError, 'invalid date'
224 else
225 civil(y, m, d, sg)
226 end
227 end
228
229 private_class_method :new_with_hash
230
231 def self.strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
232 elem = _strptime(str, fmt)
233 new_with_hash(elem, sg)
234 end
235
236 def self.parse(str='-4712-01-01', comp=false, sg=ITALY)
237 elem = _parse(str, comp)
238 new_with_hash(elem, sg)
239 end
240
241 def self.today(sg=ITALY)
242 jd = civil_to_jd(*(Time.now.to_a[3..5].reverse << sg))
243 new0(jd_to_ajd(jd, 0, 0), 0, sg)
244 end
245
246 class << self
247
248 def once(*ids)
249 for id in ids
250 module_eval <<-"end;"
251 alias_method :__#{id.to_i}__, :#{id.to_s}
252 private :__#{id.to_i}__
253 def #{id.to_s}(*args, &block)
254 (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
255 end
256 end;
257 end
258 end
259
260 private :once
261
262 end
263
264 def initialize(ajd=0, of=0, sg=ITALY) @ajd, @of, @sg = ajd, of, sg end
265
266 def ajd() @ajd end
267 def amjd() type.ajd_to_amjd(@ajd) end
268
269 once :amjd
270
271 def jd() type.ajd_to_jd(@ajd, @of)[0] end
272 def day_fraction() type.ajd_to_jd(@ajd, @of)[1] end
273 def mjd() type.jd_to_mjd(jd) end
274 def ld() type.jd_to_ld(jd) end
275
276 once :jd, :day_fraction, :mjd, :ld
277
278 def civil() type.jd_to_civil(jd, @sg) end
279 def ordinal() type.jd_to_ordinal(jd, @sg) end
280 def commercial() type.jd_to_commercial(jd, @sg) end
281
282 once :civil, :ordinal, :commercial
283 private :civil, :ordinal, :commercial
284
285 def year() civil[0] end
286 def yday() ordinal[1] end
287 def mon() civil[1] end
288 def mday() civil[2] end
289
290 alias_method :month, :mon
291 alias_method :day, :mday
292
293 def time() type.day_fraction_to_time(day_fraction) end
294
295 once :time
296 private :time
297
298 def hour() time[0] end
299 def min() time[1] end
300 def sec() time[2] end
301 def sec_fraction() time[3] end
302
303 private :hour, :min, :sec, :sec_fraction
304
305 def zone
306 ['Z',
307 format('%+.2d%02d',
308 (@of / (1.to_r/24)).to_i,
309 (@of.abs % (1.to_r/24) / (1.to_r/1440)).to_i)
310 ][@of<=>0]
311 end
312
313 private :zone
314
315 def cwyear() commercial[0] end
316 def cweek() commercial[1] end
317 def cwday() commercial[2] end
318
319 def wday() type.jd_to_wday(jd) end
320
321 once :wday
322
323 def os? () type.os?(jd, @sg) end
324 def ns? () type.ns?(jd, @sg) end
325
326 once :os?, :ns?
327
328 def leap?
329 type.jd_to_civil(type.civil_to_jd(year, 3, 1, ns?) - 1,
330 ns?)[-1] == 29
331 end
332
333 once :leap?
334
335 def start() @sg end
336 def new_start(sg=type::ITALY) type.new0(@ajd, @of, sg) end
337
338 def italy() new_start(type::ITALY) end
339 def england() new_start(type::ENGLAND) end
340 def julian() new_start(type::JULIAN) end
341 def gregorian() new_start(type::GREGORIAN) end
342
343 def offset() @of end
344 def new_offset(of=0) type.new0(@ajd, of, @sg) end
345
346 private :offset, :new_offset
347
348 def + (n)
349 case n
350 when Numeric; return type.new0(@ajd + n, @of, @sg)
351 end
352 raise TypeError, 'expected numeric'
353 end
354
355 def - (x)
356 case x
357 when Numeric; return type.new0(@ajd - x, @of, @sg)
358 when Date; return @ajd - x.ajd
359 end
360 raise TypeError, 'expected numeric or date'
361 end
362
363 def <=> (other)
364 case other
365 when Numeric; return @ajd <=> other
366 when Date; return @ajd <=> other.ajd
367 end
368 raise TypeError, 'expected numeric or date'
369 end
370
371 def === (other)
372 case other
373 when Numeric; return jd == other
374 when Date; return jd == other.jd
375 end
376 raise TypeError, 'expected numeric or date'
377 end
378
379 def >> (n)
380 y, m = clfloor(year * 12 + (mon - 1) + n, 12)
381 m, = clfloor(m + 1, 1)
382 d = mday
383 d -= 1 until jd2 = type.valid_civil?(y, m, d, ns?)
384 self + (jd2 - jd)
385 end
386
387 def << (n) self >> -n end
388
389 def step(limit, step)
390 da = self
391 op = [:-,:<=,:>=][step<=>0]
392 while da.__send__(op, limit)
393 yield da
394 da += step
395 end
396 self
397 end
398
399 def upto(max, &block) step(max, +1, &block) end
400 def downto(min, &block) step(min, -1, &block) end
401
402 def succ() self + 1 end
403
404 alias_method :next, :succ
405
406 def eql? (other) Date === other and self == other end
407 def hash() @ajd.hash end
408
409 def inspect() format('#<%s: %s,%s,%s>', type, @ajd, @of, @sg) end
410 def to_s() strftime end
411
412 def _dump(limit) Marshal.dump([@ajd, @of, @sg], -1) end
413
414 # def self._load(str) new0(*Marshal.load(str)) end
415
416 def self._load(str)
417 a = Marshal.load(str)
418 if a.size == 2
419 ajd, sg = a
420 of = 0
421 ajd -= 1.to_r/2
422 else
423 ajd, of, sg = a
424 end
425 new0(ajd, of, sg)
426 end
427
428 end
429
430 class DateTime < Date
431
432 def self.valid_time? (h, min, s)
433 h += 24 if h < 0
434 min += 60 if min < 0
435 s += 60 if s < 0
436 return unless (0..24) === h and
437 (0..59) === min and
438 (0..59) === s
439 time_to_day_fraction(h, min, s)
440 end
441
442 def self.jd(jd=0, h=0, min=0, s=0, of=0, sg=ITALY)
443 unless (jd = valid_jd?(jd, sg)) and
444 (fr = valid_time?(h, min, s))
445 raise ArgumentError, 'invalid date'
446 end
447 new0(jd_to_ajd(jd, fr, of), of, sg)
448 end
449
450 def self.ordinal(y=-4712, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
451 unless (jd = valid_ordinal?(y, d, sg)) and
452 (fr = valid_time?(h, min, s))
453 raise ArgumentError, 'invalid date'
454 end
455 new0(jd_to_ajd(jd, fr, of), of, sg)
456 end
457
458 def self.civil(y=-4712, m=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
459 unless (jd = valid_civil?(y, m, d, sg)) and
460 (fr = valid_time?(h, min, s))
461 raise ArgumentError, 'invalid date'
462 end
463 new0(jd_to_ajd(jd, fr, of), of, sg)
464 end
465
466 class << self; alias_method :new, :civil end
467
468 def self.commercial(y=1582, w=41, d=5, h=0, min=0, s=0, of=0, sg=ITALY)
469 unless (jd = valid_commercial?(y, w, d, sg)) and
470 (fr = valid_time?(h, min, s))
471 raise ArgumentError, 'invalid date'
472 end
473 new0(jd_to_ajd(jd, fr, of), of, sg)
474 end
475
476 def self.new_with_hash(elem, sg)
477 elem ||= {}
478 y, m, d, h, min, s, of =
479 elem.select(:year, :mon, :mday, :hour, :min, :sec, :offset)
480 h ||= 0
481 min ||= 0
482 s ||= 0
483 of ||= 0
484 if [y, m, d].include? nil
485 raise ArgumentError, 'invalid date'
486 else
487 civil(y, m, d, h, min, s, of.to_r/86400, sg)
488 end
489 end
490
491 private_class_method :new_with_hash
492
493 def self.strptime(str='-4712-01-01T00:00:00Z', fmt='%FT%T%Z', sg=ITALY)
494 elem = _strptime(str, fmt)
495 new_with_hash(elem, sg)
496 end
497
498 def self.parse(str='-4712-01-01T00:00:00Z', comp=false, sg=ITALY)
499 elem = _parse(str, comp)
500 new_with_hash(elem, sg)
501 end
502
503 class << self; undef_method :today end
504
505 def self.now(sg=ITALY)
506 i = Time.now
507 a = i.to_a[0..5].reverse
508 jd = civil_to_jd(*(a[0,3] << sg))
509 fr = time_to_day_fraction(*(a[3,3])) + i.usec.to_r/86400000000
510 d = Time.gm(*i.to_a).to_i - i.to_i
511 d += d / d.abs if d.nonzero?
512 of = (d / 60).to_r/1440
513 new0(jd_to_ajd(jd, fr, of), of, sg)
514 end
515
516 public :hour, :min, :sec, :sec_fraction, :zone, :offset, :new_offset
517
518 end
519
520 class Date
521
522 [ %w(exist1? valid_jd?),
523 %w(exist2? valid_ordinal?),
524 %w(exist3? valid_date?),
525 %w(exist? valid_date?),
526 %w(existw? valid_commercial?),
527 %w(new1 jd),
528 %w(new2 ordinal),
529 %w(new3 new),
530 %w(neww commercial)
531 ].each do |old, new|
532 module_eval <<-"end;"
533 def self.#{old}(*args, &block)
534 if $VERBOSE
535 $stderr.puts("\#{caller.shift.sub(/:in .*/, '')}: " \
536 "warning: \#{self}::#{old} is deprecated; " \
537 "use \#{self}::#{new}")
538 end
539 #{new}(*args, &block)
540 end
541 end;
542 end
543
544 [ %w(sg start),
545 %w(newsg new_start),
546 %w(of offset),
547 %w(newof new_offset)
548 ].each do |old, new|
549 module_eval <<-"end;"
550 def #{old}(*args, &block)
551 if $VERBOSE
552 $stderr.puts("\#{caller.shift.sub(/:in .*/, '')}: " \
553 "warning: \#{type}\##{old} is deprecated; " \
554 "use \#{type}\##{new}")
555 end
556 #{new}(*args, &block)
557 end
558 end;
559 end
560
561 private :of, :newof
562
563 end
564
565 class DateTime < Date
566
567 public :of, :newof
568
569 end