Class: IncompleteDate

Inherits:
Object
  • Object
show all
Defined in:
lib/incomplete_date.rb,
lib/incomplete_date/version.rb,
lib/incomplete_date/active_record.rb

Defined Under Namespace

Modules: IncompleteDateAttr

Constant Summary collapse

VALID_DATE_PARTS =
[:year, :month, :day, :circa]
VERSION =
'0.1.2'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value) ⇒ IncompleteDate

Returns a new instance of IncompleteDate.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 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

Instance Attribute Details

#circaObject

Returns the value of attribute circa.



7
8
9
# File 'lib/incomplete_date.rb', line 7

def circa
  @circa
end

#dayObject Also known as: mday

Returns the value of attribute day.



6
7
8
# File 'lib/incomplete_date.rb', line 6

def day
  @day
end

#monthObject

Returns the value of attribute month.



6
7
8
# File 'lib/incomplete_date.rb', line 6

def month
  @month
end

#yearObject

Returns the value of attribute year.



6
7
8
# File 'lib/incomplete_date.rb', line 6

def year
  @year
end

Class Method Details

.leap_year?(year) ⇒ Boolean

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

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


44
45
46
47
# 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) ⇒ Object



49
50
51
52
53
54
55
56
57
# 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

Instance Method Details

#[](part_name) ⇒ Object

Raises:

  • (ArgumentError)


101
102
103
104
# 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) ⇒ Object

Raises:

  • (ArgumentError)


106
107
108
109
# 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?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/incomplete_date.rb', line 95

def circa?
  @circa
end

#complete?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/incomplete_date.rb', line 119

def complete?
  !incomplete?
end

#defined_partsObject



152
153
154
# File 'lib/incomplete_date.rb', line 152

def defined_parts
  VALID_DATE_PARTS.reject { |part| (part == :circa) || self[part].nil? }
end

#defines_birthday?Boolean

Returns:

  • (Boolean)


148
149
150
# File 'lib/incomplete_date.rb', line 148

def defines_birthday?
  has_day? && has_month?
end

#empty?Boolean

Returns:

  • (Boolean)


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

def empty?
  @day.nil? && @month.nil? && @year.nil?
end

#exact?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/incomplete_date.rb', line 123

def exact?
  complete? && !circa?
end

#has?(*parts) ⇒ Boolean

Returns:

  • (Boolean)


131
132
133
134
# 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?Boolean

Returns:

  • (Boolean)


136
137
138
# File 'lib/incomplete_date.rb', line 136

def has_day?
  !@day.nil?
end

#has_month?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/incomplete_date.rb', line 140

def has_month?
  !@month.nil?
end

#has_year?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'lib/incomplete_date.rb', line 144

def has_year?
  !@year.nil?
end

#highestObject Also known as: max, last

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



198
199
200
201
202
203
# 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

#include?(date) ⇒ Boolean

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.

Returns:

  • (Boolean)


169
170
171
172
173
# 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?Boolean

– Testing completeness ++

Returns:

  • (Boolean)


115
116
117
# File 'lib/incomplete_date.rb', line 115

def incomplete?
  @day.nil? || @month.nil? || @year.nil?
end

#lowestObject Also known as: min, first

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



183
184
185
186
# 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

#to_birthdayObject

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.



216
217
218
219
# 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 = {}) ⇒ Object

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.



228
229
230
231
232
233
234
# 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_hashObject



290
291
292
293
294
# File 'lib/incomplete_date.rb', line 290

def to_hash
  result = {}
  VALID_DATE_PARTS.each { |part| result[part] = self.send(part) }
  result
end

#to_iObject

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.



258
259
260
261
# 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_dateObject



236
237
238
# File 'lib/incomplete_date.rb', line 236

def to_incomplete_date
  self
end

#to_rangeObject

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.



286
287
288
# File 'lib/incomplete_date.rb', line 286

def to_range
  has_year? ? (min..max) : nil
end

#to_sObject



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# 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) ⇒ Boolean

Returns:

  • (Boolean)


59
60
61
62
63
# 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) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
68
# 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) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
73
74
75
# 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