source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/date.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 50.9 KB
Line 
1#
2# date.rb - date and time library
3#
4# Author: Tadayoshi Funaba 1998-2006
5#
6# Documentation: William Webber <[email protected]>
7#
8#--
9# $Id: date.rb,v 2.30 2006-12-30 21:43:41+09 tadf Exp $
10#++
11#
12# == Overview
13#
14# This file provides two classes for working with
15# dates and times.
16#
17# The first class, Date, represents dates.
18# It works with years, months, weeks, and days.
19# See the Date class documentation for more details.
20#
21# The second, DateTime, extends Date to include hours,
22# minutes, seconds, and fractions of a second. It
23# provides basic support for time zones. See the
24# DateTime class documentation for more details.
25#
26# === Ways of calculating the date.
27#
28# In common usage, the date is reckoned in years since or
29# before the Common Era (CE/BCE, also known as AD/BC), then
30# as a month and day-of-the-month within the current year.
31# This is known as the *Civil* *Date*, and abbreviated
32# as +civil+ in the Date class.
33#
34# Instead of year, month-of-the-year, and day-of-the-month,
35# the date can also be reckoned in terms of year and
36# day-of-the-year. This is known as the *Ordinal* *Date*,
37# and is abbreviated as +ordinal+ in the Date class. (Note
38# that referring to this as the Julian date is incorrect.)
39#
40# The date can also be reckoned in terms of year, week-of-the-year,
41# and day-of-the-week. This is known as the *Commercial*
42# *Date*, and is abbreviated as +commercial+ in the
43# Date class. The commercial week runs Monday (day-of-the-week
44# 1) to Sunday (day-of-the-week 7), in contrast to the civil
45# week which runs Sunday (day-of-the-week 0) to Saturday
46# (day-of-the-week 6). The first week of the commercial year
47# starts on the Monday on or before January 1, and the commercial
48# year itself starts on this Monday, not January 1.
49#
50# For scientific purposes, it is convenient to refer to a date
51# simply as a day count, counting from an arbitrary initial
52# day. The date first chosen for this was January 1, 4713 BCE.
53# A count of days from this date is the *Julian* *Day* *Number*
54# or *Julian* *Date*, which is abbreviated as +jd+ in the
55# Date class. This is in local time, and counts from midnight
56# on the initial day. The stricter usage is in UTC, and counts
57# from midday on the initial day. This is referred to in the
58# Date class as the *Astronomical* *Julian* *Day* *Number*, and
59# abbreviated as +ajd+. In the Date class, the Astronomical
60# Julian Day Number includes fractional days.
61#
62# Another absolute day count is the *Modified* *Julian* *Day*
63# *Number*, which takes November 17, 1858 as its initial day.
64# This is abbreviated as +mjd+ in the Date class. There
65# is also an *Astronomical* *Modified* *Julian* *Day* *Number*,
66# which is in UTC and includes fractional days. This is
67# abbreviated as +amjd+ in the Date class. Like the Modified
68# Julian Day Number (and unlike the Astronomical Julian
69# Day Number), it counts from midnight.
70#
71# Alternative calendars such as the Chinese Lunar Calendar,
72# the Islamic Calendar, or the French Revolutionary Calendar
73# are not supported by the Date class; nor are calendars that
74# are based on an Era different from the Common Era, such as
75# the Japanese Imperial Calendar or the Republic of China
76# Calendar.
77#
78# === Calendar Reform
79#
80# The standard civil year is 365 days long. However, the
81# solar year is fractionally longer than this. To account
82# for this, a *leap* *year* is occasionally inserted. This
83# is a year with 366 days, the extra day falling on February 29.
84# In the early days of the civil calendar, every fourth
85# year without exception was a leap year. This way of
86# reckoning leap years is the *Julian* *Calendar*.
87#
88# However, the solar year is marginally shorter than 365 1/4
89# days, and so the *Julian* *Calendar* gradually ran slow
90# over the centuries. To correct this, every 100th year
91# (but not every 400th year) was excluded as a leap year.
92# This way of reckoning leap years, which we use today, is
93# the *Gregorian* *Calendar*.
94#
95# The Gregorian Calendar was introduced at different times
96# in different regions. The day on which it was introduced
97# for a particular region is the *Day* *of* *Calendar*
98# *Reform* for that region. This is abbreviated as +sg+
99# (for Start of Gregorian calendar) in the Date class.
100#
101# Two such days are of particular
102# significance. The first is October 15, 1582, which was
103# the Day of Calendar Reform for Italy and most Catholic
104# countries. The second is September 14, 1752, which was
105# the Day of Calendar Reform for England and its colonies
106# (including what is now the United States). These two
107# dates are available as the constants Date::ITALY and
108# Date::ENGLAND, respectively. (By comparison, Germany and
109# Holland, less Catholic than Italy but less stubborn than
110# England, changed over in 1698; Sweden in 1753; Russia not
111# till 1918, after the Revolution; and Greece in 1923. Many
112# Orthodox churches still use the Julian Calendar. A complete
113# list of Days of Calendar Reform can be found at
114# http://www.polysyllabic.com/GregConv.html.)
115#
116# Switching from the Julian to the Gregorian calendar
117# involved skipping a number of days to make up for the
118# accumulated lag, and the later the switch was (or is)
119# done, the more days need to be skipped. So in 1582 in Italy,
120# 4th October was followed by 15th October, skipping 10 days; in 1752
121# in England, 2nd September was followed by 14th September, skipping
122# 11 days; and if I decided to switch from Julian to Gregorian
123# Calendar this midnight, I would go from 27th July 2003 (Julian)
124# today to 10th August 2003 (Gregorian) tomorrow, skipping
125# 13 days. The Date class is aware of this gap, and a supposed
126# date that would fall in the middle of it is regarded as invalid.
127#
128# The Day of Calendar Reform is relevant to all date representations
129# involving years. It is not relevant to the Julian Day Numbers,
130# except for converting between them and year-based representations.
131#
132# In the Date and DateTime classes, the Day of Calendar Reform or
133# +sg+ can be specified a number of ways. First, it can be as
134# the Julian Day Number of the Day of Calendar Reform. Second,
135# it can be using the constants Date::ITALY or Date::ENGLAND; these
136# are in fact the Julian Day Numbers of the Day of Calendar Reform
137# of the respective regions. Third, it can be as the constant
138# Date::JULIAN, which means to always use the Julian Calendar.
139# Finally, it can be as the constant Date::GREGORIAN, which means
140# to always use the Gregorian Calendar.
141#
142# Note: in the Julian Calendar, New Years Day was March 25. The
143# Date class does not follow this convention.
144#
145# === Time Zones
146#
147# DateTime objects support a simple representation
148# of time zones. Time zones are represented as an offset
149# from UTC, as a fraction of a day. This offset is the
150# how much local time is later (or earlier) than UTC.
151# UTC offset 0 is centred on England (also known as GMT).
152# As you travel east, the offset increases until you
153# reach the dateline in the middle of the Pacific Ocean;
154# as you travel west, the offset decreases. This offset
155# is abbreviated as +of+ in the Date class.
156#
157# This simple representation of time zones does not take
158# into account the common practice of Daylight Savings
159# Time or Summer Time.
160#
161# Most DateTime methods return the date and the
162# time in local time. The two exceptions are
163# #ajd() and #amjd(), which return the date and time
164# in UTC time, including fractional days.
165#
166# The Date class does not support time zone offsets, in that
167# there is no way to create a Date object with a time zone.
168# However, methods of the Date class when used by a
169# DateTime instance will use the time zone offset of this
170# instance.
171#
172# == Examples of use
173#
174# === Print out the date of every Sunday between two dates.
175#
176# def print_sundays(d1, d2)
177# d1 +=1 while (d1.wday != 0)
178# d1.step(d2, 7) do |date|
179# puts "#{Date::MONTHNAMES[date.mon]} #{date.day}"
180# end
181# end
182#
183# print_sundays(Date::civil(2003, 4, 8), Date::civil(2003, 5, 23))
184#
185# === Calculate how many seconds to go till midnight on New Year's Day.
186#
187# def secs_to_new_year(now = DateTime::now())
188# new_year = DateTime.new(now.year + 1, 1, 1)
189# dif = new_year - now
190# hours, mins, secs, ignore_fractions = Date::day_fraction_to_time(dif)
191# return hours * 60 * 60 + mins * 60 + secs
192# end
193#
194# puts secs_to_new_year()
195
196require 'rational'
197require 'date/format'
198
199# Class representing a date.
200#
201# See the documentation to the file date.rb for an overview.
202#
203# Internally, the date is represented as an Astronomical
204# Julian Day Number, +ajd+. The Day of Calendar Reform, +sg+, is
205# also stored, for conversions to other date formats. (There
206# is also an +of+ field for a time zone offset, but this
207# is only for the use of the DateTime subclass.)
208#
209# A new Date object is created using one of the object creation
210# class methods named after the corresponding date format, and the
211# arguments appropriate to that date format; for instance,
212# Date::civil() (aliased to Date::new()) with year, month,
213# and day-of-month, or Date::ordinal() with year and day-of-year.
214# All of these object creation class methods also take the
215# Day of Calendar Reform as an optional argument.
216#
217# Date objects are immutable once created.
218#
219# Once a Date has been created, date values
220# can be retrieved for the different date formats supported
221# using instance methods. For instance, #mon() gives the
222# Civil month, #cwday() gives the Commercial day of the week,
223# and #yday() gives the Ordinal day of the year. Date values
224# can be retrieved in any format, regardless of what format
225# was used to create the Date instance.
226#
227# The Date class includes the Comparable module, allowing
228# date objects to be compared and sorted, ranges of dates
229# to be created, and so forth.
230class Date
231
232 include Comparable
233
234 # Full month names, in English. Months count from 1 to 12; a
235 # month's numerical representation indexed into this array
236 # gives the name of that month (hence the first element is nil).
237 MONTHNAMES = [nil] + %w(January February March April May June July
238 August September October November December)
239
240 # Full names of days of the week, in English. Days of the week
241 # count from 0 to 6 (except in the commercial week); a day's numerical
242 # representation indexed into this array gives the name of that day.
243 DAYNAMES = %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
244
245 # Abbreviated month names, in English.
246 ABBR_MONTHNAMES = [nil] + %w(Jan Feb Mar Apr May Jun
247 Jul Aug Sep Oct Nov Dec)
248
249 # Abbreviated day names, in English.
250 ABBR_DAYNAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
251
252 [MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYNAMES].each do |xs|
253 xs.each{|x| x.freeze}.freeze
254 end
255
256 class Infinity < Numeric # :nodoc:
257
258 include Comparable
259
260 def initialize(d=1) @d = d <=> 0 end
261
262 def d() @d end
263
264 protected :d
265
266 def zero? () false end
267 def finite? () false end
268 def infinite? () d.nonzero? end
269 def nan? () d.zero? end
270
271 def abs() self.class.new end
272
273 def -@ () self.class.new(-d) end
274 def +@ () self.class.new(+d) end
275
276 def <=> (other)
277 case other
278 when Infinity; d <=> other.d
279 when Numeric; d
280 else
281 begin
282 l, r = other.coerce(self)
283 return l <=> r
284 rescue NoMethodError
285 end
286 end
287 nil
288 end
289
290 def coerce(other)
291 case other
292 when Numeric; return -d, d
293 else
294 super
295 end
296 end
297
298 end
299
300 # The Julian Day Number of the Day of Calendar Reform for Italy
301 # and the Catholic countries.
302 ITALY = 2299161 # 1582-10-15
303
304 # The Julian Day Number of the Day of Calendar Reform for England
305 # and her Colonies.
306 ENGLAND = 2361222 # 1752-09-14
307
308 # A constant used to indicate that a Date should always use the
309 # Julian calendar.
310 JULIAN = Infinity.new
311
312 # A constant used to indicate that a Date should always use the
313 # Gregorian calendar.
314 GREGORIAN = -Infinity.new
315
316 UNIXEPOCH = 2440588 # 1970-01-01 :nodoc:
317
318 # Does a given Julian Day Number fall inside the old-style (Julian)
319 # calendar?
320 #
321 # +jd+ is the Julian Day Number in question. +sg+ may be Date::GREGORIAN,
322 # in which case the answer is false; it may be Date::JULIAN, in which case
323 # the answer is true; or it may a number representing the Day of
324 # Calendar Reform. Date::ENGLAND and Date::ITALY are two possible such
325 # days.
326
327 def self.julian? (jd, sg)
328 case sg
329 when Numeric
330 jd < sg
331 else
332 if $VERBOSE
333 warn("#{caller.shift.sub(/:in .*/, '')}: " \
334"warning: do not use non-numerical object as julian day number anymore")
335 end
336 not sg
337 end
338 end
339
340 # Does a given Julian Day Number fall inside the new-style (Gregorian)
341 # calendar?
342 #
343 # The reverse of self.os? See the documentation for that method for
344 # more details.
345 def self.gregorian? (jd, sg) !julian?(jd, sg) end
346
347 def self.fix_style(jd, sg) # :nodoc:
348 if julian?(jd, sg)
349 then JULIAN
350 else GREGORIAN end
351 end
352
353 private_class_method :fix_style
354
355 # Convert an Ordinal Date to a Julian Day Number.
356 #
357 # +y+ and +d+ are the year and day-of-year to convert.
358 # +sg+ specifies the Day of Calendar Reform.
359 #
360 # Returns the corresponding Julian Day Number.
361 def self.ordinal_to_jd(y, d, sg=GREGORIAN)
362 civil_to_jd(y, 1, d, sg)
363 end
364
365 # Convert a Julian Day Number to an Ordinal Date.
366 #
367 # +jd+ is the Julian Day Number to convert.
368 # +sg+ specifies the Day of Calendar Reform.
369 #
370 # Returns the corresponding Ordinal Date as
371 # [year, day_of_year]
372 def self.jd_to_ordinal(jd, sg=GREGORIAN)
373 y = jd_to_civil(jd, sg)[0]
374 doy = jd - civil_to_jd(y - 1, 12, 31, fix_style(jd, sg))
375 return y, doy
376 end
377
378 # Convert a Civil Date to a Julian Day Number.
379 # +y+, +m+, and +d+ are the year, month, and day of the
380 # month. +sg+ specifies the Day of Calendar Reform.
381 #
382 # Returns the corresponding Julian Day Number.
383 def self.civil_to_jd(y, m, d, sg=GREGORIAN)
384 if m <= 2
385 y -= 1
386 m += 12
387 end
388 a = (y / 100.0).floor
389 b = 2 - a + (a / 4.0).floor
390 jd = (365.25 * (y + 4716)).floor +
391 (30.6001 * (m + 1)).floor +
392 d + b - 1524
393 if julian?(jd, sg)
394 jd -= b
395 end
396 jd
397 end
398
399 # Convert a Julian Day Number to a Civil Date. +jd+ is
400 # the Julian Day Number. +sg+ specifies the Day of
401 # Calendar Reform.
402 #
403 # Returns the corresponding [year, month, day_of_month]
404 # as a three-element array.
405 def self.jd_to_civil(jd, sg=GREGORIAN)
406 if julian?(jd, sg)
407 a = jd
408 else
409 x = ((jd - 1867216.25) / 36524.25).floor
410 a = jd + 1 + x - (x / 4.0).floor
411 end
412 b = a + 1524
413 c = ((b - 122.1) / 365.25).floor
414 d = (365.25 * c).floor
415 e = ((b - d) / 30.6001).floor
416 dom = b - d - (30.6001 * e).floor
417 if e <= 13
418 m = e - 1
419 y = c - 4716
420 else
421 m = e - 13
422 y = c - 4715
423 end
424 return y, m, dom
425 end
426
427 # Convert a Commercial Date to a Julian Day Number.
428 #
429 # +y+, +w+, and +d+ are the (commercial) year, week of the year,
430 # and day of the week of the Commercial Date to convert.
431 # +sg+ specifies the Day of Calendar Reform.
432 def self.commercial_to_jd(y, w, d, ns=GREGORIAN)
433 jd = civil_to_jd(y, 1, 4, ns)
434 (jd - (((jd - 1) + 1) % 7)) +
435 7 * (w - 1) +
436 (d - 1)
437 end
438
439 # Convert a Julian Day Number to a Commercial Date
440 #
441 # +jd+ is the Julian Day Number to convert.
442 # +sg+ specifies the Day of Calendar Reform.
443 #
444 # Returns the corresponding Commercial Date as
445 # [commercial_year, week_of_year, day_of_week]
446 def self.jd_to_commercial(jd, sg=GREGORIAN)
447 ns = fix_style(jd, sg)
448 a = jd_to_civil(jd - 3, ns)[0]
449 y = if jd >= commercial_to_jd(a + 1, 1, 1, ns) then a + 1 else a end
450 w = 1 + ((jd - commercial_to_jd(y, 1, 1, ns)) / 7).floor
451 d = (jd + 1) % 7
452 d = 7 if d == 0
453 return y, w, d
454 end
455
456 def self.weeknum_to_jd(y, w, d, f=0, ns=GREGORIAN) # :nodoc:
457 a = civil_to_jd(y, 1, 1, ns) + 6
458 (a - ((a - f) + 1) % 7 - 7) + 7 * w + d
459 end
460
461 def self.jd_to_weeknum(jd, f=0, sg=GREGORIAN) # :nodoc:
462 ns = fix_style(jd, sg)
463 y, m, d = jd_to_civil(jd, ns)
464 a = civil_to_jd(y, 1, 1, ns) + 6
465 w, d = (jd - (a - ((a - f) + 1) % 7) + 7).divmod(7)
466 return y, w, d
467 end
468
469 private_class_method :weeknum_to_jd, :jd_to_weeknum
470
471 # Convert an Astronomical Julian Day Number to a (civil) Julian
472 # Day Number.
473 #
474 # +ajd+ is the Astronomical Julian Day Number to convert.
475 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
476 #
477 # Returns the (civil) Julian Day Number as [day_number,
478 # fraction] where +fraction+ is always 1/2.
479 def self.ajd_to_jd(ajd, of=0) (ajd + of + 1.to_r/2).divmod(1) end
480
481 # Convert a (civil) Julian Day Number to an Astronomical Julian
482 # Day Number.
483 #
484 # +jd+ is the Julian Day Number to convert, and +fr+ is a
485 # fractional day.
486 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
487 #
488 # Returns the Astronomical Julian Day Number as a single
489 # numeric value.
490 def self.jd_to_ajd(jd, fr, of=0) jd + fr - of - 1.to_r/2 end
491
492 # Convert a fractional day +fr+ to [hours, minutes, seconds,
493 # fraction_of_a_second]
494 def self.day_fraction_to_time(fr)
495 h, fr = fr.divmod(1.to_r/24)
496 min, fr = fr.divmod(1.to_r/1440)
497 s, fr = fr.divmod(1.to_r/86400)
498 return h, min, s, fr
499 end
500
501 # Convert an +h+ hour, +min+ minutes, +s+ seconds period
502 # to a fractional day.
503 def self.time_to_day_fraction(h, min, s)
504 h.to_r/24 + min.to_r/1440 + s.to_r/86400
505 end
506
507 # Convert an Astronomical Modified Julian Day Number to an
508 # Astronomical Julian Day Number.
509 def self.amjd_to_ajd(amjd) amjd + 4800001.to_r/2 end
510
511 # Convert an Astronomical Julian Day Number to an
512 # Astronomical Modified Julian Day Number.
513 def self.ajd_to_amjd(ajd) ajd - 4800001.to_r/2 end
514
515 # Convert a Modified Julian Day Number to a Julian
516 # Day Number.
517 def self.mjd_to_jd(mjd) mjd + 2400001 end
518
519 # Convert a Julian Day Number to a Modified Julian Day
520 # Number.
521 def self.jd_to_mjd(jd) jd - 2400001 end
522
523 # Convert a count of the number of days since the adoption
524 # of the Gregorian Calendar (in Italy) to a Julian Day Number.
525 def self.ld_to_jd(ld) ld + 2299160 end
526
527 # Convert a Julian Day Number to the number of days since
528 # the adoption of the Gregorian Calendar (in Italy).
529 def self.jd_to_ld(jd) jd - 2299160 end
530
531 # Convert a Julian Day Number to the day of the week.
532 #
533 # Sunday is day-of-week 0; Saturday is day-of-week 6.
534 def self.jd_to_wday(jd) (jd + 1) % 7 end
535
536 # Is a year a leap year in the Julian calendar?
537 #
538 # All years divisible by 4 are leap years in the Julian calendar.
539 def self.julian_leap? (y) y % 4 == 0 end
540
541 # Is a year a leap year in the Gregorian calendar?
542 #
543 # All years divisible by 4 are leap years in the Gregorian calendar,
544 # except for years divisible by 100 and not by 400.
545 def self.gregorian_leap? (y) y % 4 == 0 && y % 100 != 0 || y % 400 == 0 end
546
547 class << self; alias_method :leap?, :gregorian_leap? end
548 class << self; alias_method :new!, :new end
549
550 # Is +jd+ a valid Julian Day Number?
551 #
552 # If it is, returns it. In fact, any value is treated as a valid
553 # Julian Day Number.
554 def self.valid_jd? (jd, sg=ITALY) jd end
555
556 # Do the year +y+ and day-of-year +d+ make a valid Ordinal Date?
557 # Returns the corresponding Julian Day Number if they do, or
558 # nil if they don't.
559 #
560 # +d+ can be a negative number, in which case it counts backwards
561 # from the end of the year (-1 being the last day of the year).
562 # No year wraparound is performed, however, so valid values of
563 # +d+ are -365 .. -1, 1 .. 365 on a non-leap-year,
564 # -366 .. -1, 1 .. 366 on a leap year.
565 # A date falling in the period skipped in the Day of Calendar Reform
566 # adjustment is not valid.
567 #
568 # +sg+ specifies the Day of Calendar Reform.
569 def self.valid_ordinal? (y, d, sg=ITALY)
570 if d < 0
571 ny, = (y + 1).divmod(1)
572 jd = ordinal_to_jd(ny, d + 1, sg)
573 ns = fix_style(jd, sg)
574 return unless [y] == jd_to_ordinal(jd, sg)[0..0]
575 return unless [ny, 1] == jd_to_ordinal(jd - d, ns)
576 else
577 jd = ordinal_to_jd(y, d, sg)
578 return unless [y, d] == jd_to_ordinal(jd, sg)
579 end
580 jd
581 end
582
583 # Do year +y+, month +m+, and day-of-month +d+ make a
584 # valid Civil Date? Returns the corresponding Julian
585 # Day Number if they do, nil if they don't.
586 #
587 # +m+ and +d+ can be negative, in which case they count
588 # backwards from the end of the year and the end of the
589 # month respectively. No wraparound is performed, however,
590 # and invalid values cause an ArgumentError to be raised.
591 # A date falling in the period skipped in the Day of Calendar
592 # Reform adjustment is not valid.
593 #
594 # +sg+ specifies the Day of Calendar Reform.
595 def self.valid_civil? (y, m, d, sg=ITALY)
596 if m < 0
597 m += 13
598 end
599 if d < 0
600 ny, nm = (y * 12 + m).divmod(12)
601 nm, = (nm + 1).divmod(1)
602 jd = civil_to_jd(ny, nm, d + 1, sg)
603 ns = fix_style(jd, sg)
604 return unless [y, m] == jd_to_civil(jd, sg)[0..1]
605 return unless [ny, nm, 1] == jd_to_civil(jd - d, ns)
606 else
607 jd = civil_to_jd(y, m, d, sg)
608 return unless [y, m, d] == jd_to_civil(jd, sg)
609 end
610 jd
611 end
612
613 class << self; alias_method :valid_date?, :valid_civil? end
614
615 # Do year +y+, week-of-year +w+, and day-of-week +d+ make a
616 # valid Commercial Date? Returns the corresponding Julian
617 # Day Number if they do, nil if they don't.
618 #
619 # Monday is day-of-week 1; Sunday is day-of-week 7.
620 #
621 # +w+ and +d+ can be negative, in which case they count
622 # backwards from the end of the year and the end of the
623 # week respectively. No wraparound is performed, however,
624 # and invalid values cause an ArgumentError to be raised.
625 # A date falling in the period skipped in the Day of Calendar
626 # Reform adjustment is not valid.
627 #
628 # +sg+ specifies the Day of Calendar Reform.
629 def self.valid_commercial? (y, w, d, sg=ITALY)
630 if d < 0
631 d += 8
632 end
633 if w < 0
634 ny, nw, nd =
635 jd_to_commercial(commercial_to_jd(y + 1, 1, 1) + w * 7)
636 return unless ny == y
637 w = nw
638 end
639 jd = commercial_to_jd(y, w, d)
640 return unless gregorian?(jd, sg)
641 return unless [y, w, d] == jd_to_commercial(jd)
642 jd
643 end
644
645 def self.valid_weeknum? (y, w, d, f, sg=ITALY) # :nodoc:
646 if d < 0
647 d += 7
648 end
649 if w < 0
650 ny, nw, nd, nf =
651 jd_to_weeknum(weeknum_to_jd(y + 1, 1, f, f) + w * 7, f)
652 return unless ny == y
653 w = nw
654 end
655 jd = weeknum_to_jd(y, w, d, f)
656 return unless gregorian?(jd, sg)
657 return unless [y, w, d] == jd_to_weeknum(jd, f)
658 jd
659 end
660
661 private_class_method :valid_weeknum?
662
663 # Do hour +h+, minute +min+, and second +s+ constitute a valid time?
664 #
665 # If they do, returns their value as a fraction of a day. If not,
666 # returns nil.
667 #
668 # The 24-hour clock is used. Negative values of +h+, +min+, and
669 # +sec+ are treating as counting backwards from the end of the
670 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
671 # wraparound is performed.
672 def self.valid_time? (h, min, s)
673 h += 24 if h < 0
674 min += 60 if min < 0
675 s += 60 if s < 0
676 return unless ((0..23) === h &&
677 (0..59) === min &&
678 (0..59) === s) ||
679 (24 == h &&
680 0 == min &&
681 0 == s)
682 time_to_day_fraction(h, min, s)
683 end
684
685 # Create a new Date object from a Julian Day Number.
686 #
687 # +jd+ is the Julian Day Number; if not specified, it defaults to
688 # 0.
689 # +sg+ specifies the Day of Calendar Reform.
690 def self.jd(jd=0, sg=ITALY)
691 jd = valid_jd?(jd, sg)
692 new!(jd_to_ajd(jd, 0, 0), 0, sg)
693 end
694
695 # Create a new Date object from an Ordinal Date, specified
696 # by year +y+ and day-of-year +d+. +d+ can be negative,
697 # in which it counts backwards from the end of the year.
698 # No year wraparound is performed, however. An invalid
699 # value for +d+ results in an ArgumentError being raised.
700 #
701 # +y+ defaults to -4712, and +d+ to 1; this is Julian Day
702 # Number day 0.
703 #
704 # +sg+ specifies the Day of Calendar Reform.
705 def self.ordinal(y=-4712, d=1, sg=ITALY)
706 unless jd = valid_ordinal?(y, d, sg)
707 raise ArgumentError, 'invalid date'
708 end
709 new!(jd_to_ajd(jd, 0, 0), 0, sg)
710 end
711
712 # Create a new Date object for the Civil Date specified by
713 # year +y+, month +m+, and day-of-month +d+.
714 #
715 # +m+ and +d+ can be negative, in which case they count
716 # backwards from the end of the year and the end of the
717 # month respectively. No wraparound is performed, however,
718 # and invalid values cause an ArgumentError to be raised.
719 # can be negative
720 #
721 # +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is
722 # Julian Day Number day 0.
723 #
724 # +sg+ specifies the Day of Calendar Reform.
725 def self.civil(y=-4712, m=1, d=1, sg=ITALY)
726 unless jd = valid_civil?(y, m, d, sg)
727 raise ArgumentError, 'invalid date'
728 end
729 new!(jd_to_ajd(jd, 0, 0), 0, sg)
730 end
731
732 class << self; alias_method :new, :civil end
733
734 # Create a new Date object for the Commercial Date specified by
735 # year +y+, week-of-year +w+, and day-of-week +d+.
736 #
737 # Monday is day-of-week 1; Sunday is day-of-week 7.
738 #
739 # +w+ and +d+ can be negative, in which case they count
740 # backwards from the end of the year and the end of the
741 # week respectively. No wraparound is performed, however,
742 # and invalid values cause an ArgumentError to be raised.
743 #
744 # +y+ defaults to 1582, +w+ to 41, and +d+ to 5, the Day of
745 # Calendar Reform for Italy and the Catholic countries.
746 #
747 # +sg+ specifies the Day of Calendar Reform.
748 def self.commercial(y=1582, w=41, d=5, sg=ITALY)
749 unless jd = valid_commercial?(y, w, d, sg)
750 raise ArgumentError, 'invalid date'
751 end
752 new!(jd_to_ajd(jd, 0, 0), 0, sg)
753 end
754
755 def self.weeknum(y=1582, w=41, d=5, f=0, sg=ITALY) # :nodoc:
756 unless jd = valid_weeknum?(y, w, d, f, sg)
757 raise ArgumentError, 'invalid date'
758 end
759 new!(jd_to_ajd(jd, 0, 0), 0, sg)
760 end
761
762 private_class_method :weeknum
763
764 def self.rewrite_frags(elem) # :nodoc:
765 elem ||= {}
766 if seconds = elem[:seconds]
767 d, fr = seconds.divmod(86400)
768 h, fr = fr.divmod(3600)
769 min, fr = fr.divmod(60)
770 s, fr = fr.divmod(1)
771 elem[:jd] = UNIXEPOCH + d
772 elem[:hour] = h
773 elem[:min] = min
774 elem[:sec] = s
775 elem[:sec_fraction] = fr
776 elem.delete(:seconds)
777 elem.delete(:offset)
778 end
779 elem
780 end
781
782 private_class_method :rewrite_frags
783
784 def self.complete_frags(elem) # :nodoc:
785 i = 0
786 g = [[:time, [:hour, :min, :sec]],
787 [nil, [:jd]],
788 [:ordinal, [:year, :yday, :hour, :min, :sec]],
789 [:civil, [:year, :mon, :mday, :hour, :min, :sec]],
790 [:commercial, [:cwyear, :cweek, :cwday, :hour, :min, :sec]],
791 [:wday, [:wday, :hour, :min, :sec]],
792 [:wnum0, [:year, :wnum0, :wday, :hour, :min, :sec]],
793 [:wnum1, [:year, :wnum1, :wday, :hour, :min, :sec]],
794 [nil, [:cwyear, :cweek, :wday, :hour, :min, :sec]],
795 [nil, [:year, :wnum0, :cwday, :hour, :min, :sec]],
796 [nil, [:year, :wnum1, :cwday, :hour, :min, :sec]]].
797 collect{|k, a| e = elem.values_at(*a).compact; [k, a, e]}.
798 select{|k, a, e| e.size > 0}.
799 sort_by{|k, a, e| [e.size, i -= 1]}.last
800
801 d = nil
802
803 if g && g[0] && (g[1].size - g[2].size) != 0
804 d ||= Date.today
805
806 case g[0]
807 when :ordinal
808 elem[:year] ||= d.year
809 elem[:yday] ||= 1
810 when :civil
811 g[1].each do |e|
812 break if elem[e]
813 elem[e] = d.__send__(e)
814 end
815 elem[:mon] ||= 1
816 elem[:mday] ||= 1
817 when :commercial
818 g[1].each do |e|
819 break if elem[e]
820 elem[e] = d.__send__(e)
821 end
822 elem[:cweek] ||= 1
823 elem[:cwday] ||= 1
824 when :wday
825 elem[:jd] ||= (d - d.wday + elem[:wday]).jd
826 when :wnum0
827 g[1].each do |e|
828 break if elem[e]
829 elem[e] = d.__send__(e)
830 end
831 elem[:wnum0] ||= 0
832 elem[:wday] ||= 0
833 when :wnum1
834 g[1].each do |e|
835 break if elem[e]
836 elem[e] = d.__send__(e)
837 end
838 elem[:wnum1] ||= 0
839 elem[:wday] ||= 0
840 end
841 end
842
843 if g && g[0] == :time
844 if self <= DateTime
845 d ||= Date.today
846 elem[:jd] ||= d.jd
847 end
848 end
849
850 elem[:hour] ||= 0
851 elem[:min] ||= 0
852 elem[:sec] ||= 0
853 elem[:sec] = [elem[:sec], 59].min
854
855 elem
856 end
857
858 private_class_method :complete_frags
859
860 def self.valid_date_frags?(elem, sg) # :nodoc:
861 catch :jd do
862 a = elem.values_at(:jd)
863 if a.all?
864 if jd = valid_jd?(*(a << sg))
865 throw :jd, jd
866 end
867 end
868
869 a = elem.values_at(:year, :yday)
870 if a.all?
871 if jd = valid_ordinal?(*(a << sg))
872 throw :jd, jd
873 end
874 end
875
876 a = elem.values_at(:year, :mon, :mday)
877 if a.all?
878 if jd = valid_civil?(*(a << sg))
879 throw :jd, jd
880 end
881 end
882
883 a = elem.values_at(:cwyear, :cweek, :cwday)
884 if a[2].nil? && elem[:wday]
885 a[2] = elem[:wday].nonzero? || 7
886 end
887 if a.all?
888 if jd = valid_commercial?(*(a << sg))
889 throw :jd, jd
890 end
891 end
892
893 a = elem.values_at(:year, :wnum0, :wday)
894 if a[2].nil? && elem[:cwday]
895 a[2] = elem[:cwday] % 7
896 end
897 if a.all?
898 if jd = valid_weeknum?(*(a << 0 << sg))
899 throw :jd, jd
900 end
901 end
902
903 a = elem.values_at(:year, :wnum1, :wday)
904 if a[2]
905 a[2] = (a[2] - 1) % 7
906 end
907 if a[2].nil? && elem[:cwday]
908 a[2] = (elem[:cwday] - 1) % 7
909 end
910 if a.all?
911 if jd = valid_weeknum?(*(a << 1 << sg))
912 throw :jd, jd
913 end
914 end
915 end
916 end
917
918 private_class_method :valid_date_frags?
919
920 def self.valid_time_frags? (elem) # :nodoc:
921 h, min, s = elem.values_at(:hour, :min, :sec)
922 valid_time?(h, min, s)
923 end
924
925 private_class_method :valid_time_frags?
926
927 def self.new_by_frags(elem, sg) # :nodoc:
928 elem = rewrite_frags(elem)
929 elem = complete_frags(elem)
930 unless jd = valid_date_frags?(elem, sg)
931 raise ArgumentError, 'invalid date'
932 end
933 new!(jd_to_ajd(jd, 0, 0), 0, sg)
934 end
935
936 private_class_method :new_by_frags
937
938 # Create a new Date object by parsing from a String
939 # according to a specified format.
940 #
941 # +str+ is a String holding a date representation.
942 # +fmt+ is the format that the date is in. See
943 # date/format.rb for details on supported formats.
944 #
945 # The default +str+ is '-4712-01-01', and the default
946 # +fmt+ is '%F', which means Year-Month-Day_of_Month.
947 # This gives Julian Day Number day 0.
948 #
949 # +sg+ specifies the Day of Calendar Reform.
950 #
951 # An ArgumentError will be raised if +str+ cannot be
952 # parsed.
953 def self.strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
954 elem = _strptime(str, fmt)
955 new_by_frags(elem, sg)
956 end
957
958 # Create a new Date object by parsing from a String,
959 # without specifying the format.
960 #
961 # +str+ is a String holding a date representation.
962 # +comp+ specifies whether to interpret 2-digit years
963 # as 19XX (>= 69) or 20XX (< 69); the default is not to.
964 # The method will attempt to parse a date from the String
965 # using various heuristics; see #_parse in date/format.rb
966 # for more details. If parsing fails, an ArgumentError
967 # will be raised.
968 #
969 # The default +str+ is '-4712-01-01'; this is Julian
970 # Day Number day 0.
971 #
972 # +sg+ specifies the Day of Calendar Reform.
973 def self.parse(str='-4712-01-01', comp=false, sg=ITALY)
974 elem = _parse(str, comp)
975 new_by_frags(elem, sg)
976 end
977
978 class << self
979
980 def once(*ids) # :nodoc:
981 for id in ids
982 module_eval <<-"end;"
983 alias_method :__#{id.to_i}__, :#{id.to_s}
984 private :__#{id.to_i}__
985 def #{id.to_s}(*args, &block)
986 (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
987 end
988 end;
989 end
990 end
991
992 private :once
993
994 end
995
996 # *NOTE* this is the documentation for the method new!(). If
997 # you are reading this as the documentation for new(), that is
998 # because rdoc doesn't fully support the aliasing of the
999 # initialize() method.
1000 # new() is in
1001 # fact an alias for #civil(): read the documentation for that
1002 # method instead.
1003 #
1004 # Create a new Date object.
1005 #
1006 # +ajd+ is the Astronomical Julian Day Number.
1007 # +of+ is the offset from UTC as a fraction of a day.
1008 # Both default to 0.
1009 #
1010 # +sg+ specifies the Day of Calendar Reform to use for this
1011 # Date object.
1012 #
1013 # Using one of the factory methods such as Date::civil is
1014 # generally easier and safer.
1015 def initialize(ajd=0, of=0, sg=ITALY) @ajd, @of, @sg = ajd, of, sg end
1016
1017 # Get the date as an Astronomical Julian Day Number.
1018 def ajd() @ajd end
1019
1020 # Get the date as an Astronomical Modified Julian Day Number.
1021 def amjd() self.class.ajd_to_amjd(@ajd) end
1022
1023 once :amjd
1024
1025 # Get the date as a Julian Day Number.
1026 def jd() self.class.ajd_to_jd(@ajd, @of)[0] end
1027
1028 # Get any fractional day part of the date.
1029 def day_fraction() self.class.ajd_to_jd(@ajd, @of)[1] end
1030
1031 # Get the date as a Modified Julian Day Number.
1032 def mjd() self.class.jd_to_mjd(jd) end
1033
1034 # Get the date as the number of days since the Day of Calendar
1035 # Reform (in Italy and the Catholic countries).
1036 def ld() self.class.jd_to_ld(jd) end
1037
1038 once :jd, :day_fraction, :mjd, :ld
1039
1040 # Get the date as a Civil Date, [year, month, day_of_month]
1041 def civil() self.class.jd_to_civil(jd, @sg) end # :nodoc:
1042
1043 # Get the date as an Ordinal Date, [year, day_of_year]
1044 def ordinal() self.class.jd_to_ordinal(jd, @sg) end # :nodoc:
1045
1046 # Get the date as a Commercial Date, [year, week_of_year, day_of_week]
1047 def commercial() self.class.jd_to_commercial(jd, @sg) end # :nodoc:
1048
1049 def weeknum0() self.class.__send__(:jd_to_weeknum, jd, 0, @sg) end # :nodoc:
1050 def weeknum1() self.class.__send__(:jd_to_weeknum, jd, 1, @sg) end # :nodoc:
1051
1052 once :civil, :ordinal, :commercial, :weeknum0, :weeknum1
1053 private :civil, :ordinal, :commercial, :weeknum0, :weeknum1
1054
1055 # Get the year of this date.
1056 def year() civil[0] end
1057
1058 # Get the day-of-the-year of this date.
1059 #
1060 # January 1 is day-of-the-year 1
1061 def yday() ordinal[1] end
1062
1063 # Get the month of this date.
1064 #
1065 # January is month 1.
1066 def mon() civil[1] end
1067
1068 # Get the day-of-the-month of this date.
1069 def mday() civil[2] end
1070
1071 alias_method :month, :mon
1072 alias_method :day, :mday
1073
1074 def wnum0() weeknum0[1] end # :nodoc:
1075 def wnum1() weeknum1[1] end # :nodoc:
1076
1077 private :wnum0, :wnum1
1078
1079 # Get the time of this date as [hours, minutes, seconds,
1080 # fraction_of_a_second]
1081 def time() self.class.day_fraction_to_time(day_fraction) end # :nodoc:
1082
1083 once :time
1084 private :time
1085
1086 # Get the hour of this date.
1087 def hour() time[0] end
1088
1089 # Get the minute of this date.
1090 def min() time[1] end
1091
1092 # Get the second of this date.
1093 def sec() time[2] end
1094
1095 # Get the fraction-of-a-second of this date. The unit is in days.
1096 # I do NOT recommend you to use this method.
1097 def sec_fraction() time[3] end
1098
1099 private :hour, :min, :sec, :sec_fraction
1100
1101 def zone() strftime('%:z') end
1102
1103 private :zone
1104
1105 # Get the commercial year of this date. See *Commercial* *Date*
1106 # in the introduction for how this differs from the normal year.
1107 def cwyear() commercial[0] end
1108
1109 # Get the commercial week of the year of this date.
1110 def cweek() commercial[1] end
1111
1112 # Get the commercial day of the week of this date. Monday is
1113 # commercial day-of-week 1; Sunday is commercial day-of-week 7.
1114 def cwday() commercial[2] end
1115
1116 # Get the week day of this date. Sunday is day-of-week 0;
1117 # Saturday is day-of-week 6.
1118 def wday() self.class.jd_to_wday(jd) end
1119
1120 once :wday
1121
1122=begin
1123 MONTHNAMES.each_with_index do |n, i|
1124 if n
1125 define_method(n.downcase + '?'){mon == i}
1126 end
1127 end
1128
1129 DAYNAMES.each_with_index do |n, i|
1130 define_method(n.downcase + '?'){wday == i}
1131 end
1132=end
1133
1134 # Is the current date old-style (Julian Calendar)?
1135 def julian? () self.class.julian?(jd, @sg) end
1136
1137 # Is the current date new-style (Gregorian Calendar)?
1138 def gregorian? () self.class.gregorian?(jd, @sg) end
1139
1140 once :julian?, :gregorian?
1141
1142 def fix_style # :nodoc:
1143 if julian?
1144 then self.class::JULIAN
1145 else self.class::GREGORIAN end
1146 end
1147
1148 private :fix_style
1149
1150 # Is this a leap year?
1151 def leap?
1152 self.class.jd_to_civil(self.class.civil_to_jd(year, 3, 1, fix_style) - 1,
1153 fix_style)[-1] == 29
1154 end
1155
1156 once :leap?
1157
1158 # When is the Day of Calendar Reform for this Date object?
1159 def start() @sg end
1160
1161 # Create a copy of this Date object using a new Day of Calendar Reform.
1162 def new_start(sg=self.class::ITALY) self.class.new!(@ajd, @of, sg) end
1163
1164 # Create a copy of this Date object that uses the Italian/Catholic
1165 # Day of Calendar Reform.
1166 def italy() new_start(self.class::ITALY) end
1167
1168 # Create a copy of this Date object that uses the English/Colonial
1169 # Day of Calendar Reform.
1170 def england() new_start(self.class::ENGLAND) end
1171
1172 # Create a copy of this Date object that always uses the Julian
1173 # Calendar.
1174 def julian() new_start(self.class::JULIAN) end
1175
1176 # Create a copy of this Date object that always uses the Gregorian
1177 # Calendar.
1178 def gregorian() new_start(self.class::GREGORIAN) end
1179
1180 def offset() @of end
1181
1182 def new_offset(of=0)
1183 if String === of
1184 of = (self.class.zone_to_diff(of) || 0).to_r/86400
1185 end
1186 self.class.new!(@ajd, of, @sg)
1187 end
1188
1189 private :offset, :new_offset
1190
1191 # Return a new Date object that is +n+ days later than the
1192 # current one.
1193 #
1194 # +n+ may be a negative value, in which case the new Date
1195 # is earlier than the current one; however, #-() might be
1196 # more intuitive.
1197 #
1198 # If +n+ is not a Numeric, a TypeError will be thrown. In
1199 # particular, two Dates cannot be added to each other.
1200 def + (n)
1201 case n
1202 when Numeric; return self.class.new!(@ajd + n, @of, @sg)
1203 end
1204 raise TypeError, 'expected numeric'
1205 end
1206
1207 # If +x+ is a Numeric value, create a new Date object that is
1208 # +x+ days earlier than the current one.
1209 #
1210 # If +x+ is a Date, return the number of days between the
1211 # two dates; or, more precisely, how many days later the current
1212 # date is than +x+.
1213 #
1214 # If +x+ is neither Numeric nor a Date, a TypeError is raised.
1215 def - (x)
1216 case x
1217 when Numeric; return self.class.new!(@ajd - x, @of, @sg)
1218 when Date; return @ajd - x.ajd
1219 end
1220 raise TypeError, 'expected numeric or date'
1221 end
1222
1223 # Compare this date with another date.
1224 #
1225 # +other+ can also be a Numeric value, in which case it is
1226 # interpreted as an Astronomical Julian Day Number.
1227 #
1228 # Comparison is by Astronomical Julian Day Number, including
1229 # fractional days. This means that both the time and the
1230 # timezone offset are taken into account when comparing
1231 # two DateTime instances. When comparing a DateTime instance
1232 # with a Date instance, the time of the latter will be
1233 # considered as falling on midnight UTC.
1234 def <=> (other)
1235 case other
1236 when Numeric; return @ajd <=> other
1237 when Date; return @ajd <=> other.ajd
1238 end
1239 nil
1240 end
1241
1242 # The relationship operator for Date.
1243 #
1244 # Compares dates by Julian Day Number. When comparing
1245 # two DateTime instances, or a DateTime with a Date,
1246 # the instances will be regarded as equivalent if they
1247 # fall on the same date in local time.
1248 def === (other)
1249 case other
1250 when Numeric; return jd == other
1251 when Date; return jd == other.jd
1252 end
1253 false
1254 end
1255
1256 def next_day(n=1) self + n end
1257# def prev_day(n=1) self - n end
1258
1259 private :next_day
1260
1261 # Return a new Date one day after this one.
1262 def next() next_day end
1263
1264 alias_method :succ, :next
1265
1266 # Return a new Date object that is +n+ months later than
1267 # the current one.
1268 #
1269 # If the day-of-the-month of the current Date is greater
1270 # than the last day of the target month, the day-of-the-month
1271 # of the returned Date will be the last day of the target month.
1272 def >> (n)
1273 y, m = (year * 12 + (mon - 1) + n).divmod(12)
1274 m, = (m + 1) .divmod(1)
1275 d = mday
1276 d -= 1 until jd2 = self.class.valid_civil?(y, m, d, fix_style)
1277 self + (jd2 - jd)
1278 end
1279
1280 # Return a new Date object that is +n+ months earlier than
1281 # the current one.
1282 #
1283 # If the day-of-the-month of the current Date is greater
1284 # than the last day of the target month, the day-of-the-month
1285 # of the returned Date will be the last day of the target month.
1286 def << (n) self >> -n end
1287
1288=begin
1289 def next_month(n=1) self >> n end
1290 def prev_month(n=1) self << n end
1291
1292 def next_year(n=1) self >> n * 12 end
1293 def prev_year(n=1) self << n * 12 end
1294=end
1295
1296# require 'enumerator'
1297
1298 # Step the current date forward +step+ days at a
1299 # time (or backward, if +step+ is negative) until
1300 # we reach +limit+ (inclusive), yielding the resultant
1301 # date at each step.
1302 def step(limit, step=1) # :yield: date
1303=begin
1304 unless block_given?
1305 return to_enum(:step, limit, step)
1306 end
1307=end
1308 da = self
1309 op = %w(- <= >=)[step <=> 0]
1310 while da.__send__(op, limit)
1311 yield da
1312 da += step
1313 end
1314 self
1315 end
1316
1317 # Step forward one day at a time until we reach +max+
1318 # (inclusive), yielding each date as we go.
1319 def upto(max, &block) # :yield: date
1320 step(max, +1, &block)
1321 end
1322
1323 # Step backward one day at a time until we reach +min+
1324 # (inclusive), yielding each date as we go.
1325 def downto(min, &block) # :yield: date
1326 step(min, -1, &block)
1327 end
1328
1329 # Is this Date equal to +other+?
1330 #
1331 # +other+ must both be a Date object, and represent the same date.
1332 def eql? (other) Date === other && self == other end
1333
1334 # Calculate a hash value for this date.
1335 def hash() @ajd.hash end
1336
1337 # Return internal object state as a programmer-readable string.
1338 def inspect() format('#<%s: %s,%s,%s>', self.class, @ajd, @of, @sg) end
1339
1340 # Return the date as a human-readable string.
1341 #
1342 # The format used is YYYY-MM-DD.
1343 def to_s() strftime end
1344
1345 # Dump to Marshal format.
1346 def _dump(limit) Marshal.dump([@ajd, @of, @sg], -1) end
1347
1348# def self._load(str) new!(*Marshal.load(str)) end
1349
1350 # Load from Marshall format.
1351 def self._load(str)
1352 a = Marshal.load(str)
1353 if a.size == 2
1354 ajd, sg = a
1355 of = 0
1356 ajd -= 1.to_r/2
1357 else
1358 ajd, of, sg = a
1359 end
1360 new!(ajd, of, sg)
1361 end
1362
1363end
1364
1365# Class representing a date and time.
1366#
1367# See the documentation to the file date.rb for an overview.
1368#
1369# DateTime objects are immutable once created.
1370#
1371# == Other methods.
1372#
1373# The following methods are defined in Date, but declared private
1374# there. They are made public in DateTime. They are documented
1375# here.
1376#
1377# === hour()
1378#
1379# Get the hour-of-the-day of the time. This is given
1380# using the 24-hour clock, counting from midnight. The first
1381# hour after midnight is hour 0; the last hour of the day is
1382# hour 23.
1383#
1384# === min()
1385#
1386# Get the minute-of-the-hour of the time.
1387#
1388# === sec()
1389#
1390# Get the second-of-the-minute of the time.
1391#
1392# === sec_fraction()
1393#
1394# Get the fraction of a second of the time. This is returned as
1395# a +Rational+. The unit is in days.
1396# I do NOT recommend you to use this method.
1397#
1398# === zone()
1399#
1400# Get the time zone as a String. This is representation of the
1401# time offset such as "+1000", not the true time-zone name.
1402#
1403# === offset()
1404#
1405# Get the time zone offset as a fraction of a day. This is returned
1406# as a +Rational+.
1407#
1408# === new_offset(of=0)
1409#
1410# Create a new DateTime object, identical to the current one, except
1411# with a new time zone offset of +of+. +of+ is the new offset from
1412# UTC as a fraction of a day.
1413#
1414class DateTime < Date
1415
1416 # Create a new DateTime object corresponding to the specified
1417 # Julian Day Number +jd+ and hour +h+, minute +min+, second +s+.
1418 #
1419 # The 24-hour clock is used. Negative values of +h+, +min+, and
1420 # +sec+ are treating as counting backwards from the end of the
1421 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1422 # wraparound is performed. If an invalid time portion is specified,
1423 # an ArgumentError is raised.
1424 #
1425 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1426 # +sg+ specifies the Day of Calendar Reform.
1427 #
1428 # All day/time values default to 0.
1429 def self.jd(jd=0, h=0, min=0, s=0, of=0, sg=ITALY)
1430 unless (jd = valid_jd?(jd, sg)) &&
1431 (fr = valid_time?(h, min, s))
1432 raise ArgumentError, 'invalid date'
1433 end
1434 if String === of
1435 of = (zone_to_diff(of) || 0).to_r/86400
1436 end
1437 new!(jd_to_ajd(jd, fr, of), of, sg)
1438 end
1439
1440 # Create a new DateTime object corresponding to the specified
1441 # Ordinal Date and hour +h+, minute +min+, second +s+.
1442 #
1443 # The 24-hour clock is used. Negative values of +h+, +min+, and
1444 # +sec+ are treating as counting backwards from the end of the
1445 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1446 # wraparound is performed. If an invalid time portion is specified,
1447 # an ArgumentError is raised.
1448 #
1449 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1450 # +sg+ specifies the Day of Calendar Reform.
1451 #
1452 # +y+ defaults to -4712, and +d+ to 1; this is Julian Day Number
1453 # day 0. The time values default to 0.
1454 def self.ordinal(y=-4712, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1455 unless (jd = valid_ordinal?(y, d, sg)) &&
1456 (fr = valid_time?(h, min, s))
1457 raise ArgumentError, 'invalid date'
1458 end
1459 if String === of
1460 of = (zone_to_diff(of) || 0).to_r/86400
1461 end
1462 new!(jd_to_ajd(jd, fr, of), of, sg)
1463 end
1464
1465 # Create a new DateTime object corresponding to the specified
1466 # Civil Date and hour +h+, minute +min+, second +s+.
1467 #
1468 # The 24-hour clock is used. Negative values of +h+, +min+, and
1469 # +sec+ are treating as counting backwards from the end of the
1470 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1471 # wraparound is performed. If an invalid time portion is specified,
1472 # an ArgumentError is raised.
1473 #
1474 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1475 # +sg+ specifies the Day of Calendar Reform.
1476 #
1477 # +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is Julian Day
1478 # Number day 0. The time values default to 0.
1479 def self.civil(y=-4712, m=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1480 unless (jd = valid_civil?(y, m, d, sg)) &&
1481 (fr = valid_time?(h, min, s))
1482 raise ArgumentError, 'invalid date'
1483 end
1484 if String === of
1485 of = (zone_to_diff(of) || 0).to_r/86400
1486 end
1487 new!(jd_to_ajd(jd, fr, of), of, sg)
1488 end
1489
1490 class << self; alias_method :new, :civil end
1491
1492 # Create a new DateTime object corresponding to the specified
1493 # Commercial Date and hour +h+, minute +min+, second +s+.
1494 #
1495 # The 24-hour clock is used. Negative values of +h+, +min+, and
1496 # +sec+ are treating as counting backwards from the end of the
1497 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1498 # wraparound is performed. If an invalid time portion is specified,
1499 # an ArgumentError is raised.
1500 #
1501 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1502 # +sg+ specifies the Day of Calendar Reform.
1503 #
1504 # +y+ defaults to 1582, +w+ to 41, and +d+ to 5; this is the Day of
1505 # Calendar Reform for Italy and the Catholic countries.
1506 # The time values default to 0.
1507 def self.commercial(y=1582, w=41, d=5, h=0, min=0, s=0, of=0, sg=ITALY)
1508 unless (jd = valid_commercial?(y, w, d, sg)) &&
1509 (fr = valid_time?(h, min, s))
1510 raise ArgumentError, 'invalid date'
1511 end
1512 if String === of
1513 of = (zone_to_diff(of) || 0).to_r/86400
1514 end
1515 new!(jd_to_ajd(jd, fr, of), of, sg)
1516 end
1517
1518 def self.weeknum(y=1582, w=41, d=5, f=0, h=0, min=0, s=0, of=0, sg=ITALY) # :nodoc:
1519 unless (jd = valid_weeknum?(y, w, d, f, sg)) &&
1520 (fr = valid_time?(h, min, s))
1521 raise ArgumentError, 'invalid date'
1522 end
1523 if String === of
1524 of = (zone_to_diff(of) || 0).to_r/86400
1525 end
1526 new!(jd_to_ajd(jd, fr, of), of, sg)
1527 end
1528
1529 private_class_method :weeknum
1530
1531 def self.new_by_frags(elem, sg) # :nodoc:
1532 elem = rewrite_frags(elem)
1533 elem = complete_frags(elem)
1534 unless (jd = valid_date_frags?(elem, sg)) &&
1535 (fr = valid_time_frags?(elem))
1536 raise ArgumentError, 'invalid date'
1537 end
1538 sf = (elem[:sec_fraction] || 0)
1539 fr += sf/86400
1540 of = (elem[:offset] || 0)
1541 of = of.to_r/86400
1542 new!(jd_to_ajd(jd, fr, of), of, sg)
1543 end
1544
1545 private_class_method :new_by_frags
1546
1547 # Create a new DateTime object by parsing from a String
1548 # according to a specified format.
1549 #
1550 # +str+ is a String holding a date-time representation.
1551 # +fmt+ is the format that the date-time is in. See
1552 # date/format.rb for details on supported formats.
1553 #
1554 # The default +str+ is '-4712-01-01T00:00:00+00:00', and the default
1555 # +fmt+ is '%FT%T%z'. This gives midnight on Julian Day Number day 0.
1556 #
1557 # +sg+ specifies the Day of Calendar Reform.
1558 #
1559 # An ArgumentError will be raised if +str+ cannot be
1560 # parsed.
1561 def self.strptime(str='-4712-01-01T00:00:00+00:00', fmt='%FT%T%z', sg=ITALY)
1562 elem = _strptime(str, fmt)
1563 new_by_frags(elem, sg)
1564 end
1565
1566 # Create a new DateTime object by parsing from a String,
1567 # without specifying the format.
1568 #
1569 # +str+ is a String holding a date-time representation.
1570 # +comp+ specifies whether to interpret 2-digit years
1571 # as 19XX (>= 69) or 20XX (< 69); the default is not to.
1572 # The method will attempt to parse a date-time from the String
1573 # using various heuristics; see #_parse in date/format.rb
1574 # for more details. If parsing fails, an ArgumentError
1575 # will be raised.
1576 #
1577 # The default +str+ is '-4712-01-01T00:00:00+00:00'; this is Julian
1578 # Day Number day 0.
1579 #
1580 # +sg+ specifies the Day of Calendar Reform.
1581 def self.parse(str='-4712-01-01T00:00:00+00:00', comp=false, sg=ITALY)
1582 elem = _parse(str, comp)
1583 new_by_frags(elem, sg)
1584 end
1585
1586 public :hour, :min, :sec, :sec_fraction, :zone, :offset, :new_offset
1587
1588end
1589
1590class Time
1591
1592# def to_time() getlocal end
1593
1594 def to_date
1595 jd = Date.civil_to_jd(year, mon, mday, Date::ITALY)
1596 Date.new!(Date.jd_to_ajd(jd, 0, 0), 0, Date::ITALY)
1597 end
1598
1599 def to_datetime
1600 jd = DateTime.civil_to_jd(year, mon, mday, DateTime::ITALY)
1601 fr = DateTime.time_to_day_fraction(hour, min, [sec, 59].min) +
1602 usec.to_r/86400000000
1603 of = utc_offset.to_r/86400
1604 DateTime.new!(DateTime.jd_to_ajd(jd, fr, of), of, DateTime::ITALY)
1605 end
1606
1607 private :to_date, :to_datetime
1608
1609end
1610
1611class Date
1612
1613=begin
1614 def to_time() Time.local(year, mon, mday) end
1615 def to_date() self end
1616 def to_datetime() DateTime.new!(self.class.jd_to_ajd(jd, 0, 0), @of, @sg) end
1617=end
1618
1619 # Create a new Date object representing today.
1620 #
1621 # +sg+ specifies the Day of Calendar Reform.
1622 def self.today(sg=ITALY) Time.now.__send__(:to_date) .new_start(sg) end
1623
1624 # Create a new DateTime object representing the current time.
1625 #
1626 # +sg+ specifies the Day of Calendar Reform.
1627 def self.now (sg=ITALY) Time.now.__send__(:to_datetime).new_start(sg) end
1628
1629 private_class_method :now
1630
1631end
1632
1633class DateTime < Date
1634
1635=begin
1636 def to_time
1637 d = new_offset(0)
1638 d.instance_eval do
1639 Time.utc(year, mon, mday, hour, min, sec,
1640 (sec_fraction * 86400000000).to_i)
1641 end.
1642 getlocal
1643 end
1644
1645 def to_date() Date.new!(self.class.jd_to_ajd(jd, 0, 0), 0, @sg) end
1646 def to_datetime() self end
1647=end
1648
1649 private_class_method :today
1650 public_class_method :now
1651
1652end
1653
1654class Date
1655
1656 [ %w(os? julian?),
1657 %w(ns? gregorian?),
1658 %w(exist1? valid_jd?),
1659 %w(exist2? valid_ordinal?),
1660 %w(exist3? valid_date?),
1661 %w(exist? valid_date?),
1662 %w(existw? valid_commercial?),
1663 %w(new0 new!),
1664 %w(new1 jd),
1665 %w(new2 ordinal),
1666 %w(new3 new),
1667 %w(neww commercial)
1668 ].each do |old, new|
1669 module_eval <<-"end;"
1670 def self.#{old}(*args, &block)
1671 if $VERBOSE
1672 warn("\#{caller.shift.sub(/:in .*/, '')}: " \
1673 "warning: \#{self}::#{old} is deprecated; " \
1674 "use \#{self}::#{new}")
1675 end
1676 #{new}(*args, &block)
1677 end
1678 end;
1679 end
1680
1681 [ %w(os? julian?),
1682 %w(ns? gregorian?),
1683 %w(sg start),
1684 %w(newsg new_start),
1685 %w(of offset),
1686 %w(newof new_offset)
1687 ].each do |old, new|
1688 module_eval <<-"end;"
1689 def #{old}(*args, &block)
1690 if $VERBOSE
1691 warn("\#{caller.shift.sub(/:in .*/, '')}: " \
1692 "warning: \#{self.class}\##{old} is deprecated; " \
1693 "use \#{self.class}\##{new}")
1694 end
1695 #{new}(*args, &block)
1696 end
1697 end;
1698 end
1699
1700 private :of, :newof
1701
1702end
1703
1704class DateTime < Date
1705
1706 public :of, :newof
1707
1708end
Note: See TracBrowser for help on using the repository browser.