2
# date.rb - date and time library
4
# Author: Tadayoshi Funaba 1998-2011
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()
196
require 'date/format'
198
# Class representing a date.
200
# See the documentation to the file date.rb for an overview.
202
# Internally, the date is represented as an Astronomical
203
# Julian Day Number, +ajd+. The Day of Calendar Reform, +sg+, is
204
# also stored, for conversions to other date formats. (There
205
# is also an +of+ field for a time zone offset, but this
206
# is only for the use of the DateTime subclass.)
208
# A new Date object is created using one of the object creation
209
# class methods named after the corresponding date format, and the
210
# arguments appropriate to that date format; for instance,
211
# Date::civil() (aliased to Date::new()) with year, month,
212
# and day-of-month, or Date::ordinal() with year and day-of-year.
213
# All of these object creation class methods also take the
214
# Day of Calendar Reform as an optional argument.
216
# Date objects are immutable once created.
218
# Once a Date has been created, date values
219
# can be retrieved for the different date formats supported
220
# using instance methods. For instance, #mon() gives the
221
# Civil month, #cwday() gives the Commercial day of the week,
222
# and #yday() gives the Ordinal day of the year. Date values
223
# can be retrieved in any format, regardless of what format
224
# was used to create the Date instance.
226
# The Date class includes the Comparable module, allowing
227
# date objects to be compared and sorted, ranges of dates
228
# to be created, and so forth.
233
# Full month names, in English. Months count from 1 to 12; a
234
# month's numerical representation indexed into this array
235
# gives the name of that month (hence the first element is nil).
236
MONTHNAMES = [nil] + %w(January February March April May June July
237
August September October November December)
239
# Full names of days of the week, in English. Days of the week
240
# count from 0 to 6 (except in the commercial week); a day's numerical
241
# representation indexed into this array gives the name of that day.
242
DAYNAMES = %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
244
# Abbreviated month names, in English.
245
ABBR_MONTHNAMES = [nil] + %w(Jan Feb Mar Apr May Jun
246
Jul Aug Sep Oct Nov Dec)
248
# Abbreviated day names, in English.
249
ABBR_DAYNAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
251
[MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYNAMES].each do |xs|
252
xs.each{|x| x.freeze unless x.nil?}.freeze
255
class Infinity < Numeric # :nodoc:
259
def initialize(d=1) @d = d <=> 0 end
265
def zero? () false end
266
def finite? () false end
267
def infinite? () d.nonzero? end
268
def nan? () d.zero? end
270
def abs() self.class.new end
272
def -@ () self.class.new(-d) end
273
def +@ () self.class.new(+d) end
277
when Infinity; return d <=> other.d
278
when Numeric; return d
281
l, r = other.coerce(self)
291
when Numeric; return -d, d
299
# The Julian Day Number of the Day of Calendar Reform for Italy
300
# and the Catholic countries.
301
ITALY = 2299161 # 1582-10-15
303
# The Julian Day Number of the Day of Calendar Reform for England
305
ENGLAND = 2361222 # 1752-09-14
307
# A constant used to indicate that a Date should always use the
309
JULIAN = Infinity.new
311
# A constant used to indicate that a Date should always use the
312
# Gregorian calendar.
313
GREGORIAN = -Infinity.new
315
HALF_DAYS_IN_DAY = Rational(1, 2) # :nodoc:
316
HOURS_IN_DAY = Rational(1, 24) # :nodoc:
317
MINUTES_IN_DAY = Rational(1, 1440) # :nodoc:
318
SECONDS_IN_DAY = Rational(1, 86400) # :nodoc:
319
MILLISECONDS_IN_DAY = Rational(1, 86400*10**3) # :nodoc:
320
NANOSECONDS_IN_DAY = Rational(1, 86400*10**9) # :nodoc:
321
MILLISECONDS_IN_SECOND = Rational(1, 10**3) # :nodoc:
322
NANOSECONDS_IN_SECOND = Rational(1, 10**9) # :nodoc:
324
MJD_EPOCH_IN_AJD = Rational(4800001, 2) # 1858-11-17 # :nodoc:
325
UNIX_EPOCH_IN_AJD = Rational(4881175, 2) # 1970-01-01 # :nodoc:
326
MJD_EPOCH_IN_CJD = 2400001 # :nodoc:
327
UNIX_EPOCH_IN_CJD = 2440588 # :nodoc:
328
LD_EPOCH_IN_CJD = 2299160 # :nodoc:
334
def find_fdoy(y, sg) # :nodoc:
337
break if j = _valid_civil?(y, 1, d, sg)
342
def find_ldoy(y, sg) # :nodoc:
345
break if j = _valid_civil?(y, 12, d, sg)
350
def find_fdom(y, m, sg) # :nodoc:
353
break if j = _valid_civil?(y, m, d, sg)
358
def find_ldom(y, m, sg) # :nodoc:
361
break if j = _valid_civil?(y, m, d, sg)
366
# Convert an Ordinal Date to a Julian Day Number.
368
# +y+ and +d+ are the year and day-of-year to convert.
369
# +sg+ specifies the Day of Calendar Reform.
371
# Returns the corresponding Julian Day Number.
372
def ordinal_to_jd(y, d, sg=GREGORIAN) # :nodoc:
373
find_fdoy(y, sg) + d - 1
376
# Convert a Julian Day Number to an Ordinal Date.
378
# +jd+ is the Julian Day Number to convert.
379
# +sg+ specifies the Day of Calendar Reform.
381
# Returns the corresponding Ordinal Date as
382
# [year, day_of_year]
383
def jd_to_ordinal(jd, sg=GREGORIAN) # :nodoc:
384
y = jd_to_civil(jd, sg)[0]
390
# Convert a Civil Date to a Julian Day Number.
391
# +y+, +m+, and +d+ are the year, month, and day of the
392
# month. +sg+ specifies the Day of Calendar Reform.
394
# Returns the corresponding Julian Day Number.
395
def civil_to_jd(y, m, d, sg=GREGORIAN) # :nodoc:
400
a = (y / 100.0).floor
401
b = 2 - a + (a / 4.0).floor
402
jd = (365.25 * (y + 4716)).floor +
403
(30.6001 * (m + 1)).floor +
411
# Convert a Julian Day Number to a Civil Date. +jd+ is
412
# the Julian Day Number. +sg+ specifies the Day of
415
# Returns the corresponding [year, month, day_of_month]
416
# as a three-element array.
417
def jd_to_civil(jd, sg=GREGORIAN) # :nodoc:
421
x = ((jd - 1867216.25) / 36524.25).floor
422
a = jd + 1 + x - (x / 4.0).floor
425
c = ((b - 122.1) / 365.25).floor
426
d = (365.25 * c).floor
427
e = ((b - d) / 30.6001).floor
428
dom = b - d - (30.6001 * e).floor
439
# Convert a Commercial Date to a Julian Day Number.
441
# +y+, +w+, and +d+ are the (commercial) year, week of the year,
442
# and day of the week of the Commercial Date to convert.
443
# +sg+ specifies the Day of Calendar Reform.
444
def commercial_to_jd(y, w, d, sg=GREGORIAN) # :nodoc:
445
j = find_fdoy(y, sg) + 3
446
(j - (((j - 1) + 1) % 7)) +
451
# Convert a Julian Day Number to a Commercial Date
453
# +jd+ is the Julian Day Number to convert.
454
# +sg+ specifies the Day of Calendar Reform.
456
# Returns the corresponding Commercial Date as
457
# [commercial_year, week_of_year, day_of_week]
458
def jd_to_commercial(jd, sg=GREGORIAN) # :nodoc:
459
a = jd_to_civil(jd - 3, sg)[0]
460
y = if jd >= commercial_to_jd(a + 1, 1, 1, sg) then a + 1 else a end
461
w = 1 + ((jd - commercial_to_jd(y, 1, 1, sg)) / 7).floor
467
def weeknum_to_jd(y, w, d, f=0, sg=GREGORIAN) # :nodoc:
468
a = find_fdoy(y, sg) + 6
469
(a - ((a - f) + 1) % 7 - 7) + 7 * w + d
472
def jd_to_weeknum(jd, f=0, sg=GREGORIAN) # :nodoc:
473
y, m, d = jd_to_civil(jd, sg)
474
a = find_fdoy(y, sg) + 6
475
w, d = (jd - (a - ((a - f) + 1) % 7) + 7).divmod(7)
479
def nth_kday_to_jd(y, m, n, k, sg=GREGORIAN) # :nodoc:
481
find_fdom(y, m, sg) - 1
483
find_ldom(y, m, sg) + 7
485
(j - (((j - k) + 1) % 7)) + 7 * n
488
def jd_to_nth_kday(jd, sg=GREGORIAN) # :nodoc:
489
y, m, d = jd_to_civil(jd, sg)
490
j = find_fdom(y, m, sg)
491
return y, m, ((jd - j) / 7).floor + 1, jd_to_wday(jd)
494
# Convert an Astronomical Julian Day Number to a (civil) Julian
497
# +ajd+ is the Astronomical Julian Day Number to convert.
498
# +of+ is the offset from UTC as a fraction of a day (defaults to 0).
500
# Returns the (civil) Julian Day Number as [day_number,
501
# fraction] where +fraction+ is always 1/2.
502
def ajd_to_jd(ajd, of=0) (ajd + of + HALF_DAYS_IN_DAY).divmod(1) end # :nodoc:
504
# Convert a (civil) Julian Day Number to an Astronomical Julian
507
# +jd+ is the Julian Day Number to convert, and +fr+ is a
509
# +of+ is the offset from UTC as a fraction of a day (defaults to 0).
511
# Returns the Astronomical Julian Day Number as a single
513
def jd_to_ajd(jd, fr, of=0) jd + fr - of - HALF_DAYS_IN_DAY end # :nodoc:
515
# Convert a fractional day +fr+ to [hours, minutes, seconds,
516
# fraction_of_a_second]
517
def day_fraction_to_time(fr) # :nodoc:
518
ss, fr = fr.divmod(SECONDS_IN_DAY) # 4p
519
h, ss = ss.divmod(3600)
520
min, s = ss.divmod(60)
521
return h, min, s, fr * 86400
524
# Convert an +h+ hour, +min+ minutes, +s+ seconds period
525
# to a fractional day.
527
Rational(Rational(1, 2), 2) # a challenge
529
def time_to_day_fraction(h, min, s)
530
Rational(h * 3600 + min * 60 + s, 86400) # 4p
533
def time_to_day_fraction(h, min, s)
534
if Integer === h && Integer === min && Integer === s
535
Rational(h * 3600 + min * 60 + s, 86400) # 4p
537
(h * 3600 + min * 60 + s).to_r/86400 # 4p
542
# Convert an Astronomical Modified Julian Day Number to an
543
# Astronomical Julian Day Number.
544
def amjd_to_ajd(amjd) amjd + MJD_EPOCH_IN_AJD end # :nodoc:
546
# Convert an Astronomical Julian Day Number to an
547
# Astronomical Modified Julian Day Number.
548
def ajd_to_amjd(ajd) ajd - MJD_EPOCH_IN_AJD end # :nodoc:
550
# Convert a Modified Julian Day Number to a Julian
552
def mjd_to_jd(mjd) mjd + MJD_EPOCH_IN_CJD end # :nodoc:
554
# Convert a Julian Day Number to a Modified Julian Day
556
def jd_to_mjd(jd) jd - MJD_EPOCH_IN_CJD end # :nodoc:
558
# Convert a count of the number of days since the adoption
559
# of the Gregorian Calendar (in Italy) to a Julian Day Number.
560
def ld_to_jd(ld) ld + LD_EPOCH_IN_CJD end # :nodoc:
562
# Convert a Julian Day Number to the number of days since
563
# the adoption of the Gregorian Calendar (in Italy).
564
def jd_to_ld(jd) jd - LD_EPOCH_IN_CJD end # :nodoc:
566
# Convert a Julian Day Number to the day of the week.
568
# Sunday is day-of-week 0; Saturday is day-of-week 6.
569
def jd_to_wday(jd) (jd + 1) % 7 end # :nodoc:
571
# Is +jd+ a valid Julian Day Number?
573
# If it is, returns it. In fact, any value is treated as a valid
575
def _valid_jd? (jd, sg=GREGORIAN) jd end # :nodoc:
577
# Do the year +y+ and day-of-year +d+ make a valid Ordinal Date?
578
# Returns the corresponding Julian Day Number if they do, or
581
# +d+ can be a negative number, in which case it counts backwards
582
# from the end of the year (-1 being the last day of the year).
583
# No year wraparound is performed, however, so valid values of
584
# +d+ are -365 .. -1, 1 .. 365 on a non-leap-year,
585
# -366 .. -1, 1 .. 366 on a leap year.
586
# A date falling in the period skipped in the Day of Calendar Reform
587
# adjustment is not valid.
589
# +sg+ specifies the Day of Calendar Reform.
590
def _valid_ordinal? (y, d, sg=GREGORIAN) # :nodoc:
592
return unless j = find_ldoy(y, sg)
593
ny, nd = jd_to_ordinal(j + d + 1, sg)
594
return unless ny == y
597
jd = ordinal_to_jd(y, d, sg)
598
return unless [y, d] == jd_to_ordinal(jd, sg)
602
# Do year +y+, month +m+, and day-of-month +d+ make a
603
# valid Civil Date? Returns the corresponding Julian
604
# Day Number if they do, nil if they don't.
606
# +m+ and +d+ can be negative, in which case they count
607
# backwards from the end of the year and the end of the
608
# month respectively. No wraparound is performed, however,
609
# and invalid values cause an ArgumentError to be raised.
610
# A date falling in the period skipped in the Day of Calendar
611
# Reform adjustment is not valid.
613
# +sg+ specifies the Day of Calendar Reform.
614
def _valid_civil? (y, m, d, sg=GREGORIAN) # :nodoc:
619
return unless j = find_ldom(y, m, sg)
620
ny, nm, nd = jd_to_civil(j + d + 1, sg)
621
return unless [ny, nm] == [y, m]
624
jd = civil_to_jd(y, m, d, sg)
625
return unless [y, m, d] == jd_to_civil(jd, sg)
629
# Do year +y+, week-of-year +w+, and day-of-week +d+ make a
630
# valid Commercial Date? Returns the corresponding Julian
631
# Day Number if they do, nil if they don't.
633
# Monday is day-of-week 1; Sunday is day-of-week 7.
635
# +w+ and +d+ can be negative, in which case they count
636
# backwards from the end of the year and the end of the
637
# week respectively. No wraparound is performed, however,
638
# and invalid values cause an ArgumentError to be raised.
639
# A date falling in the period skipped in the Day of Calendar
640
# Reform adjustment is not valid.
642
# +sg+ specifies the Day of Calendar Reform.
643
def _valid_commercial? (y, w, d, sg=GREGORIAN) # :nodoc:
649
jd_to_commercial(commercial_to_jd(y + 1, 1, 1, sg) + w * 7, sg)
650
return unless ny == y
653
jd = commercial_to_jd(y, w, d, sg)
654
return unless [y, w, d] == jd_to_commercial(jd, sg)
658
def _valid_weeknum? (y, w, d, f, sg=GREGORIAN) # :nodoc:
664
jd_to_weeknum(weeknum_to_jd(y + 1, 1, f, f, sg) + w * 7, f, sg)
665
return unless ny == y
668
jd = weeknum_to_jd(y, w, d, f, sg)
669
return unless [y, w, d] == jd_to_weeknum(jd, f, sg)
673
def _valid_nth_kday? (y, m, n, k, sg=GREGORIAN) # :nodoc:
678
ny, nm = (y * 12 + m).divmod(12)
679
nm, = (nm + 1) .divmod(1)
681
jd_to_nth_kday(nth_kday_to_jd(ny, nm, 1, k, sg) + n * 7, sg)
682
return unless [ny, nm] == [y, m]
685
jd = nth_kday_to_jd(y, m, n, k, sg)
686
return unless [y, m, n, k] == jd_to_nth_kday(jd, sg)
690
# Do hour +h+, minute +min+, and second +s+ constitute a valid time?
692
# If they do, returns their value as a fraction of a day. If not,
695
# The 24-hour clock is used. Negative values of +h+, +min+, and
696
# +sec+ are treating as counting backwards from the end of the
697
# next larger unit (e.g. a +min+ of -2 is treated as 58). No
698
# wraparound is performed.
699
def _valid_time? (h, min, s) # :nodoc:
703
return unless ((0...24) === h &&
709
time_to_day_fraction(h, min, s)
717
# Is a year a leap year in the Julian calendar?
719
# All years divisible by 4 are leap years in the Julian calendar.
720
def self.julian_leap? (y) y % 4 == 0 end
722
# Is a year a leap year in the Gregorian calendar?
724
# All years divisible by 4 are leap years in the Gregorian calendar,
725
# except for years divisible by 100 and not by 400.
726
def self.gregorian_leap? (y) y % 4 == 0 && y % 100 != 0 || y % 400 == 0 end
728
class << self; alias_method :leap?, :gregorian_leap? end
729
class << self; alias_method :new!, :new end
731
def self.valid_jd? (jd, sg=ITALY)
735
def self.valid_ordinal? (y, d, sg=ITALY)
736
!!_valid_ordinal?(y, d, sg)
739
def self.valid_civil? (y, m, d, sg=ITALY)
740
!!_valid_civil?(y, m, d, sg)
743
class << self; alias_method :valid_date?, :valid_civil? end
745
def self.valid_commercial? (y, w, d, sg=ITALY)
746
!!_valid_commercial?(y, w, d, sg)
749
def self.valid_weeknum? (y, w, d, f, sg=ITALY) # :nodoc:
750
!!_valid_weeknum?(y, w, d, f, sg)
753
private_class_method :valid_weeknum?
755
def self.valid_nth_kday? (y, m, n, k, sg=ITALY) # :nodoc:
756
!!_valid_nth_kday?(y, m, n, k, sg)
759
private_class_method :valid_nth_kday?
761
def self.valid_time? (h, min, s) # :nodoc:
762
!!_valid_time?(h, min, s)
765
private_class_method :valid_time?
767
# Create a new Date object from a Julian Day Number.
769
# +jd+ is the Julian Day Number; if not specified, it defaults to
771
# +sg+ specifies the Day of Calendar Reform.
772
def self.jd(jd=0, sg=ITALY)
773
jd = _valid_jd?(jd, sg)
774
new!(jd_to_ajd(jd, 0, 0), 0, sg)
777
# Create a new Date object from an Ordinal Date, specified
778
# by year +y+ and day-of-year +d+. +d+ can be negative,
779
# in which it counts backwards from the end of the year.
780
# No year wraparound is performed, however. An invalid
781
# value for +d+ results in an ArgumentError being raised.
783
# +y+ defaults to -4712, and +d+ to 1; this is Julian Day
786
# +sg+ specifies the Day of Calendar Reform.
787
def self.ordinal(y=-4712, d=1, sg=ITALY)
788
unless jd = _valid_ordinal?(y, d, sg)
789
raise ArgumentError, 'invalid date'
791
new!(jd_to_ajd(jd, 0, 0), 0, sg)
794
# Create a new Date object for the Civil Date specified by
795
# year +y+, month +m+, and day-of-month +d+.
797
# +m+ and +d+ can be negative, in which case they count
798
# backwards from the end of the year and the end of the
799
# month respectively. No wraparound is performed, however,
800
# and invalid values cause an ArgumentError to be raised.
803
# +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is
804
# Julian Day Number day 0.
806
# +sg+ specifies the Day of Calendar Reform.
807
def self.civil(y=-4712, m=1, d=1, sg=ITALY)
808
unless jd = _valid_civil?(y, m, d, sg)
809
raise ArgumentError, 'invalid date'
811
new!(jd_to_ajd(jd, 0, 0), 0, sg)
814
class << self; alias_method :new, :civil end
816
# Create a new Date object for the Commercial Date specified by
817
# year +y+, week-of-year +w+, and day-of-week +d+.
819
# Monday is day-of-week 1; Sunday is day-of-week 7.
821
# +w+ and +d+ can be negative, in which case they count
822
# backwards from the end of the year and the end of the
823
# week respectively. No wraparound is performed, however,
824
# and invalid values cause an ArgumentError to be raised.
826
# +y+ defaults to -4712, +w+ to 1, and +d+ to 1; this is
827
# Julian Day Number day 0.
829
# +sg+ specifies the Day of Calendar Reform.
830
def self.commercial(y=-4712, w=1, d=1, sg=ITALY)
831
unless jd = _valid_commercial?(y, w, d, sg)
832
raise ArgumentError, 'invalid date'
834
new!(jd_to_ajd(jd, 0, 0), 0, sg)
837
def self.weeknum(y=-4712, w=0, d=1, f=0, sg=ITALY)
838
unless jd = _valid_weeknum?(y, w, d, f, sg)
839
raise ArgumentError, 'invalid date'
841
new!(jd_to_ajd(jd, 0, 0), 0, sg)
844
private_class_method :weeknum
846
def self.nth_kday(y=-4712, m=1, n=1, k=1, sg=ITALY)
847
unless jd = _valid_nth_kday?(y, m, n, k, sg)
848
raise ArgumentError, 'invalid date'
850
new!(jd_to_ajd(jd, 0, 0), 0, sg)
853
private_class_method :nth_kday
855
def self.rewrite_frags(elem) # :nodoc:
857
if seconds = elem[:seconds]
858
d, fr = seconds.divmod(86400)
859
h, fr = fr.divmod(3600)
860
min, fr = fr.divmod(60)
862
elem[:jd] = UNIX_EPOCH_IN_CJD + d
866
elem[:sec_fraction] = fr
867
elem.delete(:seconds)
873
private_class_method :rewrite_frags
875
def self.complete_frags(elem) # :nodoc:
877
g = [[:time, [:hour, :min, :sec]],
879
[:ordinal, [:year, :yday, :hour, :min, :sec]],
880
[:civil, [:year, :mon, :mday, :hour, :min, :sec]],
881
[:commercial, [:cwyear, :cweek, :cwday, :hour, :min, :sec]],
882
[:wday, [:wday, :hour, :min, :sec]],
883
[:wnum0, [:year, :wnum0, :wday, :hour, :min, :sec]],
884
[:wnum1, [:year, :wnum1, :wday, :hour, :min, :sec]],
885
[nil, [:cwyear, :cweek, :wday, :hour, :min, :sec]],
886
[nil, [:year, :wnum0, :cwday, :hour, :min, :sec]],
887
[nil, [:year, :wnum1, :cwday, :hour, :min, :sec]]].
888
collect{|k, a| e = elem.values_at(*a).compact; [k, a, e]}.
889
select{|k, a, e| e.size > 0}.
890
sort_by{|k, a, e| [e.size, i -= 1]}.last
894
if g && g[0] && (g[1].size - g[2].size) != 0
899
elem[:year] ||= d.year
904
elem[e] = d.__send__(e)
911
elem[e] = d.__send__(e)
916
elem[:jd] ||= (d - d.wday + elem[:wday]).jd
920
elem[e] = d.__send__(e)
927
elem[e] = d.__send__(e)
934
if g && g[0] == :time
944
elem[:sec] = [elem[:sec], 59].min
949
private_class_method :complete_frags
951
def self.valid_date_frags?(elem, sg) # :nodoc:
953
a = elem.values_at(:jd)
955
if jd = _valid_jd?(*(a << sg))
960
a = elem.values_at(:year, :yday)
962
if jd = _valid_ordinal?(*(a << sg))
967
a = elem.values_at(:year, :mon, :mday)
969
if jd = _valid_civil?(*(a << sg))
974
a = elem.values_at(:cwyear, :cweek, :cwday)
975
if a[2].nil? && elem[:wday]
976
a[2] = elem[:wday].nonzero? || 7
979
if jd = _valid_commercial?(*(a << sg))
984
a = elem.values_at(:year, :wnum0, :wday)
985
if a[2].nil? && elem[:cwday]
986
a[2] = elem[:cwday] % 7
989
if jd = _valid_weeknum?(*(a << 0 << sg))
994
a = elem.values_at(:year, :wnum1, :wday)
996
a[2] = (a[2] - 1) % 7
998
if a[2].nil? && elem[:cwday]
999
a[2] = (elem[:cwday] - 1) % 7
1002
if jd = _valid_weeknum?(*(a << 1 << sg))
1009
private_class_method :valid_date_frags?
1011
def self.valid_time_frags? (elem) # :nodoc:
1012
h, min, s = elem.values_at(:hour, :min, :sec)
1013
_valid_time?(h, min, s)
1016
private_class_method :valid_time_frags?
1018
def self.new_by_frags(elem, sg) # :nodoc:
1019
elem = rewrite_frags(elem)
1020
elem = complete_frags(elem)
1021
unless jd = valid_date_frags?(elem, sg)
1022
raise ArgumentError, 'invalid date'
1024
new!(jd_to_ajd(jd, 0, 0), 0, sg)
1027
private_class_method :new_by_frags
1029
# Create a new Date object by parsing from a String
1030
# according to a specified format.
1032
# +str+ is a String holding a date representation.
1033
# +fmt+ is the format that the date is in. See
1034
# date/format.rb for details on supported formats.
1036
# The default +str+ is '-4712-01-01', and the default
1037
# +fmt+ is '%F', which means Year-Month-Day_of_Month.
1038
# This gives Julian Day Number day 0.
1040
# +sg+ specifies the Day of Calendar Reform.
1042
# An ArgumentError will be raised if +str+ cannot be
1044
def self.strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
1045
elem = _strptime(str, fmt)
1046
new_by_frags(elem, sg)
1049
# Create a new Date object by parsing from a String,
1050
# without specifying the format.
1052
# +str+ is a String holding a date representation.
1053
# +comp+ specifies whether to interpret 2-digit years
1054
# as 19XX (>= 69) or 20XX (< 69); the default is not to.
1055
# The method will attempt to parse a date from the String
1056
# using various heuristics; see #_parse in date/format.rb
1057
# for more details. If parsing fails, an ArgumentError
1060
# The default +str+ is '-4712-01-01'; this is Julian
1063
# +sg+ specifies the Day of Calendar Reform.
1064
def self.parse(str='-4712-01-01', comp=true, sg=ITALY)
1065
elem = _parse(str, comp)
1066
new_by_frags(elem, sg)
1069
def self.iso8601(str='-4712-01-01', sg=ITALY) # :nodoc:
1070
elem = _iso8601(str)
1071
new_by_frags(elem, sg)
1074
def self.rfc3339(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1075
elem = _rfc3339(str)
1076
new_by_frags(elem, sg)
1079
def self.xmlschema(str='-4712-01-01', sg=ITALY) # :nodoc:
1080
elem = _xmlschema(str)
1081
new_by_frags(elem, sg)
1084
def self.rfc2822(str='Mon, 1 Jan -4712 00:00:00 +0000', sg=ITALY) # :nodoc:
1085
elem = _rfc2822(str)
1086
new_by_frags(elem, sg)
1089
class << self; alias_method :rfc822, :rfc2822 end
1091
def self.httpdate(str='Mon, 01 Jan -4712 00:00:00 GMT', sg=ITALY) # :nodoc:
1092
elem = _httpdate(str)
1093
new_by_frags(elem, sg)
1096
def self.jisx0301(str='-4712-01-01', sg=ITALY) # :nodoc:
1097
elem = _jisx0301(str)
1098
new_by_frags(elem, sg)
1103
def once(*ids) # :nodoc: -- restricted
1105
module_eval <<-"end;"
1106
alias_method :__#{id.object_id}__, :#{id.to_s}
1107
private :__#{id.object_id}__
1108
def #{id.to_s}(*args)
1109
@__ca__[#{id.object_id}] ||= __#{id.object_id}__(*args)
1119
# *NOTE* this is the documentation for the method new!(). If
1120
# you are reading this as the documentation for new(), that is
1121
# because rdoc doesn't fully support the aliasing of the
1122
# initialize() method.
1124
# fact an alias for #civil(): read the documentation for that
1127
# Create a new Date object.
1129
# +ajd+ is the Astronomical Julian Day Number.
1130
# +of+ is the offset from UTC as a fraction of a day.
1131
# Both default to 0.
1133
# +sg+ specifies the Day of Calendar Reform to use for this
1136
# Using one of the factory methods such as Date::civil is
1137
# generally easier and safer.
1138
def initialize(ajd=0, of=0, sg=ITALY)
1139
@ajd, @of, @sg = ajd, of, sg
1143
# Get the date as an Astronomical Julian Day Number.
1146
# Get the date as an Astronomical Modified Julian Day Number.
1147
def amjd() ajd_to_amjd(@ajd) end
1151
# Get the date as a Julian Day Number.
1152
def jd() ajd_to_jd(@ajd, @of)[0] end
1154
# Get any fractional day part of the date.
1155
def day_fraction() ajd_to_jd(@ajd, @of)[1] end
1157
# Get the date as a Modified Julian Day Number.
1158
def mjd() jd_to_mjd(jd) end
1160
# Get the date as the number of days since the Day of Calendar
1161
# Reform (in Italy and the Catholic countries).
1162
def ld() jd_to_ld(jd) end
1164
once :jd, :day_fraction, :mjd, :ld
1166
# Get the date as a Civil Date, [year, month, day_of_month]
1167
def civil() jd_to_civil(jd, @sg) end # :nodoc:
1169
# Get the date as an Ordinal Date, [year, day_of_year]
1170
def ordinal() jd_to_ordinal(jd, @sg) end # :nodoc:
1172
# Get the date as a Commercial Date, [year, week_of_year, day_of_week]
1173
def commercial() jd_to_commercial(jd, @sg) end # :nodoc:
1175
def weeknum0() jd_to_weeknum(jd, 0, @sg) end # :nodoc:
1176
def weeknum1() jd_to_weeknum(jd, 1, @sg) end # :nodoc:
1178
once :civil, :ordinal, :commercial, :weeknum0, :weeknum1
1179
private :civil, :ordinal, :commercial, :weeknum0, :weeknum1
1181
# Get the year of this date.
1182
def year() civil[0] end
1184
# Get the day-of-the-year of this date.
1186
# January 1 is day-of-the-year 1
1187
def yday() ordinal[1] end
1189
# Get the month of this date.
1191
# January is month 1.
1192
def mon() civil[1] end
1194
# Get the day-of-the-month of this date.
1195
def mday() civil[2] end
1197
alias_method :month, :mon
1198
alias_method :day, :mday
1200
def wnum0() weeknum0[1] end # :nodoc:
1201
def wnum1() weeknum1[1] end # :nodoc:
1203
private :wnum0, :wnum1
1205
# Get the time of this date as [hours, minutes, seconds,
1206
# fraction_of_a_second]
1207
def time() day_fraction_to_time(day_fraction) end # :nodoc:
1212
# Get the hour of this date.
1213
def hour() time[0] end
1215
# Get the minute of this date.
1216
def min() time[1] end
1218
# Get the second of this date.
1219
def sec() time[2] end
1221
# Get the fraction-of-a-second of this date.
1222
def sec_fraction() time[3] end
1224
alias_method :minute, :min
1225
alias_method :second, :sec
1226
alias_method :second_fraction, :sec_fraction
1228
private :hour, :min, :sec, :sec_fraction,
1229
:minute, :second, :second_fraction
1231
def zone() strftime('%:z') end
1235
# Get the commercial year of this date. See *Commercial* *Date*
1236
# in the introduction for how this differs from the normal year.
1237
def cwyear() commercial[0] end
1239
# Get the commercial week of the year of this date.
1240
def cweek() commercial[1] end
1242
# Get the commercial day of the week of this date. Monday is
1243
# commercial day-of-week 1; Sunday is commercial day-of-week 7.
1244
def cwday() commercial[2] end
1246
# Get the week day of this date. Sunday is day-of-week 0;
1247
# Saturday is day-of-week 6.
1248
def wday() jd_to_wday(jd) end
1253
MONTHNAMES.each_with_index do |n, i|
1255
define_method(n.downcase + '?'){mon == i}
1260
DAYNAMES.each_with_index do |n, i|
1261
define_method(n.downcase + '?'){wday == i}
1264
def nth_kday? (n, k)
1265
k == wday && jd === nth_kday_to_jd(year, mon, n, k, start)
1270
# Is the current date old-style (Julian Calendar)?
1271
def julian? () jd < @sg end
1273
# Is the current date new-style (Gregorian Calendar)?
1274
def gregorian? () !julian? end
1276
once :julian?, :gregorian?
1278
def fix_style # :nodoc:
1280
then self.class::JULIAN
1281
else self.class::GREGORIAN end
1286
# Is this a leap year?
1288
jd_to_civil(civil_to_jd(year, 3, 1, fix_style) - 1,
1289
fix_style)[-1] == 29
1294
# When is the Day of Calendar Reform for this Date object?
1297
# Create a copy of this Date object using a new Day of Calendar Reform.
1298
def new_start(sg=self.class::ITALY) self.class.new!(@ajd, @of, sg) end
1300
# Create a copy of this Date object that uses the Italian/Catholic
1301
# Day of Calendar Reform.
1302
def italy() new_start(self.class::ITALY) end
1304
# Create a copy of this Date object that uses the English/Colonial
1305
# Day of Calendar Reform.
1306
def england() new_start(self.class::ENGLAND) end
1308
# Create a copy of this Date object that always uses the Julian
1310
def julian() new_start(self.class::JULIAN) end
1312
# Create a copy of this Date object that always uses the Gregorian
1314
def gregorian() new_start(self.class::GREGORIAN) end
1316
def offset() @of end
1318
def new_offset(of=0)
1320
of = Rational(zone_to_diff(of) || 0, 86400)
1322
self.class.new!(@ajd, of, @sg)
1325
private :offset, :new_offset
1327
# Return a new Date object that is +n+ days later than the
1330
# +n+ may be a negative value, in which case the new Date
1331
# is earlier than the current one; however, #-() might be
1334
# If +n+ is not a Numeric, a TypeError will be thrown. In
1335
# particular, two Dates cannot be added to each other.
1338
when Numeric; return self.class.new!(@ajd + n, @of, @sg)
1340
raise TypeError, 'expected numeric'
1343
# If +x+ is a Numeric value, create a new Date object that is
1344
# +x+ days earlier than the current one.
1346
# If +x+ is a Date, return the number of days between the
1347
# two dates; or, more precisely, how many days later the current
1350
# If +x+ is neither Numeric nor a Date, a TypeError is raised.
1353
when Numeric; return self.class.new!(@ajd - x, @of, @sg)
1354
when Date; return @ajd - x.ajd
1356
raise TypeError, 'expected numeric or date'
1359
# Compare this date with another date.
1361
# +other+ can also be a Numeric value, in which case it is
1362
# interpreted as an Astronomical Julian Day Number.
1364
# Comparison is by Astronomical Julian Day Number, including
1365
# fractional days. This means that both the time and the
1366
# timezone offset are taken into account when comparing
1367
# two DateTime instances. When comparing a DateTime instance
1368
# with a Date instance, the time of the latter will be
1369
# considered as falling on midnight UTC.
1372
when Numeric; return @ajd <=> other
1373
when Date; return @ajd <=> other.ajd
1376
l, r = other.coerce(self)
1378
rescue NoMethodError
1384
# The relationship operator for Date.
1386
# Compares dates by Julian Day Number. When comparing
1387
# two DateTime instances, or a DateTime with a Date,
1388
# the instances will be regarded as equivalent if they
1389
# fall on the same date in local time.
1392
when Numeric; return jd == other
1393
when Date; return jd == other.jd
1396
l, r = other.coerce(self)
1398
rescue NoMethodError
1404
def next_day(n=1) self + n end
1405
def prev_day(n=1) self - n end
1407
# Return a new Date one day after this one.
1408
def next() next_day end
1410
alias_method :succ, :next
1412
# Return a new Date object that is +n+ months later than
1415
# If the day-of-the-month of the current Date is greater
1416
# than the last day of the target month, the day-of-the-month
1417
# of the returned Date will be the last day of the target month.
1419
y, m = (year * 12 + (mon - 1) + n).divmod(12)
1420
m, = (m + 1) .divmod(1)
1422
until jd2 = _valid_civil?(y, m, d, @sg)
1424
raise ArgumentError, 'invalid date' unless d > 0
1429
# Return a new Date object that is +n+ months earlier than
1432
# If the day-of-the-month of the current Date is greater
1433
# than the last day of the target month, the day-of-the-month
1434
# of the returned Date will be the last day of the target month.
1435
def << (n) self >> -n end
1437
def next_month(n=1) self >> n end
1438
def prev_month(n=1) self << n end
1440
def next_year(n=1) self >> n * 12 end
1441
def prev_year(n=1) self << n * 12 end
1443
require 'enumerator'
1445
# Step the current date forward +step+ days at a
1446
# time (or backward, if +step+ is negative) until
1447
# we reach +limit+ (inclusive), yielding the resultant
1448
# date at each step.
1449
def step(limit, step=1) # :yield: date
1452
raise ArgumentError, "step can't be 0"
1456
return to_enum(:step, limit, step)
1459
op = %w(- <= >=)[step <=> 0]
1460
while da.__send__(op, limit)
1467
# Step forward one day at a time until we reach +max+
1468
# (inclusive), yielding each date as we go.
1469
def upto(max, &block) # :yield: date
1470
step(max, +1, &block)
1473
# Step backward one day at a time until we reach +min+
1474
# (inclusive), yielding each date as we go.
1475
def downto(min, &block) # :yield: date
1476
step(min, -1, &block)
1479
# Is this Date equal to +other+?
1481
# +other+ must both be a Date object, and represent the same date.
1482
def eql? (other) Date === other && self == other end
1484
# Calculate a hash value for this date.
1485
def hash() @ajd.hash end
1487
# Return internal object state as a programmer-readable string.
1489
format('#<%s: %s (%s,%s,%s)>', self.class, to_s, @ajd, @of, @sg)
1492
# Return the date as a human-readable string.
1494
# The format used is YYYY-MM-DD.
1495
def to_s() format('%.4d-%02d-%02d', year, mon, mday) end # 4p
1497
# Dump to Marshal format.
1498
def marshal_dump() [@ajd, @of, @sg] end
1500
# Load from Marshal format.
1508
# Class representing a date and time.
1510
# See the documentation to the file date.rb for an overview.
1512
# DateTime objects are immutable once created.
1516
# The following methods are defined in Date, but declared private
1517
# there. They are made public in DateTime. They are documented
1522
# Get the hour-of-the-day of the time. This is given
1523
# using the 24-hour clock, counting from midnight. The first
1524
# hour after midnight is hour 0; the last hour of the day is
1529
# Get the minute-of-the-hour of the time.
1533
# Get the second-of-the-minute of the time.
1535
# === sec_fraction()
1537
# Get the fraction of a second of the time. This is returned as
1542
# Get the time zone as a String. This is representation of the
1543
# time offset such as "+1000", not the true time-zone name.
1547
# Get the time zone offset as a fraction of a day. This is returned
1550
# === new_offset(of=0)
1552
# Create a new DateTime object, identical to the current one, except
1553
# with a new time zone offset of +of+. +of+ is the new offset from
1554
# UTC as a fraction of a day.
1556
class DateTime < Date
1558
# Create a new DateTime object corresponding to the specified
1559
# Julian Day Number +jd+ and hour +h+, minute +min+, second +s+.
1561
# The 24-hour clock is used. Negative values of +h+, +min+, and
1562
# +sec+ are treating as counting backwards from the end of the
1563
# next larger unit (e.g. a +min+ of -2 is treated as 58). No
1564
# wraparound is performed. If an invalid time portion is specified,
1565
# an ArgumentError is raised.
1567
# +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1568
# +sg+ specifies the Day of Calendar Reform.
1570
# All day/time values default to 0.
1571
def self.jd(jd=0, h=0, min=0, s=0, of=0, sg=ITALY)
1572
unless (jd = _valid_jd?(jd, sg)) &&
1573
(fr = _valid_time?(h, min, s))
1574
raise ArgumentError, 'invalid date'
1577
of = Rational(zone_to_diff(of) || 0, 86400)
1579
new!(jd_to_ajd(jd, fr, of), of, sg)
1582
# Create a new DateTime object corresponding to the specified
1583
# Ordinal Date and hour +h+, minute +min+, second +s+.
1585
# The 24-hour clock is used. Negative values of +h+, +min+, and
1586
# +sec+ are treating as counting backwards from the end of the
1587
# next larger unit (e.g. a +min+ of -2 is treated as 58). No
1588
# wraparound is performed. If an invalid time portion is specified,
1589
# an ArgumentError is raised.
1591
# +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1592
# +sg+ specifies the Day of Calendar Reform.
1594
# +y+ defaults to -4712, and +d+ to 1; this is Julian Day Number
1595
# day 0. The time values default to 0.
1596
def self.ordinal(y=-4712, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1597
unless (jd = _valid_ordinal?(y, d, sg)) &&
1598
(fr = _valid_time?(h, min, s))
1599
raise ArgumentError, 'invalid date'
1602
of = Rational(zone_to_diff(of) || 0, 86400)
1604
new!(jd_to_ajd(jd, fr, of), of, sg)
1607
# Create a new DateTime object corresponding to the specified
1608
# Civil Date and hour +h+, minute +min+, second +s+.
1610
# The 24-hour clock is used. Negative values of +h+, +min+, and
1611
# +sec+ are treating as counting backwards from the end of the
1612
# next larger unit (e.g. a +min+ of -2 is treated as 58). No
1613
# wraparound is performed. If an invalid time portion is specified,
1614
# an ArgumentError is raised.
1616
# +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1617
# +sg+ specifies the Day of Calendar Reform.
1619
# +y+ defaults to -4712, +m+ to 1, and +d+ to 1; this is Julian Day
1620
# Number day 0. The time values default to 0.
1621
def self.civil(y=-4712, m=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1622
unless (jd = _valid_civil?(y, m, d, sg)) &&
1623
(fr = _valid_time?(h, min, s))
1624
raise ArgumentError, 'invalid date'
1627
of = Rational(zone_to_diff(of) || 0, 86400)
1629
new!(jd_to_ajd(jd, fr, of), of, sg)
1632
class << self; alias_method :new, :civil end
1634
# Create a new DateTime object corresponding to the specified
1635
# Commercial Date and hour +h+, minute +min+, second +s+.
1637
# The 24-hour clock is used. Negative values of +h+, +min+, and
1638
# +sec+ are treating as counting backwards from the end of the
1639
# next larger unit (e.g. a +min+ of -2 is treated as 58). No
1640
# wraparound is performed. If an invalid time portion is specified,
1641
# an ArgumentError is raised.
1643
# +of+ is the offset from UTC as a fraction of a day (defaults to 0).
1644
# +sg+ specifies the Day of Calendar Reform.
1646
# +y+ defaults to -4712, +w+ to 1, and +d+ to 1; this is
1647
# Julian Day Number day 0.
1648
# The time values default to 0.
1649
def self.commercial(y=-4712, w=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
1650
unless (jd = _valid_commercial?(y, w, d, sg)) &&
1651
(fr = _valid_time?(h, min, s))
1652
raise ArgumentError, 'invalid date'
1655
of = Rational(zone_to_diff(of) || 0, 86400)
1657
new!(jd_to_ajd(jd, fr, of), of, sg)
1660
def self.weeknum(y=-4712, w=0, d=1, f=0, h=0, min=0, s=0, of=0, sg=ITALY) # :nodoc:
1661
unless (jd = _valid_weeknum?(y, w, d, f, sg)) &&
1662
(fr = _valid_time?(h, min, s))
1663
raise ArgumentError, 'invalid date'
1666
of = Rational(zone_to_diff(of) || 0, 86400)
1668
new!(jd_to_ajd(jd, fr, of), of, sg)
1671
private_class_method :weeknum
1673
def self.nth_kday(y=-4712, m=1, n=1, k=1, h=0, min=0, s=0, of=0, sg=ITALY) # :nodoc:
1674
unless (jd = _valid_nth_kday?(y, m, n, k, sg)) &&
1675
(fr = _valid_time?(h, min, s))
1676
raise ArgumentError, 'invalid date'
1679
of = Rational(zone_to_diff(of) || 0, 86400)
1681
new!(jd_to_ajd(jd, fr, of), of, sg)
1684
private_class_method :nth_kday
1686
def self.new_by_frags(elem, sg) # :nodoc:
1687
elem = rewrite_frags(elem)
1688
elem = complete_frags(elem)
1689
unless (jd = valid_date_frags?(elem, sg)) &&
1690
(fr = valid_time_frags?(elem))
1691
raise ArgumentError, 'invalid date'
1693
fr += (elem[:sec_fraction] || 0) / 86400
1694
of = Rational(elem[:offset] || 0, 86400)
1695
new!(jd_to_ajd(jd, fr, of), of, sg)
1698
private_class_method :new_by_frags
1700
# Create a new DateTime object by parsing from a String
1701
# according to a specified format.
1703
# +str+ is a String holding a date-time representation.
1704
# +fmt+ is the format that the date-time is in. See
1705
# date/format.rb for details on supported formats.
1707
# The default +str+ is '-4712-01-01T00:00:00+00:00', and the default
1708
# +fmt+ is '%FT%T%z'. This gives midnight on Julian Day Number day 0.
1710
# +sg+ specifies the Day of Calendar Reform.
1712
# An ArgumentError will be raised if +str+ cannot be
1714
def self.strptime(str='-4712-01-01T00:00:00+00:00', fmt='%FT%T%z', sg=ITALY)
1715
elem = _strptime(str, fmt)
1716
new_by_frags(elem, sg)
1719
# Create a new DateTime object by parsing from a String,
1720
# without specifying the format.
1722
# +str+ is a String holding a date-time representation.
1723
# +comp+ specifies whether to interpret 2-digit years
1724
# as 19XX (>= 69) or 20XX (< 69); the default is not to.
1725
# The method will attempt to parse a date-time from the String
1726
# using various heuristics; see #_parse in date/format.rb
1727
# for more details. If parsing fails, an ArgumentError
1730
# The default +str+ is '-4712-01-01T00:00:00+00:00'; this is Julian
1733
# +sg+ specifies the Day of Calendar Reform.
1734
def self.parse(str='-4712-01-01T00:00:00+00:00', comp=true, sg=ITALY)
1735
elem = _parse(str, comp)
1736
new_by_frags(elem, sg)
1739
def self.iso8601(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1740
elem = _iso8601(str)
1741
new_by_frags(elem, sg)
1744
def self.rfc3339(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1745
elem = _rfc3339(str)
1746
new_by_frags(elem, sg)
1749
def self.xmlschema(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1750
elem = _xmlschema(str)
1751
new_by_frags(elem, sg)
1754
def self.rfc2822(str='Mon, 1 Jan -4712 00:00:00 +0000', sg=ITALY) # :nodoc:
1755
elem = _rfc2822(str)
1756
new_by_frags(elem, sg)
1759
class << self; alias_method :rfc822, :rfc2822 end
1761
def self.httpdate(str='Mon, 01 Jan -4712 00:00:00 GMT', sg=ITALY) # :nodoc:
1762
elem = _httpdate(str)
1763
new_by_frags(elem, sg)
1766
def self.jisx0301(str='-4712-01-01T00:00:00+00:00', sg=ITALY) # :nodoc:
1767
elem = _jisx0301(str)
1768
new_by_frags(elem, sg)
1771
public :hour, :min, :sec, :sec_fraction, :zone, :offset, :new_offset,
1772
:minute, :second, :second_fraction
1775
format('%.4d-%02d-%02dT%02d:%02d:%02d%s',
1776
year, mon, mday, hour, min, sec, zone)
1783
def to_time() getlocal end
1786
jd = Date.__send__(:civil_to_jd, year, mon, mday, Date::ITALY)
1787
Date.new!(Date.__send__(:jd_to_ajd, jd, 0, 0), 0, Date::ITALY)
1791
jd = DateTime.__send__(:civil_to_jd, year, mon, mday, DateTime::ITALY)
1792
fr = DateTime.__send__(:time_to_day_fraction, hour, min, [sec, 59].min) +
1793
Rational(subsec, 86400)
1794
of = Rational(utc_offset, 86400)
1795
DateTime.new!(DateTime.__send__(:jd_to_ajd, jd, fr, of),
1796
of, DateTime::ITALY)
1803
def to_time() Time.local(year, mon, mday) end
1804
def to_date() self end
1805
def to_datetime() DateTime.new!(jd_to_ajd(jd, 0, 0), @of, @sg) end
1807
# Create a new Date object representing today.
1809
# +sg+ specifies the Day of Calendar Reform.
1810
def self.today(sg=ITALY)
1812
jd = civil_to_jd(t.year, t.mon, t.mday, sg)
1813
new!(jd_to_ajd(jd, 0, 0), 0, sg)
1816
# Create a new DateTime object representing the current time.
1818
# +sg+ specifies the Day of Calendar Reform.
1819
def self.now(sg=ITALY)
1821
jd = civil_to_jd(t.year, t.mon, t.mday, sg)
1822
fr = time_to_day_fraction(t.hour, t.min, [t.sec, 59].min) +
1823
Rational(t.subsec, 86400)
1824
of = Rational(t.utc_offset, 86400)
1825
new!(jd_to_ajd(jd, fr, of), of, sg)
1828
private_class_method :now
1832
class DateTime < Date
1837
Time.utc(year, mon, mday, hour, min, sec +
1843
def to_date() Date.new!(jd_to_ajd(jd, 0, 0), 0, @sg) end
1844
def to_datetime() self end
1846
private_class_method :today
1847
public_class_method :now