class PartialDate::Date

Public: A class for handling partial dates. Year (including negative values), month and day are optional although month must be set before day. Partial dates are stored internally in a single integer bit store using bitmask and bitwise operations to set and get year, month, and day values.

A numerical (human readable) date value can be retrieved via the d.value accessor.

Use d.value to get and set the numerical date value as well as for storing dates as a single value in a persistence store.

Examples

date = PartialDate::Date.new
date.value = 20121201
# => 2012-12-01

date = PartialDate::Date.load 20121201
# => 2012-12-01

date = PartialDate::Date.new
date.year = 2012
date.month = 12
date.day = 1

date = PartialDate::Date.new {|d| d.year = 2012; d.month = 12; d.day = 1}

date = PartialDate::Date.new {|d| d.year = 2012 }

Attributes

bits[R]

Public: Readonly accessor for the raw bit integer date value.

Returns the single Integer backing store with ‘bits’ flipped for year, month and day.

Public Class Methods

get_date(register) click to toggle source
# File lib/partial-date/date.rb, line 364
def self.get_date(register)
  date = (get_year(register) * 10000).abs + (get_month(register) * 100) + get_day(register) 
  if get_sign(register) == 1
    date * -1
  else
    date
  end
end
get_day(register) click to toggle source
# File lib/partial-date/date.rb, line 410
def self.get_day(register)
  register & DAY_MASK
end
get_month(register) click to toggle source
# File lib/partial-date/date.rb, line 402
def self.get_month(register)
  (register & MONTH_MASK) >> MONTH_SHIFT
end
get_sign(register) click to toggle source
# File lib/partial-date/date.rb, line 380
def self.get_sign(register)
  (register & SIGN_MASK) >> SIGN_SHIFT
end
get_year(register) click to toggle source
# File lib/partial-date/date.rb, line 388
def self.get_year(register)
  year = (register & YEAR_MASK) >> YEAR_SHIFT
  if get_sign(register) == 1 
    year * -1
  else
    year
  end
end
load(value) click to toggle source

Public: Loads an 8 digit date value into a date object. Can be used when rehydrating a date object from a persisted partial date value.

value - an 8 digit value in partial date format.

Examples

date = PartialDate::Date.load 201212201
date.value
# => 20121200
date.year
# => 2012
date.month
# => 12
date.day
# => 0

Returns date object

# File lib/partial-date/date.rb, line 141
def self.load(value)
  PartialDate::Date.new {|d| d.value = value}
end
new() { |self| ... } click to toggle source

Public: Create a new partial date class from a block of integers or strings.

Examples

# From integers
date = PartialDate::Date.new {|d| d.year = 2012; d.month = 12; d.day = 1}
date.value
# => 20121201

# From strings
date = PartialDate::Date.new {|d| d.year = "2012"; d.month = "12"; d.day = "1"}
date.value
# => 20121201

Returns a date object.

# File lib/partial-date/date.rb, line 118
def initialize
  @bits = 0
  yield self if block_given?
end
set_date(register, value) click to toggle source
# File lib/partial-date/date.rb, line 373
def self.set_date(register, value)
  register = (value < 0) ? set_sign(register, 1) : set_sign(register, 0)
  register = set_year(register, (value.abs / 10000).abs)
  register = set_month(register, ((value - (value / 10000).abs * 10000) / 100).abs)
  register = set_day(register, value - (value / 100).abs * 100)
end
set_day(register, value) click to toggle source
# File lib/partial-date/date.rb, line 414
def self.set_day(register, value)
  register = (register & ZERO_DAY_MASK) | value
end
set_month(register, value) click to toggle source
# File lib/partial-date/date.rb, line 406
def self.set_month(register, value)
  register = (register & ZERO_MONTH_MASK) | (value << MONTH_SHIFT)
end
set_sign(register, value) click to toggle source
# File lib/partial-date/date.rb, line 384
def self.set_sign(register, value)
  register = (register & ZERO_SIGN_MASK) | (value << SIGN_SHIFT)
end
set_year(register, value) click to toggle source
# File lib/partial-date/date.rb, line 397
def self.set_year(register, value)
  register = (value < 0) ? set_sign(register, 1) : set_sign(register, 0)
  register = (register & ZERO_YEAR_MASK) | (value.abs << YEAR_SHIFT)
end

Public Instance Methods

<=>(other_date) click to toggle source

Public: Spaceship operator for date comparisons. Comparisons are made by cascading down from year, to month to day. This should be faster than passing to self.value <=> other_date.value since the integer value attribute requires multiplication to calculate.

Returns -1, 1, or 0

# File lib/partial-date/date.rb, line 339
def <=>(other_date)
  if self.year < other_date.year
    return -1
  elsif self.year > other_date.year
    return 1
  else
    if self.month < other_date.month
      return -1
    elsif
      self.month > other_date.month
      return 1
    else
      if self.day < other_date.day
        return -1
      elsif
        self.day > other_date.day
        return 1
      else
        return 0
      end
    end
  end
end
day() click to toggle source

Public: Get the day from a partial date.

# File lib/partial-date/date.rb, line 267
def day
  self.class.get_day(@bits)
end
day=(value) click to toggle source

Public: Set the day portion of a partial date. Day is optional so zero, nil and empty strings are allowed.

