2 # date.rb - date and time library
4 # Author: Tadayoshi Funaba 1998-2008
6 # Documentation: William Webber <william@williamwebber.com>
9 # $Id: date.rb,v 2.37 2008-01-17 20:16:31+09 tadf Exp $
14 # This file provides two classes for working with
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.
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.
26 # === Ways of calculating the date.
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.
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.)
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.
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.
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.
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
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*.
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*.
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.
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.)
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.
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.
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.
142 # Note: in the Julian Calendar, New Years Day was March 25. The
143 # Date class does not follow this convention.
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.
157 # This simple representation of time zones does not take
158 # into account the common practice of Daylight Savings
159 # Time or Summer Time.
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.
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
174 # === Print out the date of every Sunday between two dates.
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}"
183 # print_sundays(Date::civil(2003, 4, 8), Date::civil(2003, 5, 23))
185 # === Calculate how many seconds to go till midnight on New Year's Day.
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
194 # puts secs_to_new_year()
197 require 'date/format'
199 # Class representing a date.
201 # See the documentation to the file date.rb for an overview.
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.)
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.
217 # Date objects are immutable once created.
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.
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.
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)
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)
245 # Abbreviated month names, in English.
246 ABBR_MONTHNAMES = [nil] + %w(Jan Feb Mar Apr May Jun
247 Jul Aug Sep Oct Nov Dec)
249 # Abbreviated day names, in English.
250 ABBR_DAYNAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
252 [MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYNAMES].each do |xs|
253 xs.each{|x| x.freeze unless x.nil?}.freeze
256 class Infinity < Numeric # :nodoc:
260 def initialize(d=1) @d = d <=> 0 end
266 def zero? () false end
267 def finite? () false end
268 def infinite? () d.nonzero? end
269 def nan? () d.zero? end
271 def abs() self.class.new end
273 def -@ () self.class.new(-d) end
274 def +@ () self.class.new(+d) end
278 when Infinity; return d <=> other.d
279 when Numeric; return d
282 l, r = other.coerce(self)
292 when Numeric; return -d, d
300 # The Julian Day Number of the Day of Calendar Reform for Italy
301 # and the Catholic countries.
302 ITALY = 2299161 # 1582-10-15
304 # The Julian Day Number of the Day of Calendar Reform for England
306 ENGLAND = 2361222 # 1752-09-14
308 # A constant used to indicate that a Date should always use the
310 JULIAN = Infinity.new
312 # A constant used to indicate that a Date should always use the
313 # Gregorian calendar.
314 GREGORIAN = -Infinity.new
316 HALF_DAYS_IN_DAY = Rational(1, 2) # :nodoc:
317 HOURS_IN_DAY = Rational(1, 24) # :nodoc:
318 MINUTES_IN_DAY = Rational(1, 1440) # :nodoc:
319 SECONDS_IN_DAY = Rational(1, 86400) # :nodoc:
320 MILLISECONDS_IN_DAY = Rational(1, 86400*10**3) # :nodoc:
321 NANOSECONDS_IN_DAY = Rational(1, 86400*10**9) # :nodoc:
322 MILLISECONDS_IN_SECOND = Rational(1, 10**3) # :nodoc:
323 NANOSECONDS_IN_SECOND = Rational(1, 10**9) # :nodoc:
325 MJD_EPOCH_IN_AJD = Rational(4800001, 2) # 1858-11-17 # :nodoc:
326 UNIX_EPOCH_IN_AJD = Rational(4881175, 2) # 1970-01-01 # :nodoc:
327 MJD_EPOCH_IN_CJD = 2400001 # :nodoc:
328 UNIX_EPOCH_IN_CJD = 2440588 # :nodoc:
329 LD_EPOCH_IN_CJD = 2299160 # :nodoc:
335 def find_fdoy(y, sg) # :nodoc:
338 break if j = _valid_civil?(y, 1, d, sg)
343 def find_ldoy(y, sg) # :nodoc:
346 break if j = _valid_civil?(y, 12, d, sg)
351 def find_fdom(y, m, sg) # :nodoc:
354 break if j = _valid_civil?(y, m, d, sg)
359 def find_ldom(y, m, sg) # :nodoc:
362 break if j = _valid_civil?(y, m, d, sg)
367 # Convert an Ordinal Date to a Julian Day Number.
369 # +y+ and +d+ are the year and day-of-year to convert.
370 # +sg+ specifies the Day of Calendar Reform.
372 # Returns the corresponding Julian Day Number.
373 def ordinal_to_jd(y, d, sg=GREGORIAN) # :nodoc:
374 find_fdoy(y, sg) + d - 1
377 # Convert a Julian Day Number to an Ordinal Date.
379 # +jd+ is the Julian Day Number to convert.
380 # +sg+ specifies the Day of Calendar Reform.
382 # Returns the corresponding Ordinal Date as
383 # [year, day_of_year]
384 def jd_to_ordinal(jd, sg=GREGORIAN) # :nodoc:
385 y = jd_to_civil(jd, sg)[0]
391 # Convert a Civil Date to a Julian Day Number.
392 # +y+, +m+, and +d+ are the year, month, and day of the
393 # month. +sg+ specifies the Day of Calendar Reform.
395 # Returns the corresponding Julian Day Number.
396 def civil_to_jd(y, m, d, sg=GREGORIAN) # :nodoc:
401 a = (y / 100.0).floor
402 b = 2 - a + (a / 4.0).floor
403 jd = (365.25 * (y + 4716)).floor +
404 (30.6001 * (m + 1)).floor +
412 # Convert a Julian Day Number to a Civil Date. +jd+ is
413 # the Julian Day Number. +sg+ specifies the Day of
416 # Returns the corresponding [year, month, day_of_month]
417 # as a three-element array.
418 def jd_to_civil(jd, sg=GREGORIAN) # :nodoc:
422 x = ((jd - 1867216.25) / 36524.25).floor
423 a = jd + 1 + x - (x / 4.0).floor
426 c = ((b - 122.1) / 365.25).floor
427 d = (365.25 * c).floor
428 e = ((b - d) / 30.6001).floor
429 dom = b - d - (30.6001 * e).floor
440 # Convert a Commercial Date to a Julian Day Number.
442 # +y+, +w+, and +d+ are the (commercial) year, week of the year,
443 # and day of the week of the Commercial Date to convert.
444 # +sg+ specifies the Day of Calendar Reform.
445 def commercial_to_jd(y, w, d, sg=GREGORIAN) # :nodoc:
446 j = find_fdoy(y, sg) + 3
447 (j - (((j - 1) + 1) % 7)) +
452 # Convert a Julian Day Number to a Commercial Date
454 # +jd+ is the Julian Day Number to convert.
455 # +sg+ specifies the Day of Calendar Reform.
457 # Returns the corresponding Commercial Date as
458 # [commercial_year, week_of_year, day_of_week]
459 def jd_to_commercial(jd, sg=GREGORIAN) # :nodoc:
460 a = jd_to_civil(jd - 3, sg)[0]
461 y = if jd >= commercial_to_jd(a + 1, 1, 1, sg) then a + 1 else a end
462 w = 1 + ((jd - commercial_to_jd(y, 1, 1, sg)) / 7).floor
468 def weeknum_to_jd(y, w, d, f=0, sg=GREGORIAN) # :nodoc:
469 a = find_fdoy(y, sg) + 6
470 (a - ((a - f) + 1) % 7 - 7) + 7 * w + d
473 def jd_to_weeknum(jd, f=0, sg=GREGORIAN) # :nodoc:
474 y, m, d = jd_to_civil(jd, sg)
475 a = find_fdoy(y, sg) + 6
476 w, d = (jd - (a - ((a - f) + 1) % 7) + 7).divmod(7)
480 def nth_kday_to_jd(y, m, n, k, sg=GREGORIAN) # :nodoc:
482 find_fdom(y, m, sg) - 1
484 find_ldom(y, m, sg) + 7
486 (j - (((j - k) + 1) % 7)) + 7 * n
489 def jd_to_nth_kday(jd, sg=GREGORIAN) # :nodoc:
490 y, m, d = jd_to_civil(jd, sg)
491 j = find_fdom(y, m, sg)
492 return y, m, ((jd - j) / 7).floor + 1, jd_to_wday(jd)
495 # Convert an Astronomical Julian Day Number to a (civil) Julian
498 # +ajd+ is the Astronomical Julian Day Number to convert.
499 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
501 # Returns the (civil) Julian Day Number as [day_number,
502 # fraction] where +fraction+ is always 1/2.
503 def ajd_to_jd(ajd, of=0) (ajd + of + HALF_DAYS_IN_DAY).divmod(1) end # :nodoc:
505 # Convert a (civil) Julian Day Number to an Astronomical Julian
508 # +jd+ is the Julian Day Number to convert, and +fr+ is a
510 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
512 # Returns the Astronomical Julian Day Number as a single
514 def jd_to_ajd(jd, fr, of=0) jd + fr - of - HALF_DAYS_IN_DAY end # :nodoc:
516 # Convert a fractional day +fr+ to [hours, minutes, seconds,
517 # fraction_of_a_second]
518 def day_fraction_to_time(fr) # :nodoc:
519 ss, fr = fr.divmod(SECONDS_IN_DAY) # 4p
520 h, ss = ss.divmod(3600)
521 min, s = ss.divmod(60)
522 return h, min, s, fr * 86400
525 # Convert an +h+ hour, +min+ minutes, +s+ seconds period
526 # to a fractional day.
528 Rational(Rational(1, 2), 2) # a challenge
530 def time_to_day_fraction(h, min, s)
531 Rational(h * 3600 + min * 60 + s, 86400) # 4p
534 def time_to_day_fraction(h, min, s)
535 if Integer === h && Integer === min && Integer === s
536 Rational(h * 3600 + min * 60 + s, 86400) # 4p
538 (h * 3600 + min * 60 + s).to_r/86400 # 4p
543 # Convert an Astronomical Modified Julian Day Number to an
544 # Astronomical Julian Day Number.
545 def amjd_to_ajd(amjd) amjd + MJD_EPOCH_IN_AJD end # :nodoc:
547 # Convert an Astronomical Julian Day Number to an
548 # Astronomical Modified Julian Day Number.
549 def ajd_to_amjd(ajd) ajd - MJD_EPOCH_IN_AJD end # :nodoc:
551 # Convert a Modified Julian Day Number to a Julian
553 def mjd_to_jd(mjd) mjd + MJD_EPOCH_IN_CJD end # :nodoc:
555 # Convert a Julian Day Number to a Modified Julian Day
557 def jd_to_mjd(jd) jd - MJD_EPOCH_IN_CJD end # :nodoc:
559 # Convert a count of the number of days since the adoption
560 # of the Gregorian Calendar (in Italy) to a Julian Day Number.
561 def ld_to_jd(ld) ld + LD_EPOCH_IN_CJD end # :nodoc:
563 # Convert a Julian Day Number to the number of days since
564 # the adoption of the Gregorian Calendar (in Italy).
565 def jd_to_ld(jd) jd - LD_EPOCH_IN_CJD end # :nodoc:
567 # Convert a Julian Day Number to the day of the week.
569 # Sunday is day-of-week 0; Saturday is day-of-week 6.
570 def jd_to_wday(jd) (jd + 1) % 7 end # :nodoc:
572 # Is +jd+ a valid Julian Day Number?
574 # If it is, returns it. In fact, any value is treated as a valid
576 def _valid_jd? (jd, sg=GREGORIAN) jd end # :nodoc:
578 # Do the year +y+ and day-of-year +d+ make a valid Ordinal Date?
579 # Returns the corresponding Julian Day Number if they do, or
582 # +d+ can be a negative number, in which case it counts backwards
583 # from the end of the year (-1 being the last day of the year).
584 # No year wraparound is performed, however, so valid values of
585 # +d+ are -365 .. -1, 1 .. 365 on a non-leap-year,
586 # -366 .. -1, 1 .. 366 on a leap year.
587 # A date falling in the period skipped in the Day of Calendar Reform
588 # adjustment is not valid.
590 # +sg+ specifies the Day of Calendar Reform.
591 def _valid_ordinal? (y, d, sg=GREGORIAN) # :nodoc:
594 ny, nd = jd_to_ordinal(j + d + 1, sg)
595 return unless ny == y
598 jd = ordinal_to_jd(y, d, sg)
599 return unless [y, d] == jd_to_ordinal(jd, sg)
603 # Do year +y+, month +m+, and day-of-month +d+ make a
604 # valid Civil Date? Returns the corresponding Julian
605 # Day Number if they do, nil if they don't.
607 # +m+ and +d+ can be negative, in which case they count
608 # backwards from the end of the year and the end of the
609 # month respectively. No wraparound is performed, however,
610 # and invalid values cause an ArgumentError to be raised.
611 # A date falling in the period skipped in the Day of Calendar
612 # Reform adjustment is not valid.
614 # +sg+ specifies the Day of Calendar Reform.
615 def _valid_civil? (y, m, d, sg=GREGORIAN) # :nodoc:
620 j = find_ldom(y, m, sg)
621 ny, nm, nd = jd_to_civil(j + d + 1, sg)
622 return unless [ny, nm] == [y, m]
625 jd = civil_to_jd(y, m, d, sg)
626 return unless [y, m, d] == jd_to_civil(jd, sg)
630 # Do year +y+, week-of-year +w+, and day-of-week +d+ make a
631 # valid Commercial Date? Returns the corresponding Julian
632 # Day Number if they do, nil if they don't.
634 # Monday is day-of-week 1; Sunday is day-of-week 7.
636 # +w+ and +d+ can be negative, in which case they count
637 # backwards from the end of the year and the end of the
638 # week respectively. No wraparound is performed, however,
639 # and invalid values cause an ArgumentError to be raised.
640 # A date falling in the period skipped in the Day of Calendar
641 # Reform adjustment is not valid.
643 # +sg+ specifies the Day of Calendar Reform.
644 def _valid_commercial? (y, w, d, sg=GREGORIAN) # :nodoc:
650 jd_to_commercial(commercial_to_jd(y + 1, 1, 1, sg) + w * 7, sg)
651 return unless ny == y
654 jd = commercial_to_jd(y, w, d, sg)
655 return unless [y, w, d] == jd_to_commercial(jd, sg)
659 def _valid_weeknum? (y, w, d, f, sg=GREGORIAN) # :nodoc:
665 jd_to_weeknum(weeknum_to_jd(y + 1, 1, f, f, sg) + w * 7, f, sg)
666 return unless ny == y
669 jd = weeknum_to_jd(y, w, d, f, sg)
670 return unless [y, w, d] == jd_to_weeknum(jd, f, sg)
674 def _valid_nth_kday? (y, m, n, k, sg=GREGORIAN) # :nodoc:
679 ny, nm = (y * 12 + m).divmod(12)
680 nm, = (nm + 1) .divmod(1)
682 jd_to_nth_kday(nth_kday_to_jd(ny, nm, 1, k, sg) + n * 7, sg)
683 return unless [ny, nm] == [y, m]
686 jd = nth_kday_to_jd(y, m, n, k, sg)
687 return unless [y, m, n, k] == jd_to_nth_kday(jd, sg)
691 # Do hour +h+, minute +min+, and second +s+ constitute a valid time?
693 # If they do, returns their value as a fraction of a day. If not,
696 # The 24-hour clock is used. Negative values of +h+, +min+, and
697 # +sec+ are treating as counting backwards from the end of the
698 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
699 # wraparound is performed.
700 def _valid_time? (h, min, s) # :nodoc:
704 return unless ((0...24) === h &&
710 time_to_day_fraction(h, min, s)
718 # Is a year a leap year in the Julian calendar?
720 # All years divisible by 4 are leap years in the Julian calendar.
721 def self.julian_leap? (y) y % 4 == 0 end
723 # Is a year a leap year in the Gregorian calendar?
725 # All years divisible by 4 are leap years in the Gregorian calendar,
726 # except for years divisible by 100 and not by 400.
727 def self.gregorian_leap? (y) y % 4 == 0 && y % 100 != 0 || y % 400 == 0 end
729 class << self; alias_method :leap?, :gregorian_leap? end
730 class << self; alias_method :new!, :new end
732 def self.valid_jd? (jd, sg=ITALY)
736 def self.valid_ordinal? (y, d, sg=ITALY)
737 !!_valid_ordinal?(y, d, sg)
740 def self.valid_civil? (y, m, d, sg=ITALY)
741 !!_valid_civil?(y, m, d, sg)
744 class << self; alias_method :valid_date?, :valid_civil? end
746 def self.valid_commercial? (y, w, d, sg=ITALY)
747 !!_valid_commercial?(y, w, d, sg)
750 def self.valid_weeknum? (y, w, d, f, sg=ITALY) # :nodoc:
751 !!_valid_weeknum?(y, w, d, f, sg)
754 private_class_method :valid_weeknum?
756 def self.valid_nth_kday? (y, m, n, k, sg=ITALY) # :nodoc:
757 !!_valid_nth_kday?(y, m, n, k, sg)
760 private_class_method :valid_nth_kday?
762 def self.valid_time? (h, min, s) # :nodoc:
763 !!_valid_time?(h, min, s)
766 private_class_method :valid_time?
768 # Create a new Date object from a Julian Day Number.
770 # +jd+ is the Julian Day Number; if not specified, it defaults to
772 # +sg+ specifies the Day of Calendar Reform.
773 def self.jd(jd=0, sg=ITALY)
774 jd = _valid_jd?(jd, sg)
775 new!(jd_to_ajd(jd, 0, 0), 0, sg)
778 # Create a new Date object from an Ordinal Date, specified
779 # by year +y+ and day-of-year +d+. +d+ can be negative,
780 # in which it counts backwards from the end of the year.
781 # No year wraparound is performed, however. An invalid
782 # value for +d+ results in an ArgumentError being raised.
784 # +y+ defaults to -4712, and +d+ to 1; this is Julian Day
787 # +sg+ specifies the Day of Calendar Reform.
788 def self.ordinal(y=-4712, d=1, sg=ITALY)
789 unless jd = _valid_ordinal?(y, d, sg)
790 raise ArgumentError, 'invalid date'
792 new!(jd_to_ajd(jd, 0, 0), 0, sg)
795 # Create a new Date object for the Civil Date specified by
796 # year +y+, month +m+, and day-of-month +d+.
798 # +m+ and +d+ can be negative, in which case they count
799 # backwards from the end of the year and the end of the
800 # month respectively. No wraparound is performed, however,
801 # and invalid values cause an ArgumentError to be raised.
804 # +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is
805 # Julian Day Number day 0.
807 # +sg+ specifies the Day of Calendar Reform.
808 def self.civil(y=-4712, m=1, d=1, sg=ITALY)
809 unless jd = _valid_civil?(y, m, d, sg)
810 raise ArgumentError, 'invalid date'
812 new!(jd_to_ajd(jd, 0, 0), 0, sg)
815 class << self; alias_method :new, :civil end
817 # Create a new Date object for the Commercial Date specified by
818 # year +y+, week-of-year +w+, and day-of-week +d+.
820 # Monday is day-of-week 1; Sunday is day-of-week 7.
822 # +w+ and +d+ can be negative, in which case they count
823 # backwards from the end of the year and the end of the
824 # week respectively. No wraparound is performed, however,
825 # and invalid values cause an ArgumentError to be raised.
827 # +y+ defaults to -4712, +w+ to 1, and +d+ to 1; this is
828 # Julian Day Number day 0.
830 # +sg+ specifies the Day of Calendar Reform.
831 def self.commercial(y=-4712, w=1, d=1, sg=ITALY)
832 unless jd = _valid_commercial?(y, w, d, sg)
833 raise ArgumentError, 'invalid date'
835 new!(jd_to_ajd(jd, 0, 0), 0, sg)
838 def self.weeknum(y=-4712, w=0, d=1, f=0, sg=ITALY)
839 unless jd = _valid_weeknum?(y, w, d, f, sg)
840 raise ArgumentError, 'invalid date'
842 new!(jd_to_ajd(jd, 0, 0), 0, sg)
845 private_class_method :weeknum
847 def self.nth_kday(y=-4712, m=1, n=1, k=1, sg=ITALY)
848 unless jd = _valid_nth_kday?(y, m, n, k, sg)
849 raise ArgumentError, 'invalid date'
851 new!(jd_to_ajd(jd, 0, 0), 0, sg)
854 private_class_method :nth_kday
856 def self.rewrite_frags(elem) # :nodoc:
858 if seconds = elem[:seconds]
859 d, fr = seconds.divmod(86400)
860 h, fr = fr.divmod(3600)
861 min, fr = fr.divmod(60)
863 elem[:jd] = UNIX_EPOCH_IN_CJD + d
867 elem[:sec_fraction] = fr
868 elem.delete(:seconds)
874 private_class_method :rewrite_frags
876 def self.complete_frags(elem) # :nodoc:
878 g = [[:time, [:hour, :min, :sec]],
880 [:ordinal, [:year, :yday, :hour, :min, :sec]],
881 [:civil, [:year, :mon, :mday, :hour, :min, :sec]],
882 [:commercial, [:cwyear, :cweek, :cwday, :hour, :min, :sec]],
883 [:wday, [:wday, :hour, :min, :sec]],
884 [:wnum0, [:year, :wnum0, :wday, :hour, :min, :sec]],
885 [:wnum1, [:year, :wnum1, :wday, :hour, :min, :sec]],
886 [nil, [:cwyear, :cweek, :wday, :hour, :min, :sec]],
887 [nil, [:year, :wnum0, :cwday, :hour, :min, :sec]],
888 [nil, [:year, :wnum1, :cwday, :hour, :min, :sec]]].
889 collect{|k, a| e = elem.values_at(*a).compact; [k, a, e]}.
890 select{|k, a, e| e.size > 0}.
891 sort_by{|k, a, e| [e.size, i -= 1]}.last
895 if g && g[0] && (g[1].size - g[2].size) != 0
900 elem[:year] ||= d.year
905 elem[e] = d.__send__(e)
912 elem[e] = d.__send__(e)
917 elem[:jd] ||= (d - d.wday + elem[:wday]).jd
921 elem[e] = d.__send__(e)
928 elem[e] = d.__send__(e)
935 if g && g[0] == :time
945 elem[:sec] = [elem[:sec], 59].min
950 private_class_method :complete_frags
952 def self.valid_date_frags?(elem, sg) # :nodoc:
954 a = elem.values_at(:jd)
956 if jd = _valid_jd?(*(a << sg))
961 a = elem.values_at(:year, :yday)
963 if jd = _valid_ordinal?(*(a << sg))
968 a = elem.values_at(:year, :mon, :mday)
970 if jd = _valid_civil?(*(a << sg))
975 a = elem.values_at(:cwyear, :cweek, :cwday)
976 if a[2].nil? && elem[:wday]
977 a[2] = elem[:wday].nonzero? || 7
980 if jd = _valid_commercial?(*(a << sg))
985 a = elem.values_at(:year, :wnum0, :wday)
986 if a[2].nil? && elem[:cwday]
987 a[2] = elem[:cwday] % 7
990 if jd = _valid_weeknum?(*(a << 0 << sg))
995 a = elem.values_at(:year, :wnum1, :wday)
997 a[2] = (a[2] - 1) % 7
999 if a[2].nil? && elem[:cwday]
1000 a[2] = (elem[:cwday] - 1) % 7
1003 if jd = _valid_weeknum?(*(a << 1 << sg))
1010 private_class_method :valid_date_frags?
1012 def self.valid_time_frags? (elem) # :nodoc:
1013 h, min, s = elem.values_at(:hour, :min, :sec)
1014 _valid_time?(h, min, s)
1017 private_class_method :valid_time_frags?
1019 def self.new_by_frags(elem, sg) # :nodoc:
1020 elem = rewrite_frags(elem)
1021 elem = complete_frags(elem)
1022 unless jd = valid_date_frags?(elem, sg)
1023 raise ArgumentError, 'invalid date'
1025 new!(jd_to_ajd(jd, 0, 0), 0, sg)
1028 private_class_method :new_by_frags
1030 # Create a new Date object by parsing from a String
1031 # according to a specified format.
1033 # +str+ is a String holding a date representation.
1034 # +fmt+ is the format that the date is in. See
1035 # date/format.rb for details on supported formats.
1037 # The default +str+ is '-4712-01-01', and the default
1038 # +fmt+ is '%F', which means Year-Month-Day_of_Month.
1039 # This gives Julian Day Number day 0.
1041 # +sg+ specifies the Day of Calendar Reform.
1043 # An ArgumentError will be raised if +str+ cannot be
1045 def self.strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
1046 elem = _strptime(str, fmt)
1047 new_by_frags(elem, sg)
1050 # Create a new Date object by parsing from a String,
1051 # without specifying the format.
1053 # +str+ is a String holding a date representation.
1054 # +comp+ specifies whether to interpret 2-digit years
1055 # as 19XX (>= 69) or 20XX (< 69); the default is not to.
1056 # The method will attempt to parse a date from the String
1057 # using various heuristics; see #_parse in date/format.rb
1058 # for more details. If parsing fails, an ArgumentError
1061 # The default +str+ is '-4712-01-01'; this is Julian
1064 # +sg+ specifies the Day of Calendar Reform.
1065 def self.parse(str='-4712-01-01', comp=true, sg=ITALY)
1066 elem = _parse(str, comp)
1067 new_by_frags(elem, sg)
1070 def self.iso8601(str='-4712-01-01', sg=ITALY) # :nodoc:
1071 elem = _iso8601(str)
1072 new_by_frags(elem, sg)
1075 def self.rfc3339(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1076 elem = _rfc3339(str)
1077 new_by_frags(elem, sg)
1080 def self.xmlschema(str='-4712-01-01', sg=ITALY) # :nodoc:
1081 elem = _xmlschema(str)
1082 new_by_frags(elem, sg)
1085 def self.rfc2822(str='Mon, 1 Jan -4712 00:00:00 +0000', sg=ITALY) # :nodoc:
1086 elem = _rfc2822(str)
1087 new_by_frags(elem, sg)
1090 class << self; alias_method :rfc822, :rfc2822 end
1092 def self.httpdate(str='Mon, 01 Jan -4712 00:00:00 GMT', sg=ITALY) # :nodoc:
1093 elem = _httpdate(str)
1094 new_by_frags(elem, sg)
1097 def self.jisx0301(str='-4712-01-01', sg=ITALY) # :nodoc:
1098 elem = _jisx0301(str)
1099 new_by_frags(elem, sg)
1104 def once(*ids) # :nodoc: -- restricted
1106 module_eval <<-"end;"
1107 alias_method :__#{id.object_id}__, :#{id.to_s}
1108 private :__#{id.object_id}__
1109 def #{id.to_s}(*args)
1110 @__ca__[#{id.object_id}] ||= __#{id.object_id}__(*args)
1120 # *NOTE* this is the documentation for the method new!(). If
1121 # you are reading this as the documentation for new(), that is
1122 # because rdoc doesn't fully support the aliasing of the
1123 # initialize() method.
1125 # fact an alias for #civil(): read the documentation for that
1128 # Create a new Date object.
1130 # +ajd+ is the Astronomical Julian Day Number.
1131 # +of+ is the offset from UTC as a fraction of a day.
1132 # Both default to 0.
1134 # +sg+ specifies the Day of Calendar Reform to use for this
1137 # Using one of the factory methods such as Date::civil is
1138 # generally easier and safer.
1139 def initialize(ajd=0, of=0, sg=ITALY)
1140 @ajd, @of, @sg = ajd, of, sg
1144 # Get the date as an Astronomical Julian Day Number.
1147 # Get the date as an Astronomical Modified Julian Day Number.
1148 def amjd() ajd_to_amjd(@ajd) end
1152 # Get the date as a Julian Day Number.
1153 def jd() ajd_to_jd(@ajd, @of)[0] end
1155 # Get any fractional day part of the date.
1156 def day_fraction() ajd_to_jd(@ajd, @of)[1] end
1158 # Get the date as a Modified Julian Day Number.
1159 def mjd() jd_to_mjd(jd) end
1161 # Get the date as the number of days since the Day of Calendar
1162 # Reform (in Italy and the Catholic countries).
1163 def ld() jd_to_ld(jd) end
1165 once :jd, :day_fraction, :mjd, :ld
1167 # Get the date as a Civil Date, [year, month, day_of_month]
1168 def civil() jd_to_civil(jd, @sg) end # :nodoc:
1170 # Get the date as an Ordinal Date, [year, day_of_year]
1171 def ordinal() jd_to_ordinal(jd, @sg) end # :nodoc:
1173 # Get the date as a Commercial Date, [year, week_of_year, day_of_week]
1174 def commercial() jd_to_commercial(jd, @sg) end # :nodoc:
1176 def weeknum0() jd_to_weeknum(jd, 0, @sg) end # :nodoc:
1177 def weeknum1() jd_to_weeknum(jd, 1, @sg) end # :nodoc:
1179 once :civil, :ordinal, :commercial, :weeknum0, :weeknum1
1180 private :civil, :ordinal, :commercial, :weeknum0, :weeknum1
1182 # Get the year of this date.
1183 def year() civil[0] end
1185 # Get the day-of-the-year of this date.
1187 # January 1 is day-of-the-year 1
1188 def yday() ordinal[1] end
1190 # Get the month of this date.
1192 # January is month 1.
1193 def mon() civil[1] end
1195 # Get the day-of-the-month of this date.
1196 def mday() civil[2] end
1198 alias_method :month, :mon
1199 alias_method :day, :mday
1201 def wnum0() weeknum0[1] end # :nodoc:
1202 def wnum1() weeknum1[1] end # :nodoc:
1204 private :wnum0, :wnum1
1206 # Get the time of this date as [hours, minutes, seconds,
1207 # fraction_of_a_second]
1208 def time() day_fraction_to_time(day_fraction) end # :nodoc:
1213 # Get the hour of this date.
1214 def hour() time[0] end
1216 # Get the minute of this date.
1217 def min() time[1] end
1219 # Get the second of this date.
1220 def sec() time[2] end
1222 # Get the fraction-of-a-second of this date.
1223 def sec_fraction() time[3] end
1225 alias_method :minute, :min
1226 alias_method :second, :sec
1227 alias_method :second_fraction, :sec_fraction
1229 private :hour, :min, :sec, :sec_fraction,
1230 :minute, :second, :second_fraction
1232 def zone() strftime('%:z') end
1236 # Get the commercial year of this date. See *Commercial* *Date*
1237 # in the introduction for how this differs from the normal year.
1238 def cwyear() commercial[0] end
1240 # Get the commercial week of the year of this date.
1241 def cweek() commercial[1] end
1243 # Get the commercial day of the week of this date. Monday is
1244 # commercial day-of-week 1; Sunday is commercial day-of-week 7.
1245 def cwday() commercial[2] end
1247 # Get the week day of this date. Sunday is day-of-week 0;
1248 # Saturday is day-of-week 6.
1249 def wday() jd_to_wday(jd) end
1254 MONTHNAMES.each_with_index do |n, i|
1256 define_method(n.downcase + '?'){mon == i}
1261 DAYNAMES.each_with_index do |n, i|
1262 define_method(n.downcase + '?'){wday == i}
1265 def nth_kday? (n, k)
1266 k == wday && jd === nth_kday_to_jd(year, mon, n, k, start)
1271 # Is the current date old-style (Julian Calendar)?
1272 def julian? () jd < @sg end
1274 # Is the current date new-style (Gregorian Calendar)?
1275 def gregorian? () !julian? end
1277 once :julian?, :gregorian?
1279 def fix_style # :nodoc:
1281 then self.class::JULIAN
1282 else self.class::GREGORIAN end
1287 # Is this a leap year?
1289 jd_to_civil(civil_to_jd(year, 3, 1, fix_style) - 1,
1290 fix_style)[-1] == 29
1295 # When is the Day of Calendar Reform for this Date object?
1298 # Create a copy of this Date object using a new Day of Calendar Reform.
1299 def new_start(sg=self.class::ITALY) self.class.new!(@ajd, @of, sg) end
1301 # Create a copy of this Date object that uses the Italian/Catholic
1302 # Day of Calendar Reform.
1303 def italy() new_start(self.class::ITALY) end
1305 # Create a copy of this Date object that uses the English/Colonial
1306 # Day of Calendar Reform.
1307 def england() new_start(self.class::ENGLAND) end
1309 # Create a copy of this Date object that always uses the Julian
1311 def julian() new_start(self.class::JULIAN) end
1313 # Create a copy of this Date object that always uses the Gregorian
1315 def gregorian() new_start(self.class::GREGORIAN) end
1317 def offset() @of end
1319 def new_offset(of=0)
1321 of = Rational(zone_to_diff(of) || 0, 86400)
1323 self.class.new!(@ajd, of, @sg)
1326 private :offset, :new_offset
1328 # Return a new Date object that is +n+ days later than the
1331 # +n+ may be a negative value, in which case the new Date
1332 # is earlier than the current one; however, #-() might be
1335 # If +n+ is not a Numeric, a TypeError will be thrown. In
1336 # particular, two Dates cannot be added to each other.
1339 when Numeric; return self.class.new!(@ajd + n, @of, @sg)
1341 raise TypeError, 'expected numeric'
1344 # If +x+ is a Numeric value, create a new Date object that is
1345 # +x+ days earlier than the current one.
1347 # If +x+ is a Date, return the number of days between the
1348 # two dates; or, more precisely, how many days later the current
1351 # If +x+ is neither Numeric nor a Date, a TypeError is raised.
1354 when Numeric; return self.class.new!(@ajd - x, @of, @sg)
1355 when Date; return @ajd - x.ajd
1357 raise TypeError, 'expected numeric or date'
1360 # Compare this date with another date.
1362 # +other+ can also be a Numeric value, in which case it is
1363 # interpreted as an Astronomical Julian Day Number.
1365 # Comparison is by Astronomical Julian Day Number, including
1366 # fractional days. This means that both the time and the
1367 # timezone offset are taken into account when comparing
1368 # two DateTime instances. When comparing a DateTime instance
1369 # with a Date instance, the time of the latter will be
1370 # considered as falling on midnight UTC.
1373 when Numeric; return @ajd <=> other
1374 when Date; return @ajd <=> other.ajd
1379 # The relationship operator for Date.
1381 # Compares dates by Julian Day Number. When comparing
1382 # two DateTime instances, or a DateTime with a Date,
1383 # the instances will be regarded as equivalent if they
1384 # fall on the same date in local time.
1387 when Numeric; return jd == other
1388 when Date; return jd == other.jd
1393 def next_day(n=1) self + n end
1394 def prev_day(n=1) self - n end
1396 # Return a new Date one day after this one.
1397 def next() next_day end
1399 alias_method :succ, :next
1401 # Return a new Date object that is +n+ months later than
1404 # If the day-of-the-month of the current Date is greater
1405 # than the last day of the target month, the day-of-the-month
1406 # of the returned Date will be the last day of the target month.
1408 y, m = (year * 12 + (mon - 1) + n).divmod(12)
1409 m, = (m + 1) .divmod(1)
1411 d -= 1 until jd2 = _valid_civil?(y, m, d, @sg)
1415 # Return a new Date object that is +n+ months earlier than
1418 # If the day-of-the-month of the current Date is greater
1419 # than the last day of the target month, the day-of-the-month
1420 # of the returned Date will be the last day of the target month.
1421 def << (n) self >> -n end
1423 def next_month(n=1) self >> n end
1424 def prev_month(n=1) self << n end
1426 def next_year(n=1) self >> n * 12 end
1427 def prev_year(n=1) self << n * 12 end
1429 require 'enumerator'
1431 # Step the current date forward +step+ days at a
1432 # time (or backward, if +step+ is negative) until
1433 # we reach +limit+ (inclusive), yielding the resultant
1434 # date at each step.
1435 def step(limit, step=1) # :yield: date
1438 raise ArgumentError, "step can't be 0"
1442 return to_enum(:step, limit, step)
1445 op = %w(- <= >=)[step <=> 0]
1446 while da.__send__(op, limit)
1453 # Step forward one day at a time until we reach +max+
1454 # (inclusive), yielding each date as we go.
1455 def upto(max, &block) # :yield: date
1456 step(max, +1, &block)
1459 # Step backward one day at a time until we reach +min+
1460 # (inclusive), yielding each date as we go.
1461 def downto(min, &block) # :yield: date
1462 step(min, -1, &block)
1465 # Is this Date equal to +other+?
1467 # +other+ must both be a Date object, and represent the same date.
1468 def eql? (other) Date === other && self == other end
1470 # Calculate a hash value for this date.
1471 def hash() @ajd.hash end
1473 # Return internal object state as a programmer-readable string.
1474 def inspect() format('#<%s: %s,%s,%s>', self.class, @ajd, @of, @sg) end
1476 # Return the date as a human-readable string.
1478 # The format used is YYYY-MM-DD.
1479 def to_s() format('%.4d-%02d-%02d', year, mon, mday) end # 4p
1481 # Dump to Marshal format.
1482 def marshal_dump() [@ajd, @of, @sg] end
1484 # Load from Marshal format.
1492 # Class representing a date and time.
1494 # See the documentation to the file date.rb for an overview.
1496 # DateTime objects are immutable once created.
1500 # The following methods are defined in Date, but declared private
1501 # there. They are made public in DateTime. They are documented
1506 # Get the hour-of-the-day of the time. This is given
1507 # using the 24-hour clock, counting from midnight. The first
1508 # hour after midnight is hour 0; the last hour of the day is
1513 # Get the minute-of-the-hour of the time.
1517 # Get the second-of-the-minute of the time.
1519 # === sec_fraction()
1521 # Get the fraction of a second of the time. This is returned as
1526 # Get the time zone as a String. This is representation of the
1527 # time offset such as "+1000", not the true time-zone name.
1531 # Get the time zone offset as a fraction of a day. This is returned
1534 # === new_offset(of=0)
1536 # Create a new DateTime object, identical to the current one, except
1537 # with a new time zone offset of +of+. +of+ is the new offset from
1538 # UTC as a fraction of a day.
1540 class DateTime < Date
1542 # Create a new DateTime object corresponding to the specified
1543 # Julian Day Number +jd+ and hour +h+, minute +min+, second +s+.
1545 # The 24-hour clock is used. Negative values of +h+, +min+, and
1546 # +sec+ are treating as counting backwards from the end of the
1547 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1548 # wraparound is performed. If an invalid time portion is specified,
1549 # an ArgumentError is raised.
1551 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1552 # +sg+ specifies the Day of Calendar Reform.
1554 # All day/time values default to 0.
1555 def self.jd(jd=0, h=0, min=0, s=0, of=0, sg=ITALY)
1556 unless (jd = _valid_jd?(jd, sg)) &&
1557 (fr = _valid_time?(h, min, s))
1558 raise ArgumentError, 'invalid date'
1561 of = Rational(zone_to_diff(of) || 0, 86400)
1563 new!(jd_to_ajd(jd, fr, of), of, sg)
1566 # Create a new DateTime object corresponding to the specified
1567 # Ordinal Date and hour +h+, minute +min+, second +s+.
1569 # The 24-hour clock is used. Negative values of +h+, +min+, and
1570 # +sec+ are treating as counting backwards from the end of the
1571 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1572 # wraparound is performed. If an invalid time portion is specified,
1573 # an ArgumentError is raised.
1575 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1576 # +sg+ specifies the Day of Calendar Reform.
1578 # +y+ defaults to -4712, and +d+ to 1; this is Julian Day Number
1579 # day 0. The time values default to 0.
1580 def self.ordinal(y=-4712, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1581 unless (jd = _valid_ordinal?(y, d, sg)) &&
1582 (fr = _valid_time?(h, min, s))
1583 raise ArgumentError, 'invalid date'
1586 of = Rational(zone_to_diff(of) || 0, 86400)
1588 new!(jd_to_ajd(jd, fr, of), of, sg)
1591 # Create a new DateTime object corresponding to the specified
1592 # Civil Date and hour +h+, minute +min+, second +s+.
1594 # The 24-hour clock is used. Negative values of +h+, +min+, and
1595 # +sec+ are treating as counting backwards from the end of the
1596 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1597 # wraparound is performed. If an invalid time portion is specified,
1598 # an ArgumentError is raised.
1600 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1601 # +sg+ specifies the Day of Calendar Reform.
1603 # +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is Julian Day
1604 # Number day 0. The time values default to 0.
1605 def self.civil(y=-4712, m=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1606 unless (jd = _valid_civil?(y, m, d, sg)) &&
1607 (fr = _valid_time?(h, min, s))
1608 raise ArgumentError, 'invalid date'
1611 of = Rational(zone_to_diff(of) || 0, 86400)
1613 new!(jd_to_ajd(jd, fr, of), of, sg)
1616 class << self; alias_method :new, :civil end
1618 # Create a new DateTime object corresponding to the specified
1619 # Commercial Date and hour +h+, minute +min+, second +s+.
1621 # The 24-hour clock is used. Negative values of +h+, +min+, and
1622 # +sec+ are treating as counting backwards from the end of the
1623 # next larger unit (e.g. a +min+ of -2 is treated as 58). No
1624 # wraparound is performed. If an invalid time portion is specified,
1625 # an ArgumentError is raised.
1627 # +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1628 # +sg+ specifies the Day of Calendar Reform.
1630 # +y+ defaults to -4712, +w+ to 1, and +d+ to 1; this is
1631 # Julian Day Number day 0.
1632 # The time values default to 0.
1633 def self.commercial(y=-4712, w=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1634 unless (jd = _valid_commercial?(y, w, d, sg)) &&
1635 (fr = _valid_time?(h, min, s))
1636 raise ArgumentError, 'invalid date'
1639 of = Rational(zone_to_diff(of) || 0, 86400)
1641 new!(jd_to_ajd(jd, fr, of), of, sg)
1644 def self.weeknum(y=-4712, w=0, d=1, f=0, h=0, min=0, s=0, of=0, sg=ITALY) # :nodoc:
1645 unless (jd = _valid_weeknum?(y, w, d, f, sg)) &&
1646 (fr = _valid_time?(h, min, s))
1647 raise ArgumentError, 'invalid date'
1650 of = Rational(zone_to_diff(of) || 0, 86400)
1652 new!(jd_to_ajd(jd, fr, of), of, sg)
1655 private_class_method :weeknum
1657 def self.nth_kday(y=-4712, m=1, n=1, k=1, h=0, min=0, s=0, of=0, sg=ITALY) # :nodoc:
1658 unless (jd = _valid_nth_kday?(y, m, n, k, sg)) &&
1659 (fr = _valid_time?(h, min, s))
1660 raise ArgumentError, 'invalid date'
1663 of = Rational(zone_to_diff(of) || 0, 86400)
1665 new!(jd_to_ajd(jd, fr, of), of, sg)
1668 private_class_method :nth_kday
1670 def self.new_by_frags(elem, sg) # :nodoc:
1671 elem = rewrite_frags(elem)
1672 elem = complete_frags(elem)
1673 unless (jd = valid_date_frags?(elem, sg)) &&
1674 (fr = valid_time_frags?(elem))
1675 raise ArgumentError, 'invalid date'
1677 fr += (elem[:sec_fraction] || 0) / 86400
1678 of = Rational(elem[:offset] || 0, 86400)
1679 new!(jd_to_ajd(jd, fr, of), of, sg)
1682 private_class_method :new_by_frags
1684 # Create a new DateTime object by parsing from a String
1685 # according to a specified format.
1687 # +str+ is a String holding a date-time representation.
1688 # +fmt+ is the format that the date-time is in. See
1689 # date/format.rb for details on supported formats.
1691 # The default +str+ is '-4712-01-01T00:00:00+00:00', and the default
1692 # +fmt+ is '%FT%T%z'. This gives midnight on Julian Day Number day 0.
1694 # +sg+ specifies the Day of Calendar Reform.
1696 # An ArgumentError will be raised if +str+ cannot be
1698 def self.strptime(str='-4712-01-01T00:00:00+00:00', fmt='%FT%T%z', sg=ITALY)
1699 elem = _strptime(str, fmt)
1700 new_by_frags(elem, sg)
1703 # Create a new DateTime object by parsing from a String,
1704 # without specifying the format.
1706 # +str+ is a String holding a date-time representation.
1707 # +comp+ specifies whether to interpret 2-digit years
1708 # as 19XX (>= 69) or 20XX (< 69); the default is not to.
1709 # The method will attempt to parse a date-time from the String
1710 # using various heuristics; see #_parse in date/format.rb
1711 # for more details. If parsing fails, an ArgumentError
1714 # The default +str+ is '-4712-01-01T00:00:00+00:00'; this is Julian
1717 # +sg+ specifies the Day of Calendar Reform.
1718 def self.parse(str='-4712-01-01T00:00:00+00:00', comp=true, sg=ITALY)
1719 elem = _parse(str, comp)
1720 new_by_frags(elem, sg)
1723 def self.iso8601(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1724 elem = _iso8601(str)
1725 new_by_frags(elem, sg)
1728 def self.rfc3339(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1729 elem = _rfc3339(str)
1730 new_by_frags(elem, sg)
1733 def self.xmlschema(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1734 elem = _xmlschema(str)
1735 new_by_frags(elem, sg)
1738 def self.rfc2822(str='Mon, 1 Jan -4712 00:00:00 +0000', sg=ITALY) # :nodoc:
1739 elem = _rfc2822(str)
1740 new_by_frags(elem, sg)
1743 class << self; alias_method :rfc822, :rfc2822 end
1745 def self.httpdate(str='Mon, 01 Jan -4712 00:00:00 GMT', sg=ITALY) # :nodoc:
1746 elem = _httpdate(str)
1747 new_by_frags(elem, sg)
1750 def self.jisx0301(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1751 elem = _jisx0301(str)
1752 new_by_frags(elem, sg)
1755 public :hour, :min, :sec, :sec_fraction, :zone, :offset, :new_offset,
1756 :minute, :second, :second_fraction
1759 format('%.4d-%02d-%02dT%02d:%02d:%02d%s',
1760 year, mon, mday, hour, min, sec, zone)
1767 def to_time() getlocal end
1770 jd = Date.__send__(:civil_to_jd, year, mon, mday, Date::ITALY)
1771 Date.new!(Date.__send__(:jd_to_ajd, jd, 0, 0), 0, Date::ITALY)
1775 jd = DateTime.__send__(:civil_to_jd, year, mon, mday, DateTime::ITALY)
1776 fr = DateTime.__send__(:time_to_day_fraction, hour, min, [sec, 59].min) +
1777 Rational(nsec, 86400_000_000_000)
1778 of = Rational(utc_offset, 86400)
1779 DateTime.new!(DateTime.__send__(:jd_to_ajd, jd, fr, of),
1780 of, DateTime::ITALY)
1787 def to_time() Time.local(year, mon, mday) end
1788 def to_date() self end
1789 def to_datetime() DateTime.new!(jd_to_ajd(jd, 0, 0), @of, @sg) end
1791 # Create a new Date object representing today.
1793 # +sg+ specifies the Day of Calendar Reform.
1794 def self.today(sg=ITALY) Time.now.to_date .new_start(sg) end
1796 # Create a new DateTime object representing the current time.
1798 # +sg+ specifies the Day of Calendar Reform.
1799 def self.now (sg=ITALY) Time.now.to_datetime.new_start(sg) end
1801 private_class_method :now
1805 class DateTime < Date
1810 Time.utc(year, mon, mday, hour, min, sec +
1816 def to_date() Date.new!(jd_to_ajd(jd, 0, 0), 0, @sg) end
1817 def to_datetime() self end
1819 private_class_method :today
1820 public_class_method :now