class Nickel::ZDate
TODO: get methods should accept dayname or dayindex
Constants
- FRI
- MON
- SAT
- SUN
- THU
- TUE
- WED
Attributes
Public Class Methods
# File lib/nickel/zdate.rb, line 289 def days_in_month(month, year) if year % 400 == 0 || year % 4 == 0 && year % 100 != 0 ZDate.days_in_leap_year_months[month - 1] else ZDate.days_in_common_year_months[month - 1] end end
Gets the difference FROM month1, year1 TO month2, year2 don't use it the other way around, it won't work
# File lib/nickel/zdate.rb, line 299 def diff_in_months(month1, year1, month2, year2) # first get the difference in months if month2 >= month1 diff_in_months = month2 - month1 else diff_in_months = 12 - (month1 - month2) year2 -= 1 # this makes the next line nice end diff_in_months + (year2 - year1) * 12 end
formats the year, month, day into the format expected by the ZDate
constructor
# File lib/nickel/zdate.rb, line 324 def format_date(year, month = 1, day = 1) format_year(year) + format_month(month) + format_day(day) end
# File lib/nickel/zdate.rb, line 319 def format_day(d) d.to_s.rjust(2, '0') end
# File lib/nickel/zdate.rb, line 315 def format_month(m) m.to_s.rjust(2, '0') end
# File lib/nickel/zdate.rb, line 310 def format_year(y) # if there were only two digits, prepend 20 (e.g. "08" should be "2008") y.to_s.rjust(4, '20') end
Interpret Date is equally as important, our goals: First off, convention of the NLP
is to not allow month names to the construct finder (unless it is implying date span), so we will not be interpreting anything such as january 2nd, 2008. Instead all dates will be represented in this form month/day/year. However it may not be as nice as that. We need to match things like '5', if someone just typed in “the 5th.” Because of this, there will be overlap between interpret_date and interpret_time in matching; interpret_date should ALWAYS be found after interpret_time in the construct finder. If the construct finder happens upon a digit on it's own, e.g. “5”, it will not run interpret_time because there is no “at” preceeding it. Therefore it will fall through to the finder with interpret_date and we will assume the user meant the 5th. If interpret_date is before interpret_time, then .… wait… does the order actually matter? Even if this is before interpret_time, it shouldn't get hit because the time should be picked up at the “at” construct. This may be a bunch of useless rambling.
2/08 <—— This is not A date 2/2008 <—— Neither is this, but I can see people using these as wrappers, must support this in next version 11/08 <—— same 11/2008 <—— same 2/1/08, 2/12/08, 2/1/2008, 2/12/2008 11/1/08, 11/12/08, 11/1/2008, 11/12/2008 2/1 feb first 2/12 feb twelfth 11/1 nov first 11/12 nov twelfth 11 the 11th 2 the 2nd
Match all of the following:
a.) 1 10 b.) 1/1 1/12 10/1 10/12 c.) 1/1/08 1/12/08 1/1/2008 1/12/2008 10/1/08 10/12/08 10/12/2008 10/12/2008 d.) 1st 10th
# File lib/nickel/zdate.rb, line 358 def interpret(str, current_date) day_str, month_str, year_str = nil, nil, nil ambiguous = { month: false, year: false } # assume false, we use this flag if we aren't certain about the year # appropriate matches a_d = /^(\d{1,2})(rd|st|nd|th)?$/ # handles cases a and d b = /^(\d{1,2})\/(\d{1,2})$/ # handles case b c = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/ # handles case c if mdata = str.match(a_d) ambiguous[:month] = true day_str = mdata[1] elsif mdata = str.match(b) ambiguous[:year] = true month_str = mdata[1] day_str = mdata[2] elsif mdata = str.match(c) month_str = mdata[1] day_str = mdata[2] year_str = mdata[3] else return nil end inst_str = ZDate.format_date(year_str || current_date.year_str, month_str || current_date.month_str, day_str || current_date.day_str) # in this case we do not care if date fails validation, if it does, it just means we haven't found a valid date, return nil date = ZDate.new(inst_str) rescue nil if date if ambiguous[:year] # say the date is 11/1 and someone enters 2/1, they probably mean next year, I pick 4 months as a threshold but that is totally arbitrary current_date.diff_in_months(date) < -4 && date = date.add_years(1) elsif ambiguous[:month] current_date.day > date.day && date = date.add_months(1) end end date end
Don't use attr_accessor for date, year, month, day; we want to validate on change.
# File lib/nickel/zdate.rb, line 28 def initialize(yyyymmdd = nil) d = yyyymmdd ? yyyymmdd.dup : ::Time.new.strftime('%Y%m%d') d.gsub!(/-/, '') # remove any hyphens, so a user can initialize with something like "2008-10-23" self.date = d end
# File lib/nickel/zdate.rb, line 280 def new_first_day_in_month(month, year) ZDate.new(ZDate.format_date(year, month)) end
# File lib/nickel/zdate.rb, line 284 def new_last_day_in_month(month, year) day = days_in_month(month, year) ZDate.new(ZDate.format_date(year, month, day)) end
Public Instance Methods
# File lib/nickel/zdate.rb, line 77 def <=>(other) return nil unless [:year, :month, :day].all? { |m| other.respond_to?(m) } if before?(other) -1 elsif after?(other) 1 else 0 end end
add_ methods return new ZDate
object, they DO NOT modify self
# File lib/nickel/zdate.rb, line 146 def add_days(number) if number < 0 return sub_days(number.abs) end o = dup # new ZDate object # Let's see what month we are going to end in while number > 0 if o.days_left_in_month >= number o.date = ZDate.format_date(o.year_str, o.month_str, o.day + number) number = 0 else number = number - 1 - o.days_left_in_month # it costs 1 day to increment the month o.increment_month! end end o end
# File lib/nickel/zdate.rb, line 168 def add_months(number) new_month = 1 + ((month - 1 + number) % 12) if number > months_left_in_year # are we going to change year? years_to_increment = 1 + ((number - months_left_in_year) / 12) # second term adds years if user entered a large number of months (e.g. date.add_months(50)) else years_to_increment = 0 end new_year = year + years_to_increment new_day = get_day_or_max_day_in_month(day, new_month, new_year) ZDate.new(ZDate.format_date(new_year, new_month, new_day)) end
# File lib/nickel/zdate.rb, line 164 def add_weeks(number) add_days(7 * number) end
# File lib/nickel/zdate.rb, line 180 def add_years(number) new_year = year + number new_day = get_day_or_max_day_in_month(day, month, new_year) ZDate.new(ZDate.format_date(new_year, month_str, new_day)) end
beginning and end of month both return new ZDate
objects
# File lib/nickel/zdate.rb, line 209 def beginning_of_month ZDate.new(ZDate.format_date(year_str, month_str)) end
# File lib/nickel/zdate.rb, line 217 def beginning_of_next_month o = dup o.increment_month! o end
# File lib/nickel/zdate.rb, line 34 def date @date end
# File lib/nickel/zdate.rb, line 38 def date=(yyyymmdd) @date = yyyymmdd validate end
# File lib/nickel/zdate.rb, line 63 def day day_str.to_i end
# File lib/nickel/zdate.rb, line 446 def day_of_year doy = day # iterate through days in months arrays, summing up the days if leap_year? doy = (1...month).to_a.reduce(doy) { |sum, n| sum + ZDate.days_in_leap_year_months[n - 1] } else doy = (1...month).to_a.reduce(doy) { |sum, n| sum + ZDate.days_in_common_year_months[n - 1] } end doy end
# File lib/nickel/zdate.rb, line 51 def day_str @date[6..7] end
# File lib/nickel/zdate.rb, line 430 def dayindex ZDate.days_of_week.index(dayname) end
# File lib/nickel/zdate.rb, line 423 def dayname # well this is going to be a hack, I need an algo for finding the day # Ruby's Time.local is the fastest way to create a Ruby Time object t = ::Time.local(year, ZDate.months_of_year[month - 1], day) t.strftime('%a').downcase end
# File lib/nickel/zdate.rb, line 407 def days_in_month if leap_year? ZDate.days_in_leap_year_months[month - 1] else ZDate.days_in_common_year_months[month - 1] end end
# File lib/nickel/zdate.rb, line 415 def days_left_in_month days_in_month - day end
# File lib/nickel/zdate.rb, line 457 def days_left_in_year leap_year? ? 366 - day_of_year : 365 - day_of_year end
Gets the absolute difference in days between self and date_to_compare, order is not important.
# File lib/nickel/zdate.rb, line 251 def diff_in_days(date_to_compare) # d1 will be the earlier date, d2 the later if date_to_compare > self d1, d2 = dup, date_to_compare.dup elsif self > date_to_compare d1, d2 = date_to_compare.dup, dup else return 0 # same date end total = 0 while d1.year != d2.year total += d1.days_left_in_year + 1 # need one extra day to push us to jan 1 d1 = ZDate.new(ZDate.format_date(d1.year + 1)) end total += d2.day_of_year - d1.day_of_year total end
# File lib/nickel/zdate.rb, line 270 def diff_in_days_to_this(closest_day_index) if closest_day_index >= dayindex closest_day_index - dayindex # could be 0 else # day_num < self.dayindex 7 - (dayindex - closest_day_index) end end
difference in months FROM self TO date2, for instance, if self is oct 1 and date2 is nov 14, will return 1 if self is nov 14 and date2 is oct 1, will return -1
# File lib/nickel/zdate.rb, line 399 def diff_in_months(date2) if date2 > self ZDate.diff_in_months(month, year, date2.month, date2.year) else ZDate.diff_in_months(date2.month, date2.year, month, year) * -1 end end
# File lib/nickel/zdate.rb, line 213 def end_of_month ZDate.new(ZDate.format_date(year_str, month_str, days_in_month)) end
# File lib/nickel/zdate.rb, line 71 def fmt(txt) txt.gsub!(/%Y/, year_str) txt.gsub!(/%m/, month_str) txt.gsub!(/%d/, day_str) end
# File lib/nickel/zdate.rb, line 434 def full_dayname ZDate.full_days_of_week[dayindex] end
# File lib/nickel/zdate.rb, line 438 def full_monthname Z.full_months_of_year[month - 1] end
# File lib/nickel/zdate.rb, line 461 def get_date_from_day_and_week_of_month(day_num, week_num) # This method is extremely sloppy, clean it up # Get the index of the first day of this month first_day_of_month = beginning_of_month first_day_index = first_day_of_month.dayindex diff_in_days_to_first_occ = first_day_of_month.diff_in_days_to_this(day_num) # now find the number of days to the correct occurrence; REMEMBER TO CHECK FOR LAST MONTH if week_num == -1 total_diff_in_days = diff_in_days_to_first_occ + 21 # 7 * 3 weeks; are already at the first ocurrence, so this is total diff in days to 4th occurrence; may not be the last!! else total_diff_in_days = diff_in_days_to_first_occ + 7 * (week_num - 1) end # there is a chance that the last occurrence is not the 4th week of the month; if that is the case, add an extra 7 days if (week_num == -1) && (month == beginning_of_month.add_days(total_diff_in_days + 7).month) total_diff_in_days += 7 end # Now we have the number of days FROM THE START OF THE CURRENT MONTH; if we are not past that date, then we have found the first occurrence if (total_diff_in_days + 1) >= day return beginning_of_month.add_days(total_diff_in_days) else # We have already past the date; calculate the occurrence next month! # Get the index of the first day next month first_day_index = add_months(1).beginning_of_month.dayindex # Find the number of days away to the day of interest (NOT the week) if day_num > first_day_index diff_in_days_to_first_occ = day_num - first_day_index elsif day_num < first_day_index diff_in_days_to_first_occ = 7 - (first_day_index - day_num) else # first_day_index == day_num diff_in_days_to_first_occ = 0 end # now find the number of days to the correct occurrence; REMEMBER TO CHECK FOR LAST MONTH if week_num == -1 total_diff_in_days = diff_in_days_to_first_occ + 21 # 7 * 3 weeks else total_diff_in_days = diff_in_days_to_first_occ + 7 * (week_num - 1) end # there is a chance that the last occurrence is not the 4th week of the month; if that is the case, add an extra 7 days if (week_num == -1) && (add_months(1).month == add_months(1).beginning_of_month.add_days(total_diff_in_days + 7).month) total_diff_in_days += 7 end return add_months(1).beginning_of_month.add_days(total_diff_in_days) end # END if (total_diff_in_days + 1) ... end
returns a new ZDate
object, NOTE! this returns nil if that date does not exist (sept 31st)
# File lib/nickel/zdate.rb, line 514 def get_next_date_from_date_of_month(date_of_month) o = dup if day == date_of_month o else if day > date_of_month o.increment_month! end ZDate.new(ZDate.format_date(o.year_str, o.month_str, date_of_month)) rescue nil end end
# File lib/nickel/zdate.rb, line 89 def is_today? warn '[DEPRECATION] `is_today?` is deprecated. Please use `today?` instead.' today? end
returns new ZDate
object, note this is the MONTH NUMBER, not MONTH INDEX from ZDate.months_of_year
returns the first day of the month
# File lib/nickel/zdate.rb, line 199 def jump_to_month(month_number) # find difference in months if month_number >= month ZDate.new(ZDate.format_date(year_str, month_number)) else ZDate.new(ZDate.format_date(year + 1, month_number)) end end
# File lib/nickel/zdate.rb, line 442 def leap_year? year % 400 == 0 || year % 4 == 0 && year % 100 != 0 end
# File lib/nickel/zdate.rb, line 59 def month month_str.to_i end
# File lib/nickel/zdate.rb, line 47 def month_str @date[4..5] end
# File lib/nickel/zdate.rb, line 419 def months_left_in_year 12 - month end
for example, “next friday”
# File lib/nickel/zdate.rb, line 123 def next(day) x_weeks_from_day(1, day) end
for example, “1st friday”, uses self as the reference month
# File lib/nickel/zdate.rb, line 100 def ordinal_dayindex(num, day_index) # create a date object at the first occurrence of day_index first_occ_date = ZDate.new(ZDate.format_date(year_str, month_str)).this(day_index) # if num is 1 through 4, we can just add (num-1) weeks if num <= 4 d = first_occ_date.add_weeks(num - 1) else # we want the last occurrence of this month # add 4 weeks to first occurrence, see if we are in the same month, subtract 1 week if we are not d = first_occ_date.add_weeks(4) if d.month != month d = d.sub_weeks(1) end end d end
for example, “previous friday”
# File lib/nickel/zdate.rb, line 128 def prev(day) (dayindex == day) ? dup : x_weeks_from_day(-1, day) end
# File lib/nickel/zdate.rb, line 67 def readable month_str + '/' + day_str + '/' + year_str end
sub_ methods return new ZDate
object, they do not modify self.
# File lib/nickel/zdate.rb, line 224 def sub_days(number) o = dup while number > 0 if (o.day - 1) >= number o.date = ZDate.format_date(o.year_str, o.month_str, o.day - number) number = 0 else number -= o.day o.decrement_month! end end o end
# File lib/nickel/zdate.rb, line 242 def sub_months(number) o = dup number.times do o.decrement_month! end o end
# File lib/nickel/zdate.rb, line 238 def sub_weeks(number) sub_days(7 * number) end
for example, “this friday”
# File lib/nickel/zdate.rb, line 118 def this(day) x_weeks_from_day(0, day) end
# File lib/nickel/zdate.rb, line 526 def to_date Date.new(year, month, day) end
# File lib/nickel/zdate.rb, line 530 def to_s date end
returns true if self is today
# File lib/nickel/zdate.rb, line 95 def today? self == ZDate.new end
returns a new date object
# File lib/nickel/zdate.rb, line 133 def x_weeks_from_day(weeks_away, day2index) day1index = dayindex if day1index > day2index days_away = 7 * (weeks_away + 1) - (day1index - day2index) elsif day1index < day2index days_away = (weeks_away * 7) + (day2index - day1index) elsif day1index == day2index days_away = 7 * weeks_away end add_days(days_away) # returns a new date object end
# File lib/nickel/zdate.rb, line 55 def year year_str.to_i end
# File lib/nickel/zdate.rb, line 43 def year_str @date[0..3] end
Protected Instance Methods
# File lib/nickel/zdate.rb, line 547 def decrement_month! if month != 1 # just bump down a number and set days to the last day in the month self.date = ZDate.format_date(year_str, month - 1, ZDate.days_in_month(month - 1, year)) else self.date = ZDate.format_date(year - 1, 12, 31) # dec has 31 days end end
Modifies self. bumps self to first day of next month
# File lib/nickel/zdate.rb, line 538 def increment_month! if month != 12 # just bump up a number self.date = ZDate.format_date(year_str, month + 1) else self.date = ZDate.format_date(year + 1) end end
Private Instance Methods
# File lib/nickel/zdate.rb, line 562 def after?(other) (year > other.year) || (year == other.year && (month > other.month || (month == other.month && day > other.day))) end
# File lib/nickel/zdate.rb, line 558 def before?(other) (year < other.year) || (year == other.year && (month < other.month || (month == other.month && day < other.day))) end
# File lib/nickel/zdate.rb, line 587 def get_day_or_max_day_in_month(day, month, year) # if day exists in month/year then use it, if it is not then use the last day of the month dm = ZDate.days_in_month(month, year) dm >= day ? day : dm end
# File lib/nickel/zdate.rb, line 570 def valid # It is important that valid_day is last because we have to do the days_in_month calculation!!! @date.length == 8 && @date !~ /\D/ && valid_year && valid_month && valid_day end
# File lib/nickel/zdate.rb, line 583 def valid_day day >= 1 && day <= days_in_month end
# File lib/nickel/zdate.rb, line 579 def valid_month month >= 1 && month <= 12 end
# File lib/nickel/zdate.rb, line 575 def valid_year year >= 1900 end
# File lib/nickel/zdate.rb, line 566 def validate fail 'ZDate says: invalid date' unless valid end