# File lib/partial-date/date.rb, line 240
def day=(value)
  value = 0 if value.nil?

  if value.is_a?(String) 
    if value.length == 0
      value = 0
    elsif value =~ /\A\d{1,2}\z/
      value = value.to_i
    else
      raise DayError, "Day must be a valid one or two digit string or integer between 0 and 31"
    end
  end

  if (value >= 0 && value <= 31)
    raise DayError, "A month must be set before a day" if month == 0 && value !=0
    begin
      date = ::Date.civil(self.year, self.month, value) if value > 0
      @bits = self.class.set_day(@bits, value)
    rescue 
      raise DayError, "Day must be a valid day for the given month"
    end
  else
    raise DayError, "Day must be an integer between 0 and 31"
  end
end
month() click to toggle source

Public: Get the month from a partial date.

# File lib/partial-date/date.rb, line 233
def month
  self.class.get_month(@bits)
end
month=(value) click to toggle source

Public: Set the month of a partial date.

# File lib/partial-date/date.rb, line 211
def month=(value)
  value = 0 if value.nil?

  if value.is_a?(String) 
    if value.length == 0
      value = 0
    elsif value =~ /\A\d{1,2}\z/ 
      value = value.to_i
    else
      raise MonthError, "Month must be a valid one or two digit string or integer between 0 and 12"
    end
  end

  if (value >= 0 && value <= 12)
    @bits = self.class.set_month(@bits, value)
    @bits = self.class.set_day(@bits, 0) if value == 0
  else
    raise MonthError, "Month must an be integer between 0 and 12"
  end
end
old_to_s(format = :default) click to toggle source

Here for the moment for benchmark comparisons

# File lib/partial-date/date.rb, line 316
def old_to_s(format = :default)
  format = FORMATS[format] if format.is_a?(Symbol)

  result = format.dup
  FORMAT_METHODS.each_pair do |key, value|
    result.gsub!( key, value.call( self )) if result.include? key
  end

  # Remove any leading "/-," chars.
  # Remove double white spaces.
  # Remove any duplicate "/-," chars and replace with the single char.
  # Remove any trailing "/-," chars.
  # Anything else - you're on your own ;-)
  lead_trim = (year != 0 && format.lstrip.start_with?("%Y")) ? /\A[\/\,\s]+/ : /\A[\/\,\-\s]+/ 
    result = result.gsub(lead_trim, '').gsub(/\s\s/, ' ').gsub(/[\/\-\,]([\/\-\,])/, '\1').gsub(/[\/\,\-\s]+\z/, '')
end
to_s(format = :default) click to toggle source

Public: Returns a formatted string representation of date. A subset of date formatters have been implemented including: %Y - Year with century (can be negative, and will be padded to 4 digits at least)

-0001, 0000, 1995, 2009, 14292, etc.

%m - Month of the year, zero-padded (01..12) %B - The full month name (‘January’) %b - The abbreviated month name (‘Jan’) %d - Day of the month, zero-padded (01..31) %e - Day of the month, blank-padded ( 1..31)

Examples

date = PartialDate::Date.new {|d| d.year = 2012, d.month = 12, d.day = 31}
date.to_s
# => "2012-12-31"

Returns string representation of date.

# File lib/partial-date/date.rb, line 289
def to_s(format = :default)
  format = FORMATS[format] if format.is_a?(Symbol)
  s = format.dup
  n = b = 0
  a = 1
  while n < s.length
    if s[n] == "%" && FORMAT_METHODS.include?(s[n..n+1])
      t = FORMAT_METHODS[s[n..n+1]].call( self ) 
      if t.length == 0  
        if n >= 0 && n < s.length - 2
          a = a + 1 if s[n+2] =~ REMOVALS
        else
          b = n - 1 if s[n-1] =~ REMOVALS
        end
      end
      s.slice!(b..n+a)
      s.insert(b, t)
      n = b = b + t.length 
      a = 1 
    else
      n = b += 1
    end
  end
  s
end
value() click to toggle source

Public: Get the integer date value in partial date format.

Examples

date.year = "2012"
date.value
# => 20120000

Returns an integer representation of a partial date.

# File lib/partial-date/date.rb, line 155
def value
  self.class.get_date(@bits)
end
value=(value) click to toggle source

Public: Set a date value using an interger in partial date format.

Examples

date.value = 20121200

Returns nothing

# File lib/partial-date/date.rb, line 166
def value=(value)
  if value.is_a?(Integer) && (value >= -10485761231 && value <= 10485761231)
    @bits = self.class.set_date(@bits, value)
  else
     raise PartialDateError, "Date value must be an integer betwen -10485761231 and 10485761231"
  end
end
year() click to toggle source

Public: Get the year from a partial date.

# File lib/partial-date/date.rb, line 206
def year
  self.class.get_year(@bits)
end
year=(value) click to toggle source

Public: Sets the year portion of a partial date.

value - The string or integer value for a year.

Examples

date.year = "2000"
date.value
# => 20000000

Returns nothing

# File lib/partial-date/date.rb, line 185
def year=(value)
  value = 0 if value.nil?

  if value.is_a?(String) 
    if value.length == 0
      value = 0
    elsif value =~ /\A\-?\d{1,7}\z/
      value = value.to_i
    else
      raise YearError, "Year must be a valid string or integer from -1048576 to 1048576"
    end
  end

  if (value >= -1048576 && value <= 1048576) 
    @bits = self.class.set_year(@bits, value)
  else
    raise YearError, "Year must be an integer from -1048576 to 1048576"
  end
end