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