Skip to content

Commit

Permalink
Implement Quarter parsing
Browse files Browse the repository at this point in the history
Formats:
 * Arrow: 3 quarters ago, before 2 quarters
 * Anchor: last/this/next quarter
 * Narrow: Q1, 2nd quarter, fourth quarter
  • Loading branch information
davispuh committed Sep 28, 2014
1 parent b71dc83 commit 764b3c1
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 1 deletion.
13 changes: 13 additions & 0 deletions lib/chronic/arrow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Arrow
:wdays,
:weekends,
:fortnights,
:quarters,
:seasons
]

Expand All @@ -43,6 +44,7 @@ class Arrow
:weeks,
:fortnights,
:months,
:quarters,
:seasons,
:years
]
Expand Down Expand Up @@ -116,6 +118,12 @@ def to_span(span, timezone = nil)
when :seasons
# TODO
raise "Not Implemented Arrow #{unit}"
when :quarters
ds[0], quarter = Date::add_quarter(ds[0], Date::get_quarter_index(ds[1]), sign * count)
ds[1] = Date::QUARTERS[quarter]
ds[2] = 1
ds[3], ds[4], ds[5] = 0, 0, 0
precision = PRECISION.index(unit)
when :fortnights
ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], sign * count * Date::FORTNIGHT_DAYS)
precision = update_precision(precision, unit)
Expand Down Expand Up @@ -189,6 +197,11 @@ def to_span(span, timezone = nil)
de[0], de[1] = Date::add_month(ds[0], ds[1])
de[2] = 1
de[3] = de[4] = de[5] = 0
when :quarters
de[0], quarter = Date::add_quarter(ds[0], Date::get_quarter_index(ds[1]))
de[1] = Date::QUARTERS[quarter]
de[2] = 1
de[3] = de[4] = de[5] = 0
when :seasons
# TODO
raise "Not Implemented Arrow #{PRECISION[precision]}"
Expand Down
29 changes: 29 additions & 0 deletions lib/chronic/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ class Date
YEAR_QUARTERS = 4
YEAR_MONTHS = 12
SEASON_MONTHS = 3
QUARTER_MONTHS = 3
MONTH_DAYS = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
MONTH_DAYS_LEAP = [nil, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
FORTNIGHT_DAYS = 14
WEEK_DAYS = 7
DAY_HOURS = 24
YEAR_SECONDS = 31_536_000 # 365 * 24 * 60 * 60
SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
QUARTER_SECONDS = 7_776_000 # 90 * 24 * 60 * 60
MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
FORTNIGHT_SECONDS = 1_209_600 # 14 * 24 * 60 * 60
WEEK_SECONDS = 604_800 # 7 * 24 * 60 * 60
Expand All @@ -27,6 +29,7 @@ class Date
:autumn => [9, 23],
:winter => [12, 22]
}
QUARTERS = [nil, 1, 4, 7, 10]
MONTHS = {
:january => 1,
:february => 2,
Expand Down Expand Up @@ -102,6 +105,15 @@ def self.add_season(year, season, modifier = 1)
[year, season]
end

def self.add_quarter(year, quarter, amount = 1)
quarter += amount
if quarter > YEAR_QUARTERS or quarter < 1
year += (quarter - 1) / YEAR_QUARTERS
quarter = (quarter - 1) % YEAR_QUARTERS + 1
end
[year, quarter]
end

def self.add_month(year, month, amount = 1)
month += amount
if month > YEAR_MONTHS or month < 1
Expand All @@ -128,6 +140,18 @@ def self.add_day(year, month, day, amount = 1)
[year, month, day]
end

def self.get_quarter_index(month)
(month - 1) / QUARTER_MONTHS + 1
end

def self.get_quarter_month(month)
(get_quarter_index(month) - 1) * QUARTER_MONTHS + 1
end

def self.get_quarter_end(quarter)
QUARTERS[(quarter - 1) % YEAR_QUARTERS + 1]
end

def self.normalize(year, month, day)
year, month = add_month(year, month, 0)
year, month, day = add_day(year, month, day, 0)
Expand Down Expand Up @@ -185,6 +209,11 @@ def self.month_diff(date_month, month, modifier = 1, context = 0)
self.calculate_difference(month - date_month, YEAR_MONTHS, modifier, context)
end

# Calculate difference in quarters between given quarter and date quarter
def self.quarter_diff(date_quarter, quarter, modifier = 1, context = 0)
self.calculate_difference(quarter - date_quarter, YEAR_QUARTERS, modifier, context)
end

end

module DateStructure
Expand Down
3 changes: 3 additions & 0 deletions lib/chronic/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ def definitions
[[Grabber, SeparatorSpace, MonthName], :handle_gr_mn],
[[Grabber, SeparatorSpace, SeasonName], :handle_gr_sn],
[[Grabber, SeparatorSpace, TimeSpecial], :handle_gr_ts],
[[Grabber, SeparatorSpace, KeywordQ, Scalar], :handle_gr_q_s],
[[Grabber, SeparatorSpace, Unit], :handle_gr_u],
[[KeywordIn, SeparatorSpace, Scalar, SeparatorSpace, Unit], :handle_in_s_u],
[[DaySpecial], :handle_ds],
[[KeywordQ, Scalar], :handle_q_s],
[[Unit], :handle_u]
]
end
Expand All @@ -148,6 +150,7 @@ def definitions
[[Grabber, SeparatorSpace, Ordinal, SeparatorSpace, Unit], :handle_gr_o_u],
[[Ordinal, SeparatorSpace, Unit], :handle_o_u],
[[Ordinal, SeparatorSpace, DayName], :handle_o_dn],
[[KeywordQ, Scalar], :handle_q_s]
]
end
end
Expand Down
8 changes: 8 additions & 0 deletions lib/chronic/handlers/anchor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,13 @@ def handle_in_s_u
handle_u
end

