Class: ThirdBase::Date

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/third_base/date.rb

Overview

ThirdBase’s date class, a simple class which, unlike the standard Date class, does not include any time information.

This class is significantly faster than the standard Date class for two reasons. First, it does not depend on the Rational class (which is slow). Second, it doesn’t convert all dates to julian dates unless it is necessary.

Direct Known Subclasses

DateTime

Constant Summary collapse

MONTHNAMES =
[nil] + %w(January February March April May June July August September October November December)
ABBR_MONTHNAMES =
[nil] + %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
MONTH_NUM_MAP =
{}
DAYNAMES =
%w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
ABBR_DAYNAMES =
%w(Sun Mon Tue Wed Thu Fri Sat)
DAY_NUM_MAP =
{}
CUMMULATIVE_MONTH_DAYS =
{1=>0, 2=>31, 3=>59, 4=>90, 5=>120, 6=>151, 7=>181, 8=>212, 9=>243, 10=>273, 11=>304, 12=>334}
LEAP_CUMMULATIVE_MONTH_DAYS =
{1=>0, 2=>31, 3=>60, 4=>91, 5=>121, 6=>152, 7=>182, 8=>213, 9=>244, 10=>274, 11=>305, 12=>335}
DAYS_IN_MONTH =
{1=>31, 2=>28, 3=>31, 4=>30, 5=>31, 6=>30, 7=>31, 8=>31, 9=>30, 10=>31, 11=>30, 12=>31}
LEAP_DAYS_IN_MONTH =
{1=>31, 2=>29, 3=>31, 4=>30, 5=>31, 6=>30, 7=>31, 8=>31, 9=>30, 10=>31, 11=>30, 12=>31}
MONTHNAME_RE_PATTERN =
"(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december)"
FULL_MONTHNAME_RE_PATTERN =
"(january|february|march|april|may|june|july|august|september|october|november|december)"
ABBR_MONTHNAME_RE_PATTERN =
"(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)"
FULL_DAYNAME_RE_PATTERN =
"(sunday|monday|tuesday|wednesday|thursday|friday|saturday)"
ABBR_DAYNAME_RE_PATTERN =
"(sun|mon|tue|wed|thu|fri|sat)"
PARSER_LIST =
[]
DEFAULT_PARSER_LIST =
[:iso, :us, :num]
PARSERS =
{}
DEFAULT_PARSERS =
{}
STRFTIME_RE =
/%./o
STRPTIME_PROC_A =
proc{|h,x| h[:cwday] = DAY_NUM_MAP[x.downcase]}
STRPTIME_PROC_B =
proc{|h,x| h[:month] = MONTH_NUM_MAP[x.downcase]}
STRPTIME_PROC_C =
proc{|h,x| h[:year] ||= x.to_i*100}
STRPTIME_PROC_d =
proc{|h,x| h[:day] = x.to_i}
STRPTIME_PROC_G =
proc{|h,x| h[:cwyear] = x.to_i}
STRPTIME_PROC_g =
proc{|h,x| h[:cwyear] = two_digit_year(x)}
STRPTIME_PROC_j =
proc{|h,x| h[:yday] = x.to_i}
STRPTIME_PROC_m =
proc{|h,x| h[:month] = x.to_i}
STRPTIME_PROC_u =
proc{|h,x| h[:cwday] = x.to_i}
STRPTIME_PROC_V =
proc{|h,x| h[:cweek] = x.to_i}
STRPTIME_PROC_y =
proc{|h,x| h[:year] = two_digit_year(x)}
STRPTIME_PROC_Y =
proc{|h,x| h[:year] = x.to_i}
UNIXEPOCH =
2440588

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Date

Called by Date.new!, Takes a hash with one of the following keys:

  • :civil : should be an array with 3 elements, a year, month, and day

  • :commercial : should be an array with 3 elements, a commercial week year, commercial week, and commercial week day

  • :jd : should be an integer specifying the julian date

  • :ordinal : should be an array with 2 elements, a year and day of year.

An ArgumentError is raised if the date is invalid. All Date objects are immutable once created.



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/third_base/date.rb', line 309

def initialize(opts)
  if opts[:civil]
    @year, @mon, @day = opts[:civil]
    raise(ArgumentError, "invalid date") unless @year.is_a?(Integer) && @mon.is_a?(Integer) && @day.is_a?(Integer) && valid_civil?
  elsif opts[:ordinal]
    @year, @yday = opts[:ordinal]
    raise(ArgumentError, "invalid date") unless @year.is_a?(Integer) && @yday.is_a?(Integer) && valid_ordinal?
  elsif opts[:jd]
    @jd = opts[:jd]
    raise(ArgumentError, "invalid date") unless @jd.is_a?(Integer)
  elsif opts[:commercial]
    @cwyear, @cweek, @cwday = opts[:commercial]
    raise(ArgumentError, "invalid date") unless @cwyear.is_a?(Integer) && @cweek.is_a?(Integer) && @cwday.is_a?(Integer) && valid_commercial?
  else
    raise(ArgumentError, "invalid date format")
  end
end

Class Method Details

.add_parser(type, re, &block) ⇒ Object

Add a parser to the parser type. re should be a Regexp, and a block must be provided. The block should take a single MatchData argument, a return either nil specifying it could not parse the string, or a hash of values to be passed to new!.



99
100
101
# File 'lib/third_base/date.rb', line 99

def self.add_parser(type, re, &block)
  parser_hash[type].unshift([re, block])
end

.add_parser_type(type) ⇒ Object

Add a parser type to the list of parser types. Should be used if you want to add your own parser types.



106
107
108
# File 'lib/third_base/date.rb', line 106

def self.add_parser_type(type)
  parser_hash[type] ||= []
end

.civil(year, mon, day) ⇒ Object

Returns a new Date with the given year, month, and day.



111
112
113
# File 'lib/third_base/date.rb', line 111

def self.civil(year, mon, day)
  new!(:civil=>[year, mon, day])
end

.commercial(cwyear, cweek, cwday = 5) ⇒ Object

Returns a new Date with the given commercial week year, commercial week, and commercial week day.



117
118
119
# File 'lib/third_base/date.rb', line 117

def self.commercial(cwyear, cweek, cwday=5)
  new!(:commercial=>[cwyear, cweek, cwday])
end

.jd(j) ⇒ Object

Returns a new Date with the given julian date.



122
123
124
# File 'lib/third_base/date.rb', line 122

def self.jd(j)
  new!(:jd=>j)
end

.new(*args) ⇒ Object

Calls civil with the given arguments.



127
128
129
# File 'lib/third_base/date.rb', line 127

def self.new(*args)
  civil(*args)
end

.new!Object



91
# File 'lib/third_base/date.rb', line 91

alias new! new

.ordinal(year, yday) ⇒ Object

Returns a new Date with the given year and day of year.



132
133
134
# File 'lib/third_base/date.rb', line 132

def self.ordinal(year, yday)
  new!(:ordinal=>[year, yday])
end

.parse(str, opts = {}) ⇒ Object

Parses the given string and returns a Date. Raises an ArgumentError if no parser can correctly parse the date. Takes the following options:

  • :parser_types : an array of parser types to use, overriding the default or the ones specified by use_parsers.

Raises:

  • (ArgumentError)


142
143
144
145
146
147
148
149
150
151
152
# File 'lib/third_base/date.rb', line 142

def self.parse(str, opts={})
  s = str.strip
  parsers(opts[:parser_types]) do |pattern, block|
    if m = pattern.match(s)
      if res = block.call(m)
        return new!(res)
      end
    end
  end
  raise ArgumentError, 'invalid date'
end

.reset_parsers!Object

Reset the parsers, parser types, and order of parsers used to the default.



155
156
157
158
159
160
161
162
163
164
# File 'lib/third_base/date.rb', line 155

def self.reset_parsers!
  parser_hash.clear
  default_parser_hash.each do |type, parsers|
    add_parser_type(type)
    parsers.reverse.each do |re, parser|
      add_parser(type, re, &parser)
    end
  end
  use_parsers(*default_parser_list)
