Class: PartialDate::Date

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

Overview

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 }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ Date

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.

Yields:

  • (_self)

Yield Parameters:



118
119
120
121
# File 'lib/partial-date/date.rb', line 118

def initialize
  @bits = 0
  yield self if block_given?
end

Instance Attribute Details

#bitsObject (readonly)

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

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



100
101
102
# File 'lib/partial-date/date.rb', line 100

def bits
  @bits
end

Class Method Details

.get_date(register) ⇒ Object



364
365
366
367
368
369
370
371
# 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) ⇒ Object



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

def self.get_day(register)
  register & DAY_MASK
end

.get_month(register) ⇒ Object



402
403
404
# File 'lib/partial-date/date.rb', line 402

def self.get_month(register)
  (register & MONTH_MASK) >> MONTH_SHIFT
end

.get_sign(register) ⇒ Object



380
381
382
# File 'lib/partial-date/date.rb', line 380

def self.get_sign(register)
  (register & SIGN_MASK) >> SIGN_SHIFT
end

.get_year(register) ⇒ Object



388
389
390
391
392
393
394
395
# 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) ⇒ Object

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



141
142
143
# File 'lib/partial-date/date.rb', line 141

def self.load(value)
  PartialDate::Date.new {|d| d.value = value}
end

.set_date(register, value) ⇒ Object



373
374
375
376
377
378
# 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) ⇒ Object



414
415
416
# 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) ⇒ Object



406
407
408
# 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) ⇒ Object



384
385
386
# 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) ⇒ Object



397
398
399
400
# 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

Instance Method Details

#<=>(other_date) ⇒ Object

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



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# 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

#dayObject

Public: Get the day from a partial date.



267
268
269
# File 'lib/partial-date/date.rb', line 267

def day
  self.class.get_day(@bits)
end

#day=(value) ⇒ Object

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



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# 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

#monthObject

Public: Get the month from a partial date.



233
234
235
# File 'lib/partial-date/date.rb', line 233

def month
  self.class.get_month(@bits)
end

#month=(value) ⇒ Object

Public: Set the month of a partial date.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 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) ⇒ Object

Here for the moment for benchmark comparisons



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# 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) ⇒ Object

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.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# 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

#valueObject

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.



155
156
157
# File 'lib/partial-date/date.rb', line 155

def value
  self.class.get_date(@bits)
end

#value=(value) ⇒ Object

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

Examples

date.value = 20121200

Returns nothing



166
167
168
169
170
171
172
# 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

#yearObject

Public: Get the year from a partial date.



206
207
208
# File 'lib/partial-date/date.rb', line 206

def year
  self.class.get_year(@bits)
end

#year=(value) ⇒ Object

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



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# 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