# Handle grabber/keyword-q/scalar
# formats: gr Qs
def handle_gr_q_s
handle_gr
next_tag
handle_q_s
end

end
end
11 changes: 11 additions & 0 deletions lib/chronic/handlers/general.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,16 @@ def handle_sn
@precision = :season
end

# Handle keyword-q/scalar
# formats: Qs
def handle_q_s
next_tag # Q
@quarter = @tokens[@index].get_tag(Scalar).type
@number = @quarter
@index += 1
@precision = :quarter
@unit = :quarter
end

end
end
24 changes: 23 additions & 1 deletion lib/chronic/objects/anchor_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def is_valid?
end

def to_s
"grabber #{@grabber.inspect}, unit #{@unit.inspect}, season #{@season.inspect}, month #{@month.inspect}, wday #{@wday.inspect}, day special #{@day_special.inspect}, time special #{@time_special.inspect}, count #{@count.inspect}"
"grabber #{@grabber.inspect}, unit #{@unit.inspect}, season #{@season.inspect}, quarter #{@quarter.inspect}, month #{@month.inspect}, wday #{@wday.inspect}, day special #{@day_special.inspect}, time special #{@time_special.inspect}, count #{@count.inspect}"
end

def to_span(span = nil, time = nil, timezone = nil)
Expand Down Expand Up @@ -53,6 +53,15 @@ def to_span(span = nil, time = nil, timezone = nil)
end_year, next_season = Date::add_season(year, @season)
end_month, end_day = Date::SEASON_DATES[next_season]
hour = minute = second = end_hour = end_minute = end_second = 0
elsif @quarter
quarter = Date::get_quarter_index(month)
diff = Date::quarter_diff(quarter, @quarter, modifier, sign)
year, quarter = Date::add_quarter(year, quarter, diff)
month = Date::QUARTERS[@quarter]
end_year, next_quarter = Date::add_quarter(year, @quarter)
end_month = Date::QUARTERS[next_quarter]
day = end_day = 1
hour = minute = second = end_hour = end_minute = end_second = 0
elsif @month
diff = Date::month_diff(month, @month, modifier, sign)
year, month = Date::add_month(year, month, diff)
Expand Down Expand Up @@ -115,6 +124,19 @@ def to_span(span = nil, time = nil, timezone = nil)
end_day = 1
end_hour = end_minute = end_second = 0
end
when :quarter
year, quarter = Date::add_quarter(year, Date::get_quarter_index(month), modifier)
month = Date::QUARTERS[quarter]
unless modifier.zero? and @context == :future
day = 1
hour = minute = second = 0
end
unless modifier.zero? and @context == :past
end_year, next_quarter = Date::add_quarter(year, quarter)
end_month = Date::QUARTERS[next_quarter]
end_day = 1
end_hour = end_minute = end_second = 0
end
when :month
year, month = Date::add_month(year, month, modifier)
unless modifier.zero? and @context == :future
Expand Down
10 changes: 10 additions & 0 deletions lib/chronic/objects/narrow_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ def to_span(span = nil, timezone = nil)
when :season
# TODO
raise "Not Implemented NarrowObject #{@unit.inspect}"
when :quarter
this_quarter = Date::get_quarter_index(start.month)
diff = Date::quarter_diff(this_quarter, @number, modifier, sign)
year, quarter = Date::add_quarter(start.year, this_quarter, diff)
year = start.year if span
month = Date::QUARTERS[quarter]
end_year, next_quarter = Date::add_quarter(year, quarter)
end_month = Date::QUARTERS[next_quarter]
day = end_day = 1
hour = minute = second = 0
when :month
day = start.day
year, month = Date::add_month(start.year, start.month, @number - 1)
Expand Down
4 changes: 4 additions & 0 deletions lib/chronic/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ def pre_proccess(text)
#
# Returns a new String ready for Chronic to parse.
def pre_normalize(text)
text.gsub!(/\b(quarters?)\b/, '<=\1=>') # workaround for Numerizer
text.gsub!(/\b\s+third\b/, ' 3rd')
text.gsub!(/\b\s+fourth\b/, ' 4th')
text = Numerizer.numerize(text)
text.gsub!(/<=(quarters?)=>/, '\1')
text
end

Expand Down
7 changes: 7 additions & 0 deletions lib/chronic/tags/keyword.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def self.scan(tokens, options)
token.tag scan_for(token, KeywordOn, { 'on' => :on })
token.tag scan_for(token, KeywordAnd, { 'and' => :and })
token.tag scan_for(token, KeywordTo, { 'to' => :to })
token.tag scan_for(token, KeywordQ, { :Q => :Q })
end
end

Expand Down Expand Up @@ -53,4 +54,10 @@ def to_s
end
end

class KeywordQ < Keyword #:nodoc:
def to_s
super << '-q'
end
end

end
1 change: 1 addition & 0 deletions lib/chronic/tags/unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def self.patterns
@@paterns ||= {
/^years?$/i => :year,
/^seasons?$/i => :season,
/^quarters?$/i => :quarter,
/^months?$/i => :month,
/^fortnights?$/i => :fortnight,
/^weeks?$/i => :week,
Expand Down

0 comments on commit 764b3c1

Please sign in to comment.