end

.strptime(str, fmt = strptime_default) ⇒ Object

Parse the string using the provided format (or the default format). Raises an ArgumentError if the format does not match the string.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/third_base/date.rb', line 168

def self.strptime(str, fmt=strptime_default)
  blocks = []
  s = str.strip
  date_hash = {}
  pattern = Regexp.escape(expand_strptime_format(fmt)).gsub(STRFTIME_RE) do |x|
    pat, *blks = _strptime_part(x[1..1])
    blocks += blks
    pat
  end
  if m = /#{pattern}/i.match(s)
    m.to_a[1..-1].zip(blocks) do |x, blk|
      blk.call(date_hash, x)
    end
    new_from_parts(date_hash)
  else
    raise ArgumentError, 'invalid date'
  end
end

.todayObject

Returns a date with the current year, month, and date.



188
189
190
191
# File 'lib/third_base/date.rb', line 188

def self.today
  t = Time.now
  civil(t.year, t.mon, t.day)
end

.use_parsers(*parsers) ⇒ Object

Set the order of parser types to use to the given parser types.



194
195
196
# File 'lib/third_base/date.rb', line 194

def self.use_parsers(*parsers)
  parser_list.replace(parsers)
end

Instance Method Details

#+(d) ⇒ Object

Returns a new date with d number of days added to this date.

Raises:

  • (TypeError)


328
329
330
331
# File 'lib/third_base/date.rb', line 328

def +(d)
  raise(TypeError, "d must be an integer") unless d.is_a?(Integer)
  jd_to_civil(jd + d)
end

#-(d) ⇒ Object

Returns a new date with d number of days subtracted from this date. If d is a Date, returns the number of days between the two dates.



335
336
337
338
339
340
341
342
343
# File 'lib/third_base/date.rb', line 335

def -(d)
  if d.is_a?(self.class)
    jd - d.jd
  elsif d.is_a?(Integer)
    self + -d
  else
    raise TypeError, "d should be #{self.class} or Integer"
  end
end

#<<(m) ⇒ Object

Returns a new date with m number of months subtracted from this date.



367
368
369
# File 'lib/third_base/date.rb', line 367

def <<(m)
  self >> -m
end

#<=>(date) ⇒ Object

Compare two dates. If the given date is greater than self, return -1, if it is less, return 1, and if it is equal, return 0. If given date is a number, compare this date’s julian date to it.



374
375
376
377
378
379
380
381
# File 'lib/third_base/date.rb', line 374

def <=>(date)
  if date.is_a?(Numeric)
    jd <=> date
  else
    ((d = (year <=> date.year)) == 0) && ((d = (mon <=> date.mon)) == 0) && ((d = (day <=> date.day)) == 0)
    d
  end
end

#==(date) ⇒ Object Also known as: eql?

Dates are equel only if their year, month, and day match.



384
385
386
387
# File 'lib/third_base/date.rb', line 384

def ==(date)
  return false unless Date === date
  year == date.year and mon == date.mon and day == date.day
end

#===(d) ⇒ Object

If d is a date, only true if it is equal to this date. If d is Numeric, only true if it equals this date’s julian date.



391
392
393
394
395
396
397
# File 'lib/third_base/date.rb', line 391

def ===(d)
  case d
  when Numeric then jd == d
  when Date then self == d
  else false
  end
end

#>>(m) ⇒ Object

Returns a new date with m number of months added to this date. If the day of self does not exist in the new month, set the new day to be the last day of the new month.

Raises:

  • (TypeError)


348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/third_base/date.rb', line 348

def >>(m)
  raise(TypeError, "m must be an integer") unless m.is_a?(Integer)
  y = year
  n = mon + m
  if n > 12 or n <= 0
    a, n = n.divmod(12)
    if n == 0
      n = 12
      y += a - 1
    else
      y += a
    end
  end
  ndays = days_in_month(n, y)
  d = day > ndays ? ndays : day
  new_civil(y, n, d)
