class IncompleteDate

Constants

VALID_DATE_PARTS
VERSION

Attributes

circa[RW]
day[R]
mday[R]
month[R]
year[R]

Public Class Methods

leap_year?(year) click to toggle source

Returns true if the given year is a leap year, false otherwise.

# File lib/incomplete_date.rb, line 44
def self.leap_year?(year)
  raise ArgumentError, "year cannot be null or zero" if year.nil? or year.zero?
  (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
end
max_month_days(month = nil, year = nil) click to toggle source
# File lib/incomplete_date.rb, line 49
def self.max_month_days(month = nil, year = nil)
  year ||= 2000 # Assume a leap year if year is not known
  case month
  when nil,1,3,5,7,8,10,12 then 31
  when 4,6,9,11 then 30
  when 2 then leap_year?(year) ? 29 : 28
  else raise ArgumentError, "invalid month"
  end
end
new(value) click to toggle source
# File lib/incomplete_date.rb, line 10
def initialize(value)
  @circa = false

  case value
  when Integer
    @circa = (value < 0)
    num = value.abs
    num, @day = num.divmod(100)
    @year, @month = num.divmod(100)
  when Hash
    @day, @month, @year = value[:day], value[:month], value[:year]
    @circa = value.fetch(:circa, false)
  when Date
    @day, @month, @year = value.mday, value.month, value.year
  when IncompleteDate
    @day, @month, @year, @circa = value.day, value.month, value.year, value.circa
  when nil #invaluable for existing databases!
      @day, @month, @year, @circa = 0, 0, 0, false
  else
    raise ArgumentError, "Invalid #{self.class.name} specification"
  end

  @day = nil if @day == 0
  @month = nil if @month == 0
  @year = nil if @year == 0
end

Public Instance Methods

[](part_name) click to toggle source
# File lib/incomplete_date.rb, line 101
def [](part_name)
  raise ArgumentError, "Invalid date part #{part_name}" unless VALID_DATE_PARTS.include?(part_name)
  self.send(part_name)
end
[]=(part_name, value) click to toggle source
# File lib/incomplete_date.rb, line 106
def []=(part_name, value)
  raise ArgumentError, "Invalid date part #{part_name}" unless VALID_DATE_PARTS.include?(part_name)
  self.send("#{part_name}=", value)
end
circa?() click to toggle source
# File lib/incomplete_date.rb, line 95
def circa?
  @circa
end
complete?() click to toggle source
# File lib/incomplete_date.rb, line 119
def complete?
  !incomplete?
end
day=(value) click to toggle source
# File lib/incomplete_date.rb, line 77
def day=(value)
  value = (value == 0) ? nil : value
  raise ArgumentError, "invalid day" unless valid_day?(value)
  @day = value
end
defined_parts() click to toggle source
# File lib/incomplete_date.rb, line 152
def defined_parts
  VALID_DATE_PARTS.reject { |part| (part == :circa) || self[part].nil? }
end
defines_birthday?() click to toggle source
# File lib/incomplete_date.rb, line 148
def defines_birthday?
  has_day? && has_month?
end
empty?() click to toggle source
# File lib/incomplete_date.rb, line 127
def empty?
  @day.nil? && @month.nil? && @year.nil?
end
exact?() click to toggle source
# File lib/incomplete_date.rb, line 123
def exact?
  complete? && !circa?
end
first()
Alias for: lowest
has?(*parts) click to toggle source
# File lib/incomplete_date.rb, line 131
def has?(*parts)
  parts.collect!(&:to_sym)
  parts.inject(true) { |total,part| total && !self.send(part).nil? }
end
has_day?() click to toggle source
# File lib/incomplete_date.rb, line 136
def has_day?
  !@day.nil?
end
has_month?() click to toggle source
# File lib/incomplete_date.rb, line 140
def has_month?
  !@month.nil?
end
has_year?() click to toggle source
# File lib/incomplete_date.rb, line 144
def has_year?
  !@year.nil?
end
highest() click to toggle source

Returns the highest possible date that matches this incomplete date.

Only defined if this incomplete date has the year part defined. Otherwise it returns nil.

see lowest

# File lib/incomplete_date.rb, line 198
def highest
  return nil unless has_year?
  max_month = self.month || 12
  max_day = self.day || IncompleteDate.max_month_days(max_month, self.year)
  Date.civil(self.year, max_month, max_day)
end
Also aliased as: max, last
include?(date) click to toggle source

Returns true if the given date is included in the possible set of complete dates that match this incomplete date.

For instance 2003-xx-xx includes 2003-12-24 (or any date within the year 2003) but it does not include 1999-12-14, for instance.

The argument can be either a Date instance or an IncompleteDate instance.

# File lib/incomplete_date.rb, line 169
def include?(date)
  (self.year.nil? || (date.year == self.year)) &&
  (self.month.nil? || (date.month == self.month)) &&
  (self.day.nil? || (date.mday == self.mday))
end
incomplete?() click to toggle source
# File lib/incomplete_date.rb, line 115
def incomplete?
  @day.nil? || @month.nil? || @year.nil?
end
last()
Alias for: highest
lowest() click to toggle source

Returns the lowest possible date that matches this incomplete date.

Only defined if this incomplete date has the year part defined. Otherwise it returns nil.

see highest

# File lib/incomplete_date.rb, line 183
def lowest
  return nil unless has_year?
  Date.civil(self.year, self.month || 1, self.day || 1)
end
Also aliased as: min, first
max()
Alias for: highest
min()
Alias for: lowest
month=(value) click to toggle source
# File lib/incomplete_date.rb, line 83
def month=(value)
  value = (value == 0) ? nil : value
  raise ArgumentError, "invalid month" unless valid_month?(value)
  @month = value
end
to_birthday() click to toggle source

Returns an incomplete date which only has the birthday parts (month and day) taken from this incomplete date. If the needed date parts are not defined, it returns nil.

# File lib/incomplete_date.rb, line 216
def to_birthday
  return nil unless defines_birthday?
  IncompleteDate.new(:month => month, :day => day)
end
to_date(opts = {}) click to toggle source

Converts this incomplete date to a standard date value. Any missing date parts can be provided by the hash argument, or else are taken from today's date, or a reference date that be given as an optional argument with key :ref. In the case of the day missing and not explicitely provided, it defaults to 1, and not to the reference date.

# File lib/incomplete_date.rb, line 228
def to_date(opts = {})
  ref = opts.fetch(:ref, Date.today)
  Date.civil(
    (self.year || opts[:year] || ref.year).to_i,
    (self.month || opts[:month] || ref.month).to_i,
    (self.day || opts[:day] || 1).to_i)
end
to_hash() click to toggle source
# File lib/incomplete_date.rb, line 290
def to_hash
  result = {}
  VALID_DATE_PARTS.each { |part| result[part] = self.send(part) }
  result
end
to_i() click to toggle source

Converts this incomplete date to its equivalent integer representation suitable for the database layer.

The two less significant digits in the decimal representation of the number are the day of the month. The next two less significant digits represent the month (numbered from 01 to 12) and the rest of the digits represent the year. In any case a value of zero means that that particular part of the date is not known. Finally, the sign of the number represents wether the date is considered to be uncertain (negative) or certain (positive).

This representation relies on the fact there was no year 0 in the Gregorian or Julian calendars (see en.wikipedia.org/wiki/Year_zero).

The integer value zero represents a date that is completely unknown, so it is somehow equivalent to nil in terms of meaning, and it is questionable that we allow instances of this class with such a configuration.

# File lib/incomplete_date.rb, line 258
def to_i
  y,m,d = [year,month,day].collect(&:to_i) # converts +nil+ values to zero
  (circa ? -1 : 1) * (d + m*100 + y*10000)
end
to_incomplete_date() click to toggle source
# File lib/incomplete_date.rb, line 236
def to_incomplete_date
  self
end
to_range() click to toggle source

Returns the range of dates that are included or match with this incomplete date.

Uses lowest and highest to determine the extremes of the range. Returns nil if the year is not defined.

# File lib/incomplete_date.rb, line 286
def to_range
  has_year? ? (min..max) : nil
end
to_s() click to toggle source
# File lib/incomplete_date.rb, line 263
def to_s
  dt = to_date
  ord = has_day? ? dt.day.ordinalize : nil
  result = case defined_parts
    when [:year, :month, :day] then dt.strftime("%B #{ord}, %Y")
    when [:year, :month] then dt.strftime("%B %Y")
    when [:month, :day] then dt.strftime("%B #{ord}")
    when [:year, :day] then dt.strftime("#{ord} of the month, %Y")
    when [:year] then dt.strftime("%Y")
    when [:month] then dt.strftime("%B")
    when [:day] then "#{ord} of the month"
    else return "unknown"
  end
  circa ? "c. #{result}" : result
end
valid_day?(day, month = nil, year = nil) click to toggle source
# File lib/incomplete_date.rb, line 59
def valid_day?(day, month = nil, year = nil)
  return true if day.nil? || day.zero?
  max = IncompleteDate.max_month_days(month || self.month, year || self.year)
  (1..max).include?(day)
end
valid_month?(month) click to toggle source
# File lib/incomplete_date.rb, line 65
def valid_month?(month)
  return true if month.nil? || month.zero?
  (1..12).include?(month) && valid_day?(self.day, month)
end
valid_year?(year, month = nil, day = nil) click to toggle source
# File lib/incomplete_date.rb, line 70
def valid_year?(year, month = nil, day = nil)
  return true if year.nil? || year.zero?
  day ||= self.day
  month ||= self.month
  !(!IncompleteDate.leap_year?(year) && (day == 29) && (month == 2))
end
year=(value) click to toggle source
# File lib/incomplete_date.rb, line 89
def year=(value)
  value = (value == 0) ? nil : value
  raise ArgumentError, "invalid year" unless valid_year?(value)
  @year = value
end