end

#cwdayObject

The commercial week day for this date.



400
401
402
# File 'lib/third_base/date.rb', line 400

def cwday
  @cwday || commercial[2]
end

#cweekObject

The commercial week for this date.



405
406
407
# File 'lib/third_base/date.rb', line 405

def cweek
  @cweek || commercial[1]
end

#cwyearObject

The commercial week year for this date.



410
411
412
# File 'lib/third_base/date.rb', line 410

def cwyear
  @cwyear || commercial[0]
end

#dayObject Also known as: mday

The day of the month for this date.



415
416
417
# File 'lib/third_base/date.rb', line 415

def day
  @day || civil[2]
end

#downto(d, &block) ⇒ Object

Yield every date between this date and given date to the block. The given date should be less than this date.



422
423
424
# File 'lib/third_base/date.rb', line 422

def downto(d, &block)
  step(d, -1, &block)
end

#hashObject

Unique value for this date, based on it’s year, month, and day of month.



427
428
429
# File 'lib/third_base/date.rb', line 427

def hash
  civil.hash
end

#inspectObject

Programmer friendly readable string, much more friendly than the one in the standard date class.



433
434
435
# File 'lib/third_base/date.rb', line 433

def inspect
  "#<#{self.class} #{self}>"
end

#jdObject

This date’s julian date.



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/third_base/date.rb', line 438

def jd
  @jd ||= ( 
    y = year
    m = mon
    d = day
    if m <= 2
      y -= 1
      m += 12
    end
    a = (y / 100.0).floor
    jd = (365.25 * (y + 4716)).floor +
      (30.6001 * (m + 1)).floor +
      d - 1524 + (2 - a + (a / 4.0).floor)
  )
end

#leap?Boolean

Whether this date is in a leap year.

Returns:

  • (Boolean)


455
456
457
# File 'lib/third_base/date.rb', line 455

def leap?
  _leap?(year)
end

#monObject Also known as: month

The month number for this date (January is 1, December is 12).



460
461
462
# File 'lib/third_base/date.rb', line 460

def mon
  @mon || civil[1]
end

#step(limit, step = 1) ⇒ Object

Yield each date between this date and limit, adding step number of days in each iteration. Returns current date.



467
468
469
470
471
472
473
474
475
# File 'lib/third_base/date.rb', line 467

def step(limit, step=1)
  da = self
  op = %w(== <= >=)[step <=> 0]
  while da.__send__(op, limit)
    yield da
    da += step
  end
  self
end

#strftime(fmt = strftime_default) ⇒ Object

Format the time using a format string, or the default format string.



478
479
480
# File 'lib/third_base/date.rb', line 478

def strftime(fmt=strftime_default)
  fmt.gsub(STRFTIME_RE){|x| _strftime(x[1..1])}
end

#succObject Also known as: next

Return the day after this date.



483
484
485
# File 'lib/third_base/date.rb', line 483

def succ
  self + 1
end

#to_sObject

Alias for strftime with the default format



489
490
491
# File 'lib/third_base/date.rb', line 489

def to_s
  strftime
end

#upto(d, &block) ⇒ Object

Yield every date between this date and the given date to the block. The given date should be greater than this date.



495
496
497
# File 'lib/third_base/date.rb', line 495

def upto(d, &block)
  step(d, &block)
end

#wdayObject

Return the day of the week for this date. Sunday is 0, Saturday is 6.



500
501
502
# File 'lib/third_base/date.rb', line 500

def wday
  (jd + 1) % 7
end

#ydayObject

Return the day of the year for this date. January 1 is 1.



505
506
507
508
# File 'lib/third_base/date.rb', line 505

def yday
  h = leap? ? LEAP_CUMMULATIVE_MONTH_DAYS : CUMMULATIVE_MONTH_DAYS
  @yday ||= h[mon] + day
end

#yearObject

Return the year for this date.



511
512
513
# File 'lib/third_base/date.rb', line 511

def year
  @year || civil[0]
end