From 4b2a95dca985702d84319fa69c17fe285d5193d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sat, 27 Sep 2014 22:07:05 +0300 Subject: [PATCH 01/23] Correct tests --- test/test_parsing.rb | 148 +++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/test/test_parsing.rb b/test/test_parsing.rb index 979167b6..982ec481 100644 --- a/test/test_parsing.rb +++ b/test/test_parsing.rb @@ -325,7 +325,7 @@ def test_handle_sm_sd_sy time = parse_now("30/2/2000") assert_nil time - time = parse_now("2013-03-12 17:00", :context => :past) + time = parse_now("2013-03-12 17:00", :context => :past, :guess => :begin) assert_equal Time.local(2013, 3, 12, 17, 0, 0), time end @@ -377,7 +377,7 @@ def test_handle_sy_sm_sd time = parse_now("1902-08-20") assert_equal Time.local(1902, 8, 20, 12, 0, 0), time - time = parse_now("2013.07.30 11:45:23") + time = parse_now("2013.07.30 11:45:23", :ambiguous_time_range => :none) assert_equal Time.local(2013, 7, 30, 11, 45, 23), time time = parse_now("2013.08.09") @@ -468,11 +468,11 @@ def test_handle_r time = parse_now("on Tuesday") assert_equal Time.local(2006, 8, 22, 12), time - time = parse_now("1:00:00 PM") + time = parse_now("1:00:00 PM", :context => :none) assert_equal Time.local(2006, 8, 16, 13), time time = parse_now("01:00:00 PM") - assert_equal Time.local(2006, 8, 16, 13), time + assert_equal Time.local(2006, 8, 17, 13), time time = parse_now("today at 02:00:00", :hours24 => false) assert_equal Time.local(2006, 8, 16, 14), time @@ -486,7 +486,7 @@ def test_handle_r time = parse_now("today at 03:00:00", :hours24 => true) assert_equal Time.local(2006, 8, 16, 3), time - time = parse_now("tomorrow at 4a.m.") + time = parse_now("tomorrow at 4a.m.", :guess => :begin) assert_equal Time.local(2006, 8, 17, 4), time end @@ -558,7 +558,7 @@ def test_handle_sm_rmn_sy assert_equal Time.local(2011, 3, 30, 12), time time = parse_now('31-Aug-12') - assert_equal Time.local(2012, 8, 31), time + assert_equal Time.local(2012, 8, 31, 12), time end # end of testing handlers @@ -573,7 +573,7 @@ def test_parse_guess_r time = parse_now("5") assert_equal Time.local(2006, 8, 16, 17), time - time = Chronic.parse("5", :now => Time.local(2006, 8, 16, 3, 0, 0, 0), :ambiguous_time_range => :none) + time = Chronic.parse("5", :now => Time.local(2006, 8, 16, 3, 0, 0, 0), :ambiguous_time_range => :none, :guess => :begin) assert_equal Time.local(2006, 8, 16, 5), time time = parse_now("13:00") @@ -582,10 +582,10 @@ def test_parse_guess_r time = parse_now("13:45") assert_equal Time.local(2006, 8, 17, 13, 45), time - time = parse_now("1:01pm") + time = parse_now("1:01pm", :context => :past) assert_equal Time.local(2006, 8, 16, 13, 01), time - time = parse_now("2:01pm") + time = parse_now("2:01pm", :context => :none) assert_equal Time.local(2006, 8, 16, 14, 01), time time = parse_now("november") @@ -593,13 +593,13 @@ def test_parse_guess_r end def test_parse_guess_rr - time = parse_now("friday 13:00") + time = parse_now("friday 13:00", :guess => :begin) assert_equal Time.local(2006, 8, 18, 13), time time = parse_now("monday 4:00") assert_equal Time.local(2006, 8, 21, 16), time - time = parse_now("sat 4:00", :ambiguous_time_range => :none) + time = parse_now("sat 4:00", :ambiguous_time_range => :none, :guess => :begin) assert_equal Time.local(2006, 8, 19, 4), time time = parse_now("sunday 4:20", :ambiguous_time_range => :none) @@ -657,7 +657,7 @@ def test_parse_guess_gr # year time = parse_now("this year", :guess => false) - assert_equal Time.local(2006, 8, 17), time.begin + assert_equal Time.local(2006, 8, 16).to_date, time.begin.to_date time = parse_now("this year", :context => :past, :guess => false) assert_equal Time.local(2006, 1, 1), time.begin @@ -838,7 +838,7 @@ def test_parse_guess_grr time = parse_now("today at 2100") assert_equal Time.local(2006, 8, 16, 21), time - time = parse_now("this day at 0900") + time = parse_now("this day at 0900", :guess => :begin) assert_equal Time.local(2006, 8, 16, 9), time time = parse_now("tomorrow at 0900") @@ -908,41 +908,41 @@ def test_parse_guess_rgr end def test_parse_guess_a_ago - time = parse_now("AN hour ago") + time = parse_now("AN hour ago", :guess => :begin) assert_equal Time.local(2006, 8, 16, 13), time - time = parse_now("A day ago") + time = parse_now("A day ago", :guess => :begin) assert_equal Time.local(2006, 8, 15, 14), time - time = parse_now("a month ago") + time = parse_now("a month ago", :guess => :begin) assert_equal Time.local(2006, 7, 16, 14), time - time = parse_now("a year ago") + time = parse_now("a year ago", :guess => :begin) assert_equal Time.local(2005, 8, 16, 14), time end def test_parse_guess_s_r_p # past - time = parse_now("3 years ago") + time = parse_now("3 years ago", :guess => :begin) assert_equal Time.local(2003, 8, 16, 14), time - time = parse_now("1 month ago") + time = parse_now("1 month ago", :guess => :begin) assert_equal Time.local(2006, 7, 16, 14), time - time = parse_now("1 fortnight ago") + time = parse_now("1 fortnight ago", :guess => :begin) assert_equal Time.local(2006, 8, 2, 14), time - time = parse_now("2 fortnights ago") + time = parse_now("2 fortnights ago", :guess => :begin) assert_equal Time.local(2006, 7, 19, 14), time - time = parse_now("3 weeks ago") + time = parse_now("3 weeks ago", :guess => :begin) assert_equal Time.local(2006, 7, 26, 14), time - time = parse_now("2 weekends ago") - assert_equal Time.local(2006, 8, 5), time + time = parse_now("2 weekends ago", :guess => :begin) + assert_equal Date.new(2006, 8, 5), time.to_date - time = parse_now("3 days ago") + time = parse_now("3 days ago", :guess => :begin) assert_equal Time.local(2006, 8, 13, 14), time #time = parse_now("1 monday ago") @@ -951,51 +951,51 @@ def test_parse_guess_s_r_p time = parse_now("5 mornings ago") assert_equal Time.local(2006, 8, 12, 9), time - time = parse_now("7 hours ago") + time = parse_now("7 hours ago", :guess => :begin) assert_equal Time.local(2006, 8, 16, 7), time - time = parse_now("3 minutes ago") + time = parse_now("3 minutes ago", :guess => :begin) assert_equal Time.local(2006, 8, 16, 13, 57), time - time = parse_now("20 seconds before now") + time = parse_now("20 seconds before now", :guess => :begin) assert_equal Time.local(2006, 8, 16, 13, 59, 40), time # future - time = parse_now("3 years from now") + time = parse_now("3 years from now", :guess => :begin) assert_equal Time.local(2009, 8, 16, 14, 0, 0), time - time = parse_now("6 months hence") + time = parse_now("6 months hence", :guess => :begin) assert_equal Time.local(2007, 2, 16, 14), time - time = parse_now("3 fortnights hence") + time = parse_now("3 fortnights hence", :guess => :begin) assert_equal Time.local(2006, 9, 27, 14), time - time = parse_now("1 week from now") + time = parse_now("1 week from now", :guess => :begin) assert_equal Time.local(2006, 8, 23, 14, 0, 0), time - time = parse_now("1 weekend from now") + time = parse_now("1 weekend from now", :guess => :begin) assert_equal Time.local(2006, 8, 19), time - time = parse_now("2 weekends from now") + time = parse_now("2 weekends from now", :guess => :begin) assert_equal Time.local(2006, 8, 26), time - time = parse_now("1 day hence") + time = parse_now("1 day hence", :guess => :begin) assert_equal Time.local(2006, 8, 17, 14), time time = parse_now("5 mornings hence") assert_equal Time.local(2006, 8, 21, 9), time - time = parse_now("1 hour from now") + time = parse_now("1 hour from now", :guess => :begin) assert_equal Time.local(2006, 8, 16, 15), time - time = parse_now("20 minutes hence") + time = parse_now("20 minutes hence", :guess => :begin) assert_equal Time.local(2006, 8, 16, 14, 20), time - time = parse_now("20 seconds from now") + time = parse_now("20 seconds from now", :guess => :begin) assert_equal Time.local(2006, 8, 16, 14, 0, 20), time - time = Chronic.parse("2 months ago", :now => Time.parse("2007-03-07 23:30")) + time = Chronic.parse("2 months ago", :now => Time.parse("2007-03-07 23:30"), :guess => :begin) assert_equal Time.local(2007, 1, 7, 23, 30), time # Two repeaters @@ -1010,7 +1010,7 @@ def test_parse_guess_s_r_p end def test_parse_guess_p_s_r - time = parse_now("in 3 hours") + time = parse_now("in 3 hours", :guess => :end) assert_equal Time.local(2006, 8, 16, 17), time end @@ -1021,7 +1021,7 @@ def test_parse_guess_s_r_p_a assert_equal Time.local(2003, 8, 17, 12), time time = parse_now("3 years ago this friday") - assert_equal Time.local(2003, 8, 18, 12), time + assert_equal Time.local(2003, 8, 22, 12), time time = parse_now("3 months ago saturday at 5:00 pm") assert_equal Time.local(2006, 5, 19, 17), time @@ -1108,27 +1108,27 @@ def test_relative_to_an_hour_before # example prenormalization "10 to 2" becomes "10 minutes past 2" assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 to 2") assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 till 2") - assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 prior to 2") assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 before 2") + assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 prior to 2", :guess => :begin) # uses the current hour, so 2006-08-16 13:50:00, not 14:50 assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 to") - assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 till") + assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 till", :guess => :begin) - assert_equal Time.local(2006, 8, 16, 15, 45), parse_now("quarter to 4") + assert_equal Time.local(2006, 8, 16, 15, 45), parse_now("quarter to 4", :guess => :begin) end def test_relative_to_an_hour_after # not nil - assert_equal Time.local(2006, 8, 16, 14, 10), parse_now("10 after 2") - assert_equal Time.local(2006, 8, 16, 14, 10), parse_now("10 past 2") - assert_equal Time.local(2006, 8, 16, 14, 30), parse_now("half past 2") + assert_equal Time.local(2006, 8, 16, 14, 10), parse_now("10 after 2", :guess => :begin) + assert_equal Time.local(2006, 8, 16, 14, 10), parse_now("10 past 2", :guess => :begin) + assert_equal Time.local(2006, 8, 16, 14, 30), parse_now("half past 2", :guess => :begin) end def test_parse_only_complete_pointers - assert_equal parse_now("eat pasty buns today at 2pm"), @time_2006_08_16_14_00_00 - assert_equal parse_now("futuristically speaking today at 2pm"), @time_2006_08_16_14_00_00 - assert_equal parse_now("meeting today at 2pm"), @time_2006_08_16_14_00_00 + assert_equal parse_now("eat pasty buns today at 2pm", :guess => :begin), @time_2006_08_16_14_00_00 + assert_equal parse_now("futuristically speaking today at 2pm", :guess => :begin), @time_2006_08_16_14_00_00 + assert_equal parse_now("meeting today at 2pm", :guess => :begin), @time_2006_08_16_14_00_00 end def test_am_pm @@ -1144,35 +1144,35 @@ def test_a_p def test_seasons t = parse_now("this spring", :guess => false) assert_equal Time.local(2007, 3, 20), t.begin - assert_equal Time.local(2007, 6, 20), t.end + assert_equal Time.local(2007, 6, 21), t.end t = parse_now("this winter", :guess => false) assert_equal Time.local(2006, 12, 22), t.begin - assert_equal Time.local(2007, 3, 19), t.end + assert_equal Time.local(2007, 3, 20), t.end t = parse_now("last spring", :guess => false) assert_equal Time.local(2006, 3, 20), t.begin - assert_equal Time.local(2006, 6, 20), t.end + assert_equal Time.local(2006, 6, 21), t.end t = parse_now("last winter", :guess => false) assert_equal Time.local(2005, 12, 22), t.begin - assert_equal Time.local(2006, 3, 19), t.end + assert_equal Time.local(2006, 3, 20), t.end t = parse_now("next spring", :guess => false) assert_equal Time.local(2007, 3, 20), t.begin - assert_equal Time.local(2007, 6, 20), t.end + assert_equal Time.local(2007, 6, 21), t.end end def test_quarters - time = parse_now("this quarter", :guess => false) + time = parse_now("this quarter", {:guess => false, :context => :none}) assert_equal Time.local(2006, 7, 1), time.begin assert_equal Time.local(2006, 10, 1), time.end - time = parse_now("next quarter", :guess => false) + time = parse_now("next quarter", {:guess => false, :context => :none}) assert_equal Time.local(2006, 10, 1), time.begin assert_equal Time.local(2007, 1, 1), time.end - time = parse_now("last quarter", :guess => false) + time = parse_now("last quarter", {:guess => false, :context => :none}) assert_equal Time.local(2006, 4, 1), time.begin assert_equal Time.local(2006, 7, 1), time.end end @@ -1313,11 +1313,11 @@ def test_quarters_named assert_equal Time.local(2005, 7, 1), time.begin assert_equal Time.local(2005, 10, 1), time.end - time = parse_now("#{string} this year", :guess => false) + time = parse_now("#{string} this year", :guess => false, context: :none) assert_equal Time.local(2006, 7, 1), time.begin assert_equal Time.local(2006, 10, 1), time.end - time = parse_now("this year #{string}", :guess => false) + time = parse_now("this year #{string}", :guess => false, context: :none) assert_equal Time.local(2006, 7, 1), time.begin assert_equal Time.local(2006, 10, 1), time.end @@ -1435,10 +1435,10 @@ def test_handle_rdn_rmn_sd assert_equal Time.local(2006, 8, 10, 12), time time = parse_now("Thursday July 31") - assert_equal Time.local(2006, 7, 31, 12), time + assert_equal Time.local(2008, 7, 31, 12), time time = parse_now("Thursday December 31") - assert_equal Time.local(2006, 12, 31, 12), time + assert_equal Time.local(2009, 12, 31, 12), time end def test_handle_rdn_rmn_sd_rt @@ -1458,13 +1458,13 @@ def test_handle_rdn_od_rt time = parse_now("Thu 17th at 4pm") assert_equal Time.local(2006, 8, 17, 16), time - time = parse_now("Thu 16th at 4pm") - assert_equal Time.local(2006, 8, 16, 16), time + time = parse_now("Sat 16th at 4pm", :guess => :begin) + assert_equal Time.local(2006, 9, 16, 16), time time = parse_now("Thu 1st at 4pm") assert_equal Time.local(2006, 9, 1, 16), time - time = parse_now("Thu 1st at 4pm", :context => :past) + time = parse_now("Tue 1st at 4pm", :context => :past) assert_equal Time.local(2006, 8, 1, 16), time end @@ -1477,13 +1477,13 @@ def test_handle_rdn_rmn_sd_sy time = parse_now("Thu Aug 10 2006") assert_equal Time.local(2006, 8, 10, 12), time - time = parse_now("Thursday July 31 2006") + time = parse_now("Mon July 31 2006", :context => :past) assert_equal Time.local(2006, 7, 31, 12), time - time = parse_now("Thursday December 31 2006") + time = parse_now("Sun December 31 2006") assert_equal Time.local(2006, 12, 31, 12), time - time = parse_now("Thursday December 30 2006") + time = parse_now("Sat December 30 2006") assert_equal Time.local(2006, 12, 30, 12), time end @@ -1491,24 +1491,24 @@ def test_handle_rdn_rmn_od time = parse_now("Thu Aug 10th") assert_equal Time.local(2006, 8, 10, 12), time - time = parse_now("Thursday July 31st") + time = parse_now("Monday July 31st", :context => :past) assert_equal Time.local(2006, 7, 31, 12), time - time = parse_now("Thursday December 31st") + time = parse_now("Sun December 31st") assert_equal Time.local(2006, 12, 31, 12), time end def test_handle_rdn_rmn_od_sy - time = parse_now("Thu Aug 10th 2005") + time = parse_now("Wed Aug 10th 2005") assert_equal Time.local(2005, 8, 10, 12), time - time = parse_now("Thursday July 31st 2005") + time = parse_now("Sun July 31st 2005") assert_equal Time.local(2005, 7, 31, 12), time - time = parse_now("Thursday December 31st 2005") + time = parse_now("Sat December 31st 2005") assert_equal Time.local(2005, 12, 31, 12), time - time = parse_now("Thursday December 30th 2005") + time = parse_now("Fr December 30th 2005") assert_equal Time.local(2005, 12, 30, 12), time end From 1a6c7456c2f890d829d80d7873ea55cc19553cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sat, 27 Sep 2014 23:04:44 +0300 Subject: [PATCH 02/23] Add and improve Date and Time methods --- lib/chronic.rb | 36 ++--------------------- lib/chronic/date.rb | 70 +++++++++++++++++++++++++++++++++++++++++---- lib/chronic/time.rb | 66 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 128 insertions(+), 44 deletions(-) diff --git a/lib/chronic.rb b/lib/chronic.rb index d56641fd..70f5e961 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -107,41 +107,11 @@ def self.parse(text, options = {}) # # Returns a new Time object constructed from these params. def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, offset = nil) - if second >= 60 - minute += second / 60 - second = second % 60 - end - - if minute >= 60 - hour += minute / 60 - minute = minute % 60 - end + day, hour, minute, second = Time::normalize(day, hour, minute, second) - if hour >= 24 - day += hour / 24 - hour = hour % 24 - end + year, month, day = Date::add_day(year, month, day, 0) if day > 28 + year, month = Date::add_month(year, month, 0) if month > 12 - # determine if there is a day overflow. this is complicated by our crappy calendar - # system (non-constant number of days per month) - day <= 56 || raise('day must be no more than 56 (makes month resolution easier)') - if day > 28 # no month ever has fewer than 28 days, so only do this if necessary - days_this_month = ::Date.leap?(year) ? Date::MONTH_DAYS_LEAP[month] : Date::MONTH_DAYS[month] - if day > days_this_month - month += day / days_this_month - day = day % days_this_month - end - end - - if month > 12 - if month % 12 == 0 - year += (month - 12) / 12 - month = 12 - else - year += month / 12 - month = month % 12 - end - end if Chronic.time_class.name == 'Date' Chronic.time_class.new(year, month, day) elsif not Chronic.time_class.respond_to?(:new) or (RUBY_VERSION.to_f < 1.9 and Chronic.time_class.name == 'Time') diff --git a/lib/chronic/date.rb b/lib/chronic/date.rb index 8c646191..d4ca9cd3 100644 --- a/lib/chronic/date.rb +++ b/lib/chronic/date.rb @@ -1,8 +1,13 @@ module Chronic class Date - YEAR_MONTHS = 12 + YEAR_QUARTERS = 4 + YEAR_MONTHS = 12 + SEASON_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 MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60 @@ -10,6 +15,18 @@ class Date WEEK_SECONDS = 604_800 # 7 * 24 * 60 * 60 WEEKEND_SECONDS = 172_800 # 2 * 24 * 60 * 60 DAY_SECONDS = 86_400 # 24 * 60 * 60 + SEASONS = [ + :spring, + :summer, + :autumn, + :winter + ] + SEASON_DATES = { + :spring => [3, 20], + :summer => [6, 21], + :autumn => [9, 23], + :winter => [12, 22] + } MONTHS = { :january => 1, :february => 2, @@ -69,12 +86,55 @@ def self.make_year(year, bias) full_year end + def self.days_month(year, month) + days_month = ::Date.leap?(year) ? Date::MONTH_DAYS_LEAP[month] : Date::MONTH_DAYS[month] + end + def self.month_overflow?(year, month, day) - if ::Date.leap?(year) - day > Date::MONTH_DAYS_LEAP[month] - else - day > Date::MONTH_DAYS[month] + day > days_month(year, month) + end + + def self.add_season(year, season, modifier = 1) + index = SEASONS.index(season) + modifier + year += index / SEASONS.count + season = SEASONS[index % SEASONS.count] + [year, season] + end + + def self.add_month(year, month, amount = 1) + month += amount + if month > YEAR_MONTHS or month < 1 + year += (month - 1) / YEAR_MONTHS + month = (month - 1) % YEAR_MONTHS + 1 end + [year, month] + end + + def self.add_day(year, month, day, amount = 1) + day += amount + days_month = self.days_month(year, month) + while day > days_month + day -= days_month + year, month = add_month(year, month, 1) + days_month = self.days_month(year, month) + end + days_prev_month = self.days_month(year, (month - 2) % YEAR_MONTHS + 1) + while day < 1 + day += days_prev_month + year, month = add_month(year, month, -1) + days_prev_month = self.days_month(year, month) + end + [year, month, day] + end + + def self.normalize(year, month, day) + year, month = add_month(year, month, 0) + year, month, day = add_day(year, month, day, 0) + [year, month, day] + end + + def self.split(date) + [date.year, date.month, date.day] end end diff --git a/lib/chronic/time.rb b/lib/chronic/time.rb index 2b3e6f34..2f0e657b 100644 --- a/lib/chronic/time.rb +++ b/lib/chronic/time.rb @@ -1,9 +1,21 @@ module Chronic class Time - HOUR_SECONDS = 3600 # 60 * 60 - MINUTE_SECONDS = 60 - SECOND_SECONDS = 1 # haha, awesome + DAY_HOURS = 24 + HOUR_MINUTES = 60 + HOUR_SECONDS = 3600 # 60 * 60 + MINUTE_SECONDS = 60 + SECOND_SECONDS = 1 # haha, awesome SUBSECOND_SECONDS = 0.001 + SPECIAL = { + :am => (0... 12), # 0am to 12pm + :pm => (12...24), # 12am to 0am + :morning => (6... 12), # 6am to 12pm + :noon => (12.. 12), # 12pm + :afternoon => (13...17), # 1pm to 5pm + :evening => (17...20), # 5pm to 8pm + :night => (20...24), # 8pm to 0am + :midnight => (24.. 24) # 0am + } # Checks if given number could be hour def self.could_be_hour?(hour, width = nil, hours12 = false) @@ -12,17 +24,17 @@ def self.could_be_hour?(hour, width = nil, hours12 = false) # Checks if given number could be minute def self.could_be_minute?(minute, width = nil) - minute >= 0 && minute <= 60 && (width.nil? || width <= 2) + minute >= 0 && minute <= HOUR_MINUTES && (width.nil? || width <= 2) end # Checks if given number could be second def self.could_be_second?(second, width = nil) - second >= 0 && second <= 60 && (width.nil? || width <= 2) + second >= 0 && second <= MINUTE_SECONDS && (width.nil? || width <= 2) end # Checks if given number could be subsecond def self.could_be_subsecond?(subsecond, width = nil) - subsecond >= 0 && subsecond <= 999999 && (width.nil? || width > 0) + subsecond >= 0 && subsecond <= 99999999 && (width.nil? || width > 0) end # normalize offset in seconds to offset as string +mm:ss or -mm:ss @@ -36,5 +48,47 @@ def self.normalize_offset(offset) sign + hours + minutes end + def self.add_second(minute, second, amount = 1) + second += amount + if second >= MINUTE_SECONDS or second < 0 + minute += second / MINUTE_SECONDS + second = second % MINUTE_SECONDS + end + [minute, second] + end + + def self.add_minute(hour, minute, amount = 1) + minute += amount + if minute >= HOUR_MINUTES or minute < 0 + hour += minute / HOUR_MINUTES + minute = minute % HOUR_MINUTES + end + [hour, minute] + end + + def self.add_hour(day, hour, amount = 1) + hour += amount + if hour >= DAY_HOURS or hour < 0 + day += hour / DAY_HOURS + hour = hour % DAY_HOURS + end + [day, hour] + end + + def self.normalize(day, hour, minute, second) + minute, second = add_second(minute, second, 0) + hour, minute = add_minute(hour, minute, 0) + day, hour = add_hour(day, hour, 0) + [day, hour, minute, second] + end + + def self.split(time) + if time.is_a?(::Time) or time.is_a?(::DateTime) + [time.hour, time.min, time.sec, time.to_time.utc_offset] + else + [0, 0, 0, nil] + end + end end + end From a845dd252d03356cd65c1464a8f3fd0e49c9cdb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sat, 27 Sep 2014 23:31:40 +0300 Subject: [PATCH 03/23] Create TimeZone class and remove MiniDate --- lib/chronic.rb | 11 +++++-- lib/chronic/mini_date.rb | 38 ---------------------- lib/chronic/time.rb | 11 ------- lib/chronic/time_zone.rb | 68 ++++++++++++++++++++++++++++++++++++++++ test/test_mini_date.rb | 32 ------------------- 5 files changed, 76 insertions(+), 84 deletions(-) delete mode 100644 lib/chronic/mini_date.rb create mode 100644 lib/chronic/time_zone.rb delete mode 100644 test/test_mini_date.rb diff --git a/lib/chronic.rb b/lib/chronic.rb index 70f5e961..e8c0421d 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -7,10 +7,10 @@ require 'chronic/parser' require 'chronic/date' require 'chronic/time' +require 'chronic/time_zone' require 'chronic/handler' require 'chronic/handlers' -require 'chronic/mini_date' require 'chronic/span' require 'chronic/token' require 'chronic/tokenizer' @@ -106,7 +106,7 @@ def self.parse(text, options = {}) # second - Integer second. # # Returns a new Time object constructed from these params. - def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, offset = nil) + def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, timezone = nil) day, hour, minute, second = Time::normalize(day, hour, minute, second) year, month, day = Date::add_day(year, month, day, 0) if day > 28 @@ -117,7 +117,12 @@ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, o elsif not Chronic.time_class.respond_to?(:new) or (RUBY_VERSION.to_f < 1.9 and Chronic.time_class.name == 'Time') Chronic.time_class.local(year, month, day, hour, minute, second) else - offset = Time::normalize_offset(offset) if Chronic.time_class.name == 'DateTime' + if timezone and timezone.respond_to?(:to_offset) + offset = timezone.to_offset(year, month, day, hour, minute, second) + else + offset = timezone + end + offset = TimeZone::normalize_offset(offset) if Chronic.time_class.name == 'DateTime' Chronic.time_class.new(year, month, day, hour, minute, second, offset) end end diff --git a/lib/chronic/mini_date.rb b/lib/chronic/mini_date.rb deleted file mode 100644 index 921fc2a0..00000000 --- a/lib/chronic/mini_date.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Chronic - class MiniDate - attr_accessor :month, :day - - def self.from_time(time) - new(time.month, time.day) - end - - def initialize(month, day) - unless (1..12).include?(month) - raise ArgumentError, '1..12 are valid months' - end - - @month = month - @day = day - end - - def is_between?(md_start, md_end) - return false if (@month == md_start.month && @month == md_end.month) && - (@day < md_start.day || @day > md_end.day) - return true if (@month == md_start.month && @day >= md_start.day) || - (@month == md_end.month && @day <= md_end.day) - - i = (md_start.month % 12) + 1 - - until i == md_end.month - return true if @month == i - i = (i % 12) + 1 - end - - return false - end - - def equals?(other) - @month == other.month and @day == other.day - end - end -end diff --git a/lib/chronic/time.rb b/lib/chronic/time.rb index 2f0e657b..2d14b0f0 100644 --- a/lib/chronic/time.rb +++ b/lib/chronic/time.rb @@ -37,17 +37,6 @@ def self.could_be_subsecond?(subsecond, width = nil) subsecond >= 0 && subsecond <= 99999999 && (width.nil? || width > 0) end - # normalize offset in seconds to offset as string +mm:ss or -mm:ss - def self.normalize_offset(offset) - return offset if offset.is_a?(String) - offset = Chronic.time_class.now.to_time.utc_offset unless offset # get current system's UTC offset if offset is nil - sign = '+' - sign = '-' if offset < 0 - hours = (offset.abs / 3600).to_i.to_s.rjust(2,'0') - minutes = (offset.abs % 3600).to_s.rjust(2,'0') - sign + hours + minutes - end - def self.add_second(minute, second, amount = 1) second += amount if second >= MINUTE_SECONDS or second < 0 diff --git a/lib/chronic/time_zone.rb b/lib/chronic/time_zone.rb new file mode 100644 index 00000000..590edcf4 --- /dev/null +++ b/lib/chronic/time_zone.rb @@ -0,0 +1,68 @@ +module Chronic + class TimeZone + + # normalize offset in seconds to offset as string +mm:ss or -mm:ss + def self.normalize_offset(offset = nil) + return offset if offset.is_a?(String) + offset = Chronic.time_class.now.to_time.utc_offset unless offset # get current system's UTC offset if offset is nil + sign = '+' + sign = '-' if offset < 0 + hours = (offset.abs / Time::HOUR_SECONDS).to_i.to_s.rjust(2,'0') + minutes = (offset.abs % Time::HOUR_SECONDS).to_s.rjust(2,'0') + sign + hours + minutes + end + + def self.abbreviation_offests(abbr, date = nil) + TimezoneParser::Abbreviation.getOffsets(abbr, date, date).first + end + + def self.name_offsets(name, date = nil) + TimezoneParser::Timezone.getOffsets(name, date, date).first + end + + def self.zone_offsets(zone) + TZInfo::Timezone.get(@zone).current_period.utc_offset + end + + def self.to_offset(hour, minute, sign = 1) + offset = hour * Time::HOUR_SECONDS + minute * Time::MINUTE_SECONDS + offset *= sign + offset + end + + end + + module TimeZoneStructure + attr_reader :offset + attr_reader :sign + attr_reader :tzhour + attr_reader :tzminute + attr_reader :name + attr_reader :abbr + attr_reader :zone + + def to_offset(year, month = 1, day = 1, hour = 0, minute = 0, second = 0) + date = DateTime.new(year, month, day, hour, minute, second) + if @abbr + TimeZone::abbreviation_offests(@abbr, date) + elsif @name + TimeZone::name_offsets(@name, date) + elsif @zone + TimeZone::zone_offsets(@zone) + elsif @tzhour + sign = 1 + sign = -1 if @sign and @sign.type == :minus + TimeZone::to_offset(@tzhour, @tzminute, sign) + else + @offset + end + end + + def to_s + sign = @sign ? @sign.type.inspect : 'nil' + "sign #{sign}, tzhour #{@tzhour.inspect}, tzminute #{@tzminute.inspect}, offset #{@offset.inspect}, name #{@name.inspect}, abbr #{@abbr.inspect}, zone #{@zone.inspect}" + end + + end + +end diff --git a/test/test_mini_date.rb b/test/test_mini_date.rb deleted file mode 100644 index 3ae441d5..00000000 --- a/test/test_mini_date.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'helper' - -class TestMiniDate < TestCase - def test_valid_month - assert_raises(ArgumentError){ Chronic::MiniDate.new(0,12) } - assert_raises(ArgumentError){ Chronic::MiniDate.new(13,1) } - end - - def test_is_between - m=Chronic::MiniDate.new(3,2) - assert m.is_between?(Chronic::MiniDate.new(2,4), Chronic::MiniDate.new(4,7)) - assert !m.is_between?(Chronic::MiniDate.new(1,5), Chronic::MiniDate.new(2,7)) - - #There was a hang if date tested is in december and outside the testing range - m=Chronic::MiniDate.new(12,24) - assert !m.is_between?(Chronic::MiniDate.new(10,1), Chronic::MiniDate.new(12,21)) - end - - def test_is_between_short_range - m=Chronic::MiniDate.new(5,10) - assert m.is_between?(Chronic::MiniDate.new(5,3), Chronic::MiniDate.new(5,12)) - assert !m.is_between?(Chronic::MiniDate.new(5,11), Chronic::MiniDate.new(5,15)) - end - - def test_is_between_wrapping_range - m=Chronic::MiniDate.new(1,1) - assert m.is_between?(Chronic::MiniDate.new(11,11), Chronic::MiniDate.new(2,2)) - m=Chronic::MiniDate.new(12,12) - assert m.is_between?(Chronic::MiniDate.new(11,11), Chronic::MiniDate.new(1,5)) - end - -end From 7c360a4d90596ecc208888d533cba1a875e0f4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:56:35 +0300 Subject: [PATCH 04/23] Update and add more Tags --- lib/chronic.rb | 9 ++ lib/chronic/tag.rb | 5 - lib/chronic/tags/day_name.rb | 34 ++++++ lib/chronic/tags/day_portion.rb | 33 ++++++ lib/chronic/tags/day_special.rb | 30 ++++++ lib/chronic/tags/keyword.rb | 56 ++++++++++ lib/chronic/tags/month_name.rb | 39 +++++++ lib/chronic/tags/ordinal.rb | 6 +- lib/chronic/tags/pointer.rb | 4 +- lib/chronic/tags/rational.rb | 35 +++++++ lib/chronic/tags/scalar.rb | 20 +++- lib/chronic/tags/season_name.rb | 31 ++++++ lib/chronic/tags/separator.rb | 22 ++-- lib/chronic/tags/time_special.rb | 34 ++++++ lib/chronic/tags/time_zone.rb | 29 +++--- lib/chronic/tags/unit.rb | 173 +++++++++++++++++++++++++++++++ 16 files changed, 526 insertions(+), 34 deletions(-) create mode 100644 lib/chronic/tags/day_name.rb create mode 100644 lib/chronic/tags/day_portion.rb create mode 100644 lib/chronic/tags/day_special.rb create mode 100644 lib/chronic/tags/keyword.rb create mode 100644 lib/chronic/tags/month_name.rb create mode 100644 lib/chronic/tags/rational.rb create mode 100644 lib/chronic/tags/season_name.rb create mode 100644 lib/chronic/tags/time_special.rb create mode 100644 lib/chronic/tags/unit.rb diff --git a/lib/chronic.rb b/lib/chronic.rb index e8c0421d..023d7068 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -17,13 +17,22 @@ require 'chronic/season' require 'chronic/tag' +require 'chronic/tags/day_name' +require 'chronic/tags/day_portion' +require 'chronic/tags/day_special' require 'chronic/tags/grabber' +require 'chronic/tags/keyword' +require 'chronic/tags/month_name' require 'chronic/tags/ordinal' require 'chronic/tags/pointer' +require 'chronic/tags/rational' require 'chronic/tags/scalar' +require 'chronic/tags/season_name' require 'chronic/tags/separator' require 'chronic/tags/sign' +require 'chronic/tags/time_special' require 'chronic/tags/time_zone' +require 'chronic/tags/unit' require 'chronic/tags/repeater' require 'chronic/repeaters/repeater_year' diff --git a/lib/chronic/tag.rb b/lib/chronic/tag.rb index 48835410..7ba20092 100644 --- a/lib/chronic/tag.rb +++ b/lib/chronic/tag.rb @@ -13,11 +13,6 @@ def initialize(type, width = nil, options = {}) @options = options end - # time - Set the start Time for this Tag. - def start=(time) - @now = time - end - class << self # Public: Scan an Array of Token objects. # diff --git a/lib/chronic/tags/day_name.rb b/lib/chronic/tags/day_name.rb new file mode 100644 index 00000000..638b808e --- /dev/null +++ b/lib/chronic/tags/day_name.rb @@ -0,0 +1,34 @@ +module Chronic + class DayName < Tag + + # Scan an Array of Token objects and apply any necessary DayName + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, self, patterns, options) + end + end + + def self.patterns + @@patterns ||= { + /^m[ou]n(day)?$/i => :monday, + /^t(ue|eu|oo|u)s?(day)?$/i => :tuesday, + /^we(d|dnes|nds|nns)(day)?$/i => :wednesday, + /^th(u|ur|urs|ers)(day)?$/i => :thursday, + /^fr[iy](day)?$/i => :friday, + /^sat(t?[ue]rday)?$/i => :saturday, + /^su[nm](day)?$/i => :sunday + } + end + + def to_s + 'dayname-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/day_portion.rb b/lib/chronic/tags/day_portion.rb new file mode 100644 index 00000000..a732aca8 --- /dev/null +++ b/lib/chronic/tags/day_portion.rb @@ -0,0 +1,33 @@ +module Chronic + class DayPortion < Tag + + # Scan an Array of Token objects and apply any necessary DayPortion + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, self, patterns, options) + end + end + + def self.patterns + @@patterns ||= { + /^ams?$/i => :am, + /^pms?$/i => :pm, + 'a.m.' => :am, + 'p.m.' => :pm, + 'a' => :am, + 'p' => :pm + } + end + + def to_s + 'dayportion-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/day_special.rb b/lib/chronic/tags/day_special.rb new file mode 100644 index 00000000..04c52869 --- /dev/null +++ b/lib/chronic/tags/day_special.rb @@ -0,0 +1,30 @@ +module Chronic + class DaySpecial < Tag + + # Scan an Array of Token objects and apply any necessary SeasonName + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, self, patterns, options) + end + end + + def self.patterns + @@patterns ||= { + /^yesterday$/i => :yesterday, + /^today$/i => :today, + /^tomorrow$/i => :tomorrow + } + end + + def to_s + 'dayspecial-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/keyword.rb b/lib/chronic/tags/keyword.rb new file mode 100644 index 00000000..7be3c4b1 --- /dev/null +++ b/lib/chronic/tags/keyword.rb @@ -0,0 +1,56 @@ +module Chronic + class Keyword < Tag + + # Scan an Array of Token objects and apply any necessary Keyword + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, KeywordAt, { /^(at|@)$/i => :at }) + token.tag scan_for(token, KeywordIn, { 'in' => :in }) + token.tag scan_for(token, KeywordOn, { 'on' => :on }) + token.tag scan_for(token, KeywordAnd, { 'and' => :and }) + token.tag scan_for(token, KeywordTo, { 'to' => :to }) + end + end + + def to_s + 'keyword' + end + end + + class KeywordAt < Keyword #:nodoc: + def to_s + super << '-at' + end + end + + class KeywordIn < Keyword #:nodoc: + def to_s + super << '-in' + end + end + + class KeywordOn < Keyword #:nodoc: + def to_s + super << '-on' + end + end + + class KeywordAnd < Keyword #:nodoc: + def to_s + super << '-and' + end + end + + class KeywordTo < Keyword #:nodoc: + def to_s + super << '-to' + end + end + +end diff --git a/lib/chronic/tags/month_name.rb b/lib/chronic/tags/month_name.rb new file mode 100644 index 00000000..85effee7 --- /dev/null +++ b/lib/chronic/tags/month_name.rb @@ -0,0 +1,39 @@ +module Chronic + class MonthName < Tag + + # Scan an Array of Token objects and apply any necessary MonthName + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, self, patterns, options) + end + end + + def self.patterns + @@patterns ||= { + /^jan[:\.]?(uary)?$/i => :january, + /^feb[:\.]?(ruary)?$/i => :february, + /^mar[:\.]?(ch)?$/i => :march, + /^apr[:\.]?(il)?$/i => :april, + /^may$/i => :may, + /^jun[:\.]?e?$/i => :june, + /^jul[:\.]?y?$/i => :july, + /^aug[:\.]?(ust)?$/i => :august, + /^sep[:\.]?(t[:\.]?|tember)?$/i => :september, + /^oct[:\.]?(ober)?$/i => :october, + /^nov[:\.]?(ember)?$/i => :november, + /^dec[:\.]?(ember)?$/i => :december + } + end + + def to_s + 'monthname-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/ordinal.rb b/lib/chronic/tags/ordinal.rb index 33b90a8c..8eff348f 100644 --- a/lib/chronic/tags/ordinal.rb +++ b/lib/chronic/tags/ordinal.rb @@ -10,9 +10,9 @@ class Ordinal < Tag # Returns an Array of tokens. def self.scan(tokens, options) tokens.each_index do |i| - if tokens[i].word =~ /^(\d+)(st|nd|rd|th|\.)$/ - width = $1.length - ordinal = $1.to_i + if tokens[i].word =~ /^\d+$/ and tokens[i + 1] and tokens[i + 1].word =~ /^st|nd|rd|th|\.$/ + width = tokens[i].word.length + ordinal = tokens[i].word.to_i tokens[i].tag(Ordinal.new(ordinal, width)) tokens[i].tag(OrdinalDay.new(ordinal, width)) if Chronic::Date::could_be_day?(ordinal, width) tokens[i].tag(OrdinalMonth.new(ordinal, width)) if Chronic::Date::could_be_month?(ordinal, width) diff --git a/lib/chronic/tags/pointer.rb b/lib/chronic/tags/pointer.rb index a920aaf7..8a1b633b 100644 --- a/lib/chronic/tags/pointer.rb +++ b/lib/chronic/tags/pointer.rb @@ -16,8 +16,8 @@ def self.scan(tokens, options) def self.patterns @@patterns ||= { - 'past' => :past, - /^future|in$/i => :future, + /^ago|before|prior|till|to$/i => :past, + /^future|hence|from|after|past$/i => :future } end diff --git a/lib/chronic/tags/rational.rb b/lib/chronic/tags/rational.rb new file mode 100644 index 00000000..4932a799 --- /dev/null +++ b/lib/chronic/tags/rational.rb @@ -0,0 +1,35 @@ +module Chronic + class Rational < Tag + + # Scan an Array of Token objects and apply any necessary Keyword + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, RationalQuarter, { 'quarter' => Rational(1, 4) }) + token.tag scan_for(token, RationalHalf, { 'half' => Rational(1, 2) }) + end + end + + def to_s + 'rational' + end + end + + class RationalQuarter < Rational #:nodoc: + def to_s + super << '-quarter' + end + end + + class RationalHalf < Rational #:nodoc: + def to_s + super << '-half' + end + end + +end diff --git a/lib/chronic/tags/scalar.rb b/lib/chronic/tags/scalar.rb index 90ce11b9..0a4c0d48 100644 --- a/lib/chronic/tags/scalar.rb +++ b/lib/chronic/tags/scalar.rb @@ -11,9 +11,9 @@ class Scalar < Tag # Returns an Array of tokens. def self.scan(tokens, options) tokens.each_index do |i| - token = tokens[i] - post_token = tokens[i + 1] - if token.word =~ /^\d+$/ + if is_scalar?(tokens, i) + token = tokens[i] + post_token = tokens[i + 1] width = token.word.length scalar = token.word.to_i token.tag(Scalar.new(scalar, width)) @@ -25,6 +25,7 @@ def self.scan(tokens, options) unless post_token and DAY_PORTIONS.include?(post_token.word) token.tag(ScalarDay.new(scalar, width)) if Chronic::Date::could_be_day?(scalar, width) token.tag(ScalarMonth.new(scalar, width)) if Chronic::Date::could_be_month?(scalar, width) + token.tag(ScalarFullYear.new(scalar, width)) if width == 4 and Chronic::Date::could_be_year?(scalar, width) if Chronic::Date::could_be_year?(scalar, width) year = Chronic::Date::make_year(scalar, options[:ambiguous_year_future_bias]) token.tag(ScalarYear.new(year.to_i, width)) @@ -34,6 +35,10 @@ def self.scan(tokens, options) end end + def self.is_scalar?(tokens, i) + tokens[i].word =~ /^\d+$/ and (not tokens[i + 1] or not tokens[i + 1].word =~ /^st|nd|rd|th$/) + end + def to_s 'scalar' end @@ -86,4 +91,11 @@ def to_s super << '-year-' << @type.to_s end end -end \ No newline at end of file + + class ScalarFullYear < Scalar #:nodoc: + def to_s + super << '-fullyear-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/season_name.rb b/lib/chronic/tags/season_name.rb new file mode 100644 index 00000000..11a3117e --- /dev/null +++ b/lib/chronic/tags/season_name.rb @@ -0,0 +1,31 @@ +module Chronic + class SeasonName < Tag + + # Scan an Array of Token objects and apply any necessary SeasonName + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, self, patterns, options) + end + end + + def self.patterns + @@patterns ||= { + /^springs?$/i => :spring, + /^summers?$/i => :summer, + /^(autumn)|(fall)s?$/i => :autumn, + /^winters?$/i => :winter + } + end + + def to_s + 'seasonname-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/separator.rb b/lib/chronic/tags/separator.rb index a8d865dd..f99a721b 100644 --- a/lib/chronic/tags/separator.rb +++ b/lib/chronic/tags/separator.rb @@ -11,17 +11,17 @@ class Separator < Tag def self.scan(tokens, options) tokens.each do |token| token.tag scan_for(token, SeparatorComma, { ','.to_sym => :comma }) - token.tag scan_for(token, SeparatorDot, { '.'.to_sym => :dot }) + token.tag scan_for(token, SeparatorDot, { '.'.to_sym => :dot }) token.tag scan_for(token, SeparatorColon, { ':'.to_sym => :colon }) token.tag scan_for(token, SeparatorSpace, { ' '.to_sym => :space }) token.tag scan_for(token, SeparatorSlash, { '/'.to_sym => :slash }) - token.tag scan_for(token, SeparatorDash, { :- => :dash }) - token.tag scan_for(token, SeparatorAt, { /^(at|@)$/i => :at }) - token.tag scan_for(token, SeparatorIn, { 'in' => :in }) - token.tag scan_for(token, SeparatorOn, { 'on' => :on }) - token.tag scan_for(token, SeparatorAnd, { 'and' => :and }) - token.tag scan_for(token, SeparatorT, { :T => :T }) - token.tag scan_for(token, SeparatorW, { :W => :W }) + token.tag scan_for(token, SeparatorDash, { :- => :dash }) + token.tag scan_for(token, SeparatorAt, { /^(at|@)$/i => :at }) + token.tag scan_for(token, SeparatorIn, { 'in' => :in }) + token.tag scan_for(token, SeparatorOn, { 'on' => :on }) + token.tag scan_for(token, SeparatorAnd, { 'and' => :and }) + token.tag scan_for(token, SeparatorT, { :T => :T }) + token.tag scan_for(token, SeparatorW, { :W => :W }) token.tag scan_for_quote(token) end end @@ -84,6 +84,12 @@ def to_s end end + class SeparatorApostrophe < Separator #:nodoc: + def to_s + super << '-apostrophe-' << @type.to_s + end + end + class SeparatorAt < Separator #:nodoc: def to_s super << '-at' diff --git a/lib/chronic/tags/time_special.rb b/lib/chronic/tags/time_special.rb new file mode 100644 index 00000000..6e473b9f --- /dev/null +++ b/lib/chronic/tags/time_special.rb @@ -0,0 +1,34 @@ +module Chronic + class TimeSpecial < Tag + + # Scan an Array of Token objects and apply any necessary TimeSpecial + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for(token, self, patterns, options) + end + end + + def self.patterns + @@patterns ||= { + 'now' => :now, + 'morning' => :morning, + 'noon' => :noon, + 'afternoon' => :afternoon, + 'evening' => :evening, + /^(to)?(night|nite)$/ => :night, + 'midnight' => :midnight + } + end + + def to_s + 'timespecial-' << @type.to_s + end + end + +end diff --git a/lib/chronic/tags/time_zone.rb b/lib/chronic/tags/time_zone.rb index 26d385ad..1d03f8ef 100644 --- a/lib/chronic/tags/time_zone.rb +++ b/lib/chronic/tags/time_zone.rb @@ -1,7 +1,7 @@ module Chronic - class TimeZone < Tag + class TimeZoneTag < Tag - # Scan an Array of Token objects and apply any necessary TimeZone + # Scan an Array of Token objects and apply any necessary TimeZoneTag # tags to each token. # # tokens - An Array of tokens to scan. @@ -9,24 +9,29 @@ class TimeZone < Tag # # Returns an Array of tokens. def self.scan(tokens, options) - tokens.each do |token| - token.tag scan_for_all(token) + tokens.each_index do |i| + token = tokens[i] + token.tag scan_for(token, TimeZoneGeneric, patterns) end end - # token - The Token object we want to scan. - # - # Returns a new Pointer object. - def self.scan_for_all(token) - scan_for token, self, - { - /[PMCE][DS]T|UTC/i => :tz, - /(tzminus)?\d{2}:?\d{2}/ => :tz + def self.patterns + @@patterns ||= { + :UTC => 0, + :Z => 0 } end def to_s 'timezone' end + end + + class TimeZoneGeneric < TimeZoneTag #:nodoc: + def to_s + super + '-generic-' + @type.to_s + end + end + end diff --git a/lib/chronic/tags/unit.rb b/lib/chronic/tags/unit.rb new file mode 100644 index 00000000..becb954b --- /dev/null +++ b/lib/chronic/tags/unit.rb @@ -0,0 +1,173 @@ +module Chronic + class Unit < Tag + + # Scan an Array of Token objects and apply any necessary Unit + # tags to each token. + # + # tokens - An Array of tokens to scan. + # options - The Hash of options specified in Chronic::parse. + # + # Returns an Array of tokens. + def self.scan(tokens, options) + tokens.each do |token| + token.tag scan_for_units(token, options) + end + end + + # token - The Token object we want to scan. + # + # Returns a new Unit object. + def self.scan_for_units(token, options = {}) + patterns.each do |item, symbol| + if item =~ token.word + klass_name = 'Unit' + symbol.to_s.capitalize + klass = Chronic.const_get(klass_name) + return klass.new(symbol, options) + end + end + return nil + end + + def self.patterns + @@paterns ||= { + /^years?$/i => :year, + /^seasons?$/i => :season, + /^months?$/i => :month, + /^fortnights?$/i => :fortnight, + /^weeks?$/i => :week, + /^weekends?$/i => :weekend, + /^(week|business)days?$/i => :weekday, + /^days?$/i => :day, + /^mornings?$/i => :morning, + /^noons?$/ => :noon, + /^afternoons?$/i => :afternoon, + /^evenings?$/i => :evening, + /^(to)?(night|nite)s?$/i => :night, + /^midnights?$/i => :midnight, + /^(hr|hour)s?$/i => :hour, + /^(min|minute)s?$/i => :minute, + /^(sec|second)s?$/i => :second, + /^(ms|miliseconds?)$/i => :milisecond + } + end + + def to_s + 'unit' + end + end + + class UnitYear < Unit #:nodoc: + def to_s + super << '-year' + end + end + + class UnitSeason < Unit #:nodoc: + def to_s + super << '-season' + end + end + + class UnitQuarter < Unit #:nodoc: + def to_s + super << '-quarter' + end + end + + class UnitMonth < Unit #:nodoc: + def to_s + super << '-month' + end + end + + class UnitFortnight < Unit #:nodoc: + def to_s + super << '-fortnight' + end + end + + class UnitWeek < Unit #:nodoc: + def to_s + super << '-week' + end + end + + class UnitWeekend < Unit #:nodoc: + def to_s + super << '-weekend' + end + end + + class UnitWeekday < Unit #:nodoc: + def to_s + super << '-weekday' + end + end + + class UnitDay < Unit #:nodoc: + def to_s + super << '-day' + end + end + + class UnitMorning < Unit #:nodoc: + def to_s + super << '-morning' + end + end + + class UnitNoon < Unit #:nodoc: + def to_s + super << '-noon' + end + end + + class UnitAfternoon < Unit #:nodoc: + def to_s + super << '-afternoon' + end + end + + class UnitEvening < Unit #:nodoc: + def to_s + super << '-evening' + end + end + + class UnitNight < Unit #:nodoc: + def to_s + super << '-night' + end + end + + class UnitMidnight < Unit #:nodoc: + def to_s + super << '-night' + end + end + + class UnitHour < Unit #:nodoc: + def to_s + super << '-hour' + end + end + + class UnitMinute < Unit #:nodoc: + def to_s + super << '-minute' + end + end + + class UnitSecond < Unit #:nodoc: + def to_s + super << '-second' + end + end + + class UnitMilisecond < Unit #:nodoc: + def to_s + super << '-milisecond' + end + end + +end From ffcca87d06314a967f5288885bd4c8c06faa8636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 00:20:41 +0300 Subject: [PATCH 05/23] Add TimeZone support and other Date/Time related methods --- chronic.gemspec | 2 + lib/chronic.rb | 3 + lib/chronic/date.rb | 102 ++++++++++++++++++++++++++++++++++ lib/chronic/tags/time_zone.rb | 19 +++++++ lib/chronic/time.rb | 58 +++++++++++++++++++ lib/chronic/time_zone.rb | 12 ++++ 6 files changed, 196 insertions(+) diff --git a/chronic.gemspec b/chronic.gemspec index 169ae011..cd7ef727 100644 --- a/chronic.gemspec +++ b/chronic.gemspec @@ -17,6 +17,8 @@ Gem::Specification.new do |s| s.test_files = `git ls-files -- test`.split($/) s.add_runtime_dependency 'numerizer', '~> 0.2' + s.add_runtime_dependency 'tzinfo' + s.add_runtime_dependency 'TimezoneParser' s.add_development_dependency 'rake', '~> 10' s.add_development_dependency 'simplecov', '~> 0' diff --git a/lib/chronic.rb b/lib/chronic.rb index 023d7068..4fd98af3 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -4,6 +4,9 @@ require 'chronic/version' +require 'tzinfo' +require 'timezone_parser' + require 'chronic/parser' require 'chronic/date' require 'chronic/time' diff --git a/lib/chronic/date.rb b/lib/chronic/date.rb index d4ca9cd3..290976f3 100644 --- a/lib/chronic/date.rb +++ b/lib/chronic/date.rb @@ -137,5 +137,107 @@ def self.split(date) [date.year, date.month, date.day] end + def self.calculate_difference(diff, limit, modifier = 1, context = 0) + state = diff <=> 0 + unless modifier.zero? + skew = modifier + skew += (modifier <=> 0) * -1 if diff * modifier > 0 + diff += limit * skew + end + unless context.zero? + if modifier.zero? + diff += limit * context if state * context <= 0 + elsif state.zero? + diff += limit * context if modifier * context < 0 + end + end + diff + end + + # Calculate difference in days between given week day and date day + def self.wday_diff(date, wday, modifier = 1, context = 0) + self.calculate_difference(wday - date.wday, WEEK_DAYS, modifier, context) + end + + # Get day difference for weekday + def self.wd_diff(date, modifier = 1) + diff = modifier + wday = date.wday + if modifier == 0 and wday == DAYS[:sunday] + diff = 1 + elsif modifier == 0 and wday == DAYS[:saturday] + diff = 2 + elsif modifier == 1 and wday == DAYS[:friday] + diff = 3 + elsif modifier == 1 and wday == DAYS[:saturday] + diff = 2 + elsif modifier == -1 and wday == DAYS[:sunday] + diff = -2 + elsif modifier == -1 and wday == DAYS[:monday] + diff = -3 + end + diff + end + + # Calculate difference in months between given month and date month + def self.month_diff(date_month, month, modifier = 1, context = 0) + self.calculate_difference(month - date_month, YEAR_MONTHS, modifier, context) + end + + end + + module DateStructure + attr_accessor :year + attr_accessor :month + attr_accessor :day + attr_accessor :have_year + attr_accessor :have_month + attr_accessor :have_day + attr_reader :precision + def update(date) + @year = date.year + @month = date.month + @day = date.day + self + end + + def is_equal?(date) + @year == date.year && + @month == date.month && + @day == date.day + end + + def add_day(amount = 1) + @year, @month, @day = Date::add_day(@year, @month, @day, amount) + end + + def to_a + [@year, @month, @day] + end + + def get_end + [@year, @month, @day] + end + + def to_span(time = nil, timezone = nil) + hour = minute = second = 0 + hour, minute, second = time.to_a if time + span_start = Chronic.construct(@year, @month, @day, hour, minute, second, timezone) + end_year, end_month, end_day = get_end + span_end = Chronic.construct(end_year, end_month, end_day, 0, 0, 0, timezone) + span = Span.new(span_start, span_end, true) + span.precision = @precision + span.precision = time.precision if time and time.precision + span + end end + + class DateInfo + include DateStructure + def initialize(now = nil) + @now = now ? now : Chronic.time_class.now + update(@now) + end + end + end diff --git a/lib/chronic/tags/time_zone.rb b/lib/chronic/tags/time_zone.rb index 1d03f8ef..7e0d4b9b 100644 --- a/lib/chronic/tags/time_zone.rb +++ b/lib/chronic/tags/time_zone.rb @@ -12,6 +12,7 @@ def self.scan(tokens, options) tokens.each_index do |i| token = tokens[i] token.tag scan_for(token, TimeZoneGeneric, patterns) + token.tag TimeZoneAbbreviation.new(token.word, nil, options) if TimeZone::is_valid_abbr?(token.word) end end @@ -34,4 +35,22 @@ def to_s end end + class TimeZoneAbbreviation < TimeZoneTag #:nodoc: + def to_s + super + '-abbr-' + @type.to_s + end + end + + class TimeZoneName < TimeZoneTag #:nodoc: + def to_s + super + '-name-' + @type.to_s + end + end + + class TimeZoneTZ < TimeZoneTag #:nodoc: + def to_s + super + '-tz-' + @type.to_s + end + end + end diff --git a/lib/chronic/time.rb b/lib/chronic/time.rb index 2d14b0f0..3e9fd31f 100644 --- a/lib/chronic/time.rb +++ b/lib/chronic/time.rb @@ -80,4 +80,62 @@ def self.split(time) end end + module TimeStructure + include Comparable + attr_accessor :hour + attr_accessor :minute + attr_accessor :second + attr_reader :subsecond + attr_reader :subsecond_size + attr_reader :precision + def update(time) + @hour = time.hour + @minute = time.min + @second = time.sec + self + end + + def is_equal?(time) + @hour == time.hour && + @minute == time.min && + @second == time.sec + end + + def <=> (time) + to_f <=> time.hour * Time::HOUR_SECONDS + time.min * Time::MINUTE_SECONDS + time.sec + end + + def to_a + [@hour, @minute, @second] + end + + def to_f + @hour * Time::HOUR_SECONDS + @minute * Time::MINUTE_SECONDS + @second + end + + def get_end + [@hour, @minute, @second] + end + + def to_span(date, timezone = nil) + span_start = Chronic.construct(date.year, date.month, date.day, @hour, @minute, @second, timezone) + end_hour, end_minute, end_second = get_end + span_end = Chronic.construct(date.year, date.month, date.day, end_hour, end_minute, end_second, timezone) + Span.new(span_start, span_end, true) + end + end + + class TimeInfo + include TimeStructure + def initialize(now = nil) + if now + update(now) + else + @hour = 0 + @minute = 0 + @second = 0 + end + end + end + end diff --git a/lib/chronic/time_zone.rb b/lib/chronic/time_zone.rb index 590edcf4..816d2609 100644 --- a/lib/chronic/time_zone.rb +++ b/lib/chronic/time_zone.rb @@ -24,6 +24,18 @@ def self.zone_offsets(zone) TZInfo::Timezone.get(@zone).current_period.utc_offset end + def self.is_valid_abbr?(abbr) + TimezoneParser::Abbreviation.isValid?(abbr) + end + + def self.is_valid_name?(name) + TimezoneParser::Timezone.isValid?(name) + end + + def self.is_valid_zone?(name) + false # TODO + end + def self.to_offset(hour, minute, sign = 1) offset = hour * Time::HOUR_SECONDS + minute * Time::MINUTE_SECONDS offset *= sign From d720b64b847630d044d39fac1b20af34a6c552c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 02:51:57 +0300 Subject: [PATCH 06/23] Rewrite handler parsing and tagging --- lib/chronic.rb | 2 +- lib/chronic/definition.rb | 166 +++++--- lib/chronic/dictionary.rb | 4 +- lib/chronic/handler.rb | 105 ++--- lib/chronic/handlers.rb | 672 ------------------------------ lib/chronic/handlers/anchor.rb | 57 +++ lib/chronic/handlers/arrow.rb | 76 ++++ lib/chronic/handlers/date.rb | 263 ++++++++++++ lib/chronic/handlers/date_time.rb | 72 ++++ lib/chronic/handlers/general.rb | 119 ++++++ lib/chronic/handlers/narrow.rb | 54 +++ lib/chronic/handlers/time.rb | 167 ++++++++ lib/chronic/handlers/time_zone.rb | 50 +++ lib/chronic/parser.rb | 60 +-- lib/chronic/tokenizer.rb | 12 +- 15 files changed, 1026 insertions(+), 853 deletions(-) delete mode 100644 lib/chronic/handlers.rb create mode 100644 lib/chronic/handlers/anchor.rb create mode 100644 lib/chronic/handlers/arrow.rb create mode 100644 lib/chronic/handlers/date.rb create mode 100644 lib/chronic/handlers/date_time.rb create mode 100644 lib/chronic/handlers/general.rb create mode 100644 lib/chronic/handlers/narrow.rb create mode 100644 lib/chronic/handlers/time.rb create mode 100644 lib/chronic/handlers/time_zone.rb diff --git a/lib/chronic.rb b/lib/chronic.rb index 4fd98af3..d2227c31 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -13,9 +13,9 @@ require 'chronic/time_zone' require 'chronic/handler' -require 'chronic/handlers' require 'chronic/span' require 'chronic/token' +require 'chronic/token_group' require 'chronic/tokenizer' require 'chronic/season' diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index 4f001b35..0dbb2909 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -1,5 +1,3 @@ -require 'chronic/handlers' - module Chronic # SpanDefinitions subclasses return definitions constructed by Handler instances (see handler.rb) # SpanDefinitions subclasses follow a + Definitions naming pattern @@ -17,52 +15,101 @@ def definitions end class SpanDefinitions < Definitions - include Handlers end + # Match only Time class TimeDefinitions < SpanDefinitions def definitions [ - Handler.new([:repeater_time, :repeater_day_portion?], nil) + [[SeparatorSpace, TimeSpecial, SeparatorSpace, [KeywordAt, :optional], [SeparatorSpace, :optional], ScalarHour, SeparatorColon, ScalarMinute], :handle_ts_h_m], + [[SeparatorSpace, TimeSpecial, SeparatorSpace, [KeywordAt, :optional], [SeparatorSpace, :optional], ScalarHour], :handle_ts_h], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, [SeparatorDot, SeparatorColon], ScalarSubsecond], :handle_h_m_s_ss], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, SeparatorColon, ScalarMinute, [SeparatorColon, SeparatorDot], ScalarSecond, [SeparatorSpace, :optional], DayPortion], :handle_h_m_s_dp], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, SeparatorColon, ScalarMinute, [SeparatorColon, SeparatorDot], ScalarSecond], :handle_h_m_s], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, [SeparatorColon, SeparatorDot], ScalarMinute, SeparatorSpace, [KeywordIn, KeywordAt], SeparatorSpace, TimeSpecial], :handle_h_m_ts], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, [SeparatorColon, SeparatorDot], ScalarMinute, [SeparatorSpace, :optional], DayPortion], :handle_h_m_dp], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, [SeparatorColon, SeparatorDot], ScalarMinute], :handle_h_m], + [[[KeywordAt, :optional], SeparatorSpace, ScalarWide, [SeparatorSpace, :optional], DayPortion], :handle_hhmm_dp], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, SeparatorSpace, KeywordIn, SeparatorSpace, TimeSpecial], :handle_h_ts], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, SeparatorSpace, [KeywordAt, :optional], [SeparatorSpace, :optional], TimeSpecial], :handle_h_ts], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, [SeparatorSpace, :optional], DayPortion], :handle_h_dp], + [[[KeywordAt, :optional], SeparatorSpace, ScalarWide, SeparatorSpace], :handle_hhmm], + [[[KeywordAt, :optional], SeparatorSpace, TimeSpecial], :handle_ts], + [[[KeywordAt, :optional], SeparatorSpace, ScalarHour, SeparatorSpace], :handle_h] ] end end + # Match only Date class DateDefinitions < SpanDefinitions def definitions [ - Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, [:separator_slash?, :separator_dash?], :time_zone, :scalar_year], :handle_generic), - Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd), - Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :scalar_year], :handle_rdn_rmn_sd_sy), - Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day], :handle_rdn_rmn_od), - Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day, :scalar_year], :handle_rdn_rmn_od_sy), - Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rdn_rmn_sd), - Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_rmn_od), - Handler.new([:repeater_day_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_od), - Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_day, :repeater_time, :time_zone], :handle_generic), - Handler.new([:ordinal_day], :handle_generic), - Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy), - Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy), - Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy), - Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy), - Handler.new([:repeater_month_name, [:separator_slash?, :separator_dash?], :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd), - Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on), - Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od), - Handler.new([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy), - Handler.new([:ordinal_day, :repeater_month_name, :separator_at?, 'time?'], :handle_od_rmn), - Handler.new([:ordinal_day, :grabber?, :repeater_month, :separator_at?, 'time?'], :handle_od_rm), - Handler.new([:scalar_year, :repeater_month_name, :ordinal_day], :handle_sy_rmn_od), - Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on), - Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy), - Handler.new([:repeater_quarter_name, :scalar_year], :handle_rqn_sy), - Handler.new([:scalar_year, :repeater_quarter_name], :handle_sy_rqn), - Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy), - Handler.new([:scalar_day, [:separator_slash?, :separator_dash?], :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn), - Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd), - Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month], :handle_sy_sm), - Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_year], :handle_sm_sy), - Handler.new([:scalar_day, [:separator_slash, :separator_dash], :repeater_month_name, [:separator_slash, :separator_dash], :scalar_year, :repeater_time?], :handle_sm_rmn_sy), - Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar?, :time_zone], :handle_generic), + [[DayName, SeparatorSpace, MonthName, SeparatorSpace, OrdinalDay, SeparatorSpace, ScalarYear], :handle_dn_mn_od_sy], + [[DayName, SeparatorSpace, MonthName, SeparatorSpace, ScalarDay, SeparatorSpace, ScalarYear], :handle_dn_mn_sd_sy], + [[DayName, SeparatorSpace, MonthName, SeparatorSpace, OrdinalDay], :handle_dn_mn_od], + [[DayName, SeparatorSpace, MonthName, SeparatorSpace, ScalarDay], :handle_dn_mn_sd], + [[MonthName, SeparatorSpace, OrdinalDay, [SeparatorComma, SeparatorSpace], [SeparatorSpace, :optional], ScalarYear], :handle_mn_od_sy], + [[MonthName, SeparatorSpace, ScalarDay, [SeparatorComma, :optional], [SeparatorSpace, :optional], ScalarYear], :handle_mn_sd_sy], + [[ScalarYear, SeparatorSpace, MonthName, SeparatorSpace, OrdinalDay], :handle_sy_mn_od], + [[ScalarYear, SeparatorSpace, MonthName, SeparatorSpace, ScalarDay], :handle_sy_mn_sd], + [[ScalarYear, SeparatorSlash, ScalarMonth, SeparatorSlash, ScalarDay], :handle_sy_sm_sd], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay], :handle_sy_sm_sd], + [[ScalarYear, SeparatorDot, ScalarMonth, SeparatorDot, ScalarDay], :handle_sy_sm_sd], + [[ScalarYear, SeparatorColon, ScalarMonth, SeparatorColon, ScalarDay], :handle_sy_sm_sd], + [[OrdinalDay, SeparatorSpace, MonthName, SeparatorSpace, ScalarYear], :handle_od_mn_sy], + [[ScalarDay, SeparatorDash, MonthName, SeparatorDash, ScalarYear], :handle_sd_mn_sy], + [[ScalarDay, SeparatorSpace, MonthName, SeparatorSpace, ScalarYear], :handle_sd_mn_sy], + [[ScalarDay, SeparatorDot, ScalarMonth, SeparatorDot, ScalarYear], :handle_sd_sm_sy] + ] + end + end + + # Match only Date in short form + class ShortDateDefinitions < SpanDefinitions + def definitions + [ + [[DayName, SeparatorSpace, OrdinalDay], :handle_dn_od], + [[MonthName, SeparatorSpace, OrdinalDay], :handle_mn_od], + [[MonthName, [SeparatorSpace, SeparatorDash], ScalarDay, [SeparatorSpace, Unit, :none]], :handle_mn_sd], + [[MonthName, SeparatorSpace, ScalarYear], :handle_mn_sy], + [[ScalarYear, [SeparatorDash, SeparatorSlash], ScalarMonth], :handle_sy_sm], + [[ScalarFullYear, SeparatorSpace, MonthName], :handle_sy_mn], + [[OrdinalDay, SeparatorSpace, MonthName], :handle_od_mn], + [[ScalarDay, [SeparatorSpace, SeparatorDash, :optional], MonthName], :handle_sd_mn], + [[KeywordIn, SeparatorSpace, MonthName], :handle_mn], + [[DaySpecial], :handle_ds], + [[MonthName], :handle_mn], + [[DayName], :handle_dn], + [[SeparatorSpace, ScalarYear, SeparatorSpace], :handle_sy], + [[OrdinalDay], :handle_od], + [[ScalarFullYear], :handle_sy] + ] + end + end + + # Match only TimeZone + class TimezoneDefinitions < SpanDefinitions + def definitions + [ + [[[Sign, :optional], ScalarHour, SeparatorColon, ScalarMinute], :handle_hh_mm], + [[Sign, ScalarWide], :handle_hhmm], + [[TimeZoneAbbreviation], :handle_abbr], + [[TimeZoneGeneric], :handle_generic] + ] + end + end + + # Match full Date+Time, can be with TimeZone + class DateTimeDefinitions < SpanDefinitions + def definitions + [ + [[DayName, SeparatorSpace, MonthName, SeparatorSpace, ScalarDay, SeparatorSpace, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, SeparatorSpace, TimeZoneAbbreviation, SeparatorSpace, ScalarYear], :handle_dn_mn_sd_h_m_s_abbr_sy], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay, SeparatorT, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, [SeparatorDot, SeparatorColon], ScalarSubsecond, Sign, ScalarHour, SeparatorColon, ScalarMinute], :handle_sy_sm_sd_h_m_s_ss_hh_mm], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay, SeparatorT, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, Sign, ScalarHour, SeparatorColon, ScalarMinute], :handle_sy_sm_sd_h_m_s_hh_mm], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay, SeparatorT, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, [SeparatorDot, SeparatorColon], ScalarSubsecond, TimeZoneGeneric], :handle_sy_sm_sd_h_m_s_ss_tz], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay, SeparatorT, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, [SeparatorDot, SeparatorColon], ScalarSubsecond], :handle_sy_sm_sd_h_m_s_ss], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay, SeparatorT, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond, TimeZoneGeneric], :handle_sy_sm_sd_h_m_s_tz], + [[ScalarYear, SeparatorDash, ScalarMonth, SeparatorDash, ScalarDay, SeparatorT, ScalarHour, SeparatorColon, ScalarMinute, SeparatorColon, ScalarSecond], :handle_sy_sm_sd_h_m_s] ] end end @@ -70,9 +117,14 @@ def definitions class AnchorDefinitions < SpanDefinitions def definitions [ - Handler.new([:separator_on?, :grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r), - Handler.new([:grabber?, :repeater, :repeater, :separator?, :repeater?, :repeater?], :handle_r), - Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r) + [[Grabber, SeparatorSpace, DayName], :handle_gr_dn], + [[Grabber, SeparatorSpace, MonthName], :handle_gr_mn], + [[Grabber, SeparatorSpace, SeasonName], :handle_gr_sn], + [[Grabber, SeparatorSpace, TimeSpecial], :handle_gr_ts], + [[Grabber, SeparatorSpace, Unit], :handle_gr_u], + [[KeywordIn, SeparatorSpace, Scalar, SeparatorSpace, Unit], :handle_in_s_u], + [[DaySpecial], :handle_ds], + [[Unit], :handle_u] ] end end @@ -80,11 +132,12 @@ def definitions class ArrowDefinitions < SpanDefinitions def definitions [ - Handler.new([:repeater_month_name, :scalar, :repeater, :pointer], :handle_rmn_s_r_p), - Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p), - Handler.new([:scalar, :repeater, :separator_and?, :scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_a_s_r_p_a), - Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r), - Handler.new([:scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_p_a) + [[Pointer, [SeparatorSpace, :optional], Scalar, [SeparatorSpace, :optional], Unit], :handle_p_s_u], + [[Scalar, [SeparatorSpace, :optional], DayName, SeparatorSpace, Pointer], :handle_s_dn_p], + [[Rational, SeparatorSpace, Pointer], :handle_r_p], + [[Unit, SeparatorSpace, Pointer], :handle_u_p], + [[Scalar, [SeparatorSpace, :optional], Unit], :handle_s_u], + [[Scalar, [SeparatorSpace, :optional], Pointer], :handle_s_p], ] end end @@ -92,12 +145,14 @@ def definitions class NarrowDefinitions < SpanDefinitions def definitions [ - Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r), - Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r) + [[Grabber, SeparatorSpace, Ordinal, SeparatorSpace, Unit], :handle_gr_o_u], + [[Ordinal, SeparatorSpace, Unit], :handle_o_u], + [[Ordinal, SeparatorSpace, DayName], :handle_o_dn], ] end end + # Date depending on endianess class EndianDefinitions < SpanDefinitions def definitions prefered_endian @@ -106,19 +161,22 @@ def definitions def prefered_endian options[:endian_precedence] ||= [:middle, :little] - definitions = [ - Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy), - Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, :separator_at?, 'time?'], :handle_sm_sd), - Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, :separator_at?, 'time?'], :handle_sd_sm), - Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy), - Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy) + middle = [ + [[ScalarMonth, SeparatorSlash, ScalarDay, SeparatorSlash, ScalarYear], :handle_sm_sd_sy], + [[ScalarMonth, SeparatorDash, ScalarDay, SeparatorDash, ScalarYear], :handle_sm_sd_sy], + [[ScalarMonth, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarDay], :handle_sm_sd] + ] + little = [ + [[ScalarDay, SeparatorDash, ScalarMonth, SeparatorDash, ScalarYear], :handle_sd_sm_sy], + [[ScalarDay, SeparatorSlash, ScalarMonth, SeparatorSlash, ScalarYear], :handle_sd_sm_sy], + [[ScalarDay, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarMonth], :handle_sd_sm] ] case endian = Array(options[:endian_precedence]).first when :little - definitions.reverse + little + middle when :middle - definitions + middle + little else raise ArgumentError, "Unknown endian option '#{endian}'" end diff --git a/lib/chronic/dictionary.rb b/lib/chronic/dictionary.rb index 18867a02..e4ef7981 100644 --- a/lib/chronic/dictionary.rb +++ b/lib/chronic/dictionary.rb @@ -13,7 +13,7 @@ def initialize(options = {}) # returns a hash of each word's Definitions def definitions defined_items.each_with_object({}) do |word, defs| - word_type = "#{word.capitalize.to_s + 'Definitions'}" + word_type = "#{word.to_s.split('_').map(&:capitalize).join + 'Definitions'}" defs[word] = Chronic.const_get(word_type).new(options).definitions end end @@ -23,7 +23,7 @@ class SpanDictionary < Dictionary # Collection of SpanDefinitions def initialize(options = {}) super - @defined_items = [:time,:date,:anchor,:arrow,:narrow,:endian] + @defined_items = [:time, :date, :short_date, :timezone, :date_time, :anchor, :arrow, :narrow, :endian] end # returns the definitions of a specific subclass of SpanDefinitions diff --git a/lib/chronic/handler.rb b/lib/chronic/handler.rb index 200f6087..9321681d 100644 --- a/lib/chronic/handler.rb +++ b/lib/chronic/handler.rb @@ -1,96 +1,43 @@ module Chronic class Handler - - attr_reader :pattern - - attr_reader :handler_method - - # pattern - An Array of patterns to match tokens against. - # handler_method - A Symbol representing the method to be invoked - # when a pattern matches. - def initialize(pattern, handler_method) - @pattern = pattern - @handler_method = handler_method - end - # tokens - An Array of tokens to process. - # definitions - A Hash of definitions to check against. + # token_index - Index from which token to start matching from. + # pattern - An Array of patterns to check against. (eg. [ScalarDay, [SeparatorSpace, SeparatorDash, nil], MonthName]) # # Returns true if a match is found. - def match(tokens, definitions) - token_index = 0 - @pattern.each do |elements| - was_optional = false + def self.match(tokens, token_index, pattern) + count = 0 + index = 0 + pattern.each do |elements| elements = [elements] unless elements.is_a?(Array) - + optional = elements.include?(:optional) + dont_match = elements.include?(:none) + count += 1 + match = 0 elements.each_index do |i| - name = elements[i].to_s - optional = name[-1, 1] == '?' - name = name.chop if optional - - case elements[i] - when Symbol - if tags_match?(name, tokens, token_index) - token_index += 1 - break - else - if optional - was_optional = true - next - elsif i + 1 < elements.count - next - else - return false unless was_optional - end - end - - when String - return true if optional && token_index == tokens.size - - if definitions.key?(name.to_sym) - sub_handlers = definitions[name.to_sym] + if elements[i].is_a?(Class) and self.tags_match?(elements[i], tokens, token_index + index) + match += 1 + index += 1 + break unless dont_match + elsif i + 1 < elements.count + next + else + if optional or (dont_match and match < elements.count - 1) + count -= 1 + index -= match else - raise "Invalid subset #{name} specified" - end - - sub_handlers.each do |sub_handler| - return true if sub_handler.match(tokens[token_index..tokens.size], definitions) + return false end - else - raise "Invalid match type: #{elements[i].class}" end end - end - - return false if token_index != tokens.size - return true - end - - def invoke(type, tokens, parser, options) - if Chronic.debug - puts "-#{type}" - puts "Handler: #{@handler_method}" - end - - parser.send(@handler_method, tokens, options) + return false if index != count + true end - # other - The other Handler object to compare. - # - # Returns true if these Handlers match. - def ==(other) - @pattern == other.pattern - end - - private - - def tags_match?(name, tokens, token_index) - klass = Chronic.const_get(name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }) - - if tokens[token_index] - !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty? - end + def self.tags_match?(klass, tokens, token_index) + return !tokens[token_index].get_tag(klass).nil? if tokens[token_index] + false end end diff --git a/lib/chronic/handlers.rb b/lib/chronic/handlers.rb deleted file mode 100644 index 27795a7d..00000000 --- a/lib/chronic/handlers.rb +++ /dev/null @@ -1,672 +0,0 @@ -module Chronic - module Handlers - module_function - - # Handle month/day - def handle_m_d(month, day, time_tokens, options) - month.start = self.now - span = month.this(options[:context]) - year, month = span.begin.year, span.begin.month - day_start = Chronic.time_class.local(year, month, day) - day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now - - day_or_time(day_start, time_tokens, options) - end - - # Handle repeater-month-name/scalar-day - def handle_rmn_sd(tokens, options) - month = tokens[0].get_tag(RepeaterMonthName) - day = tokens[1].get_tag(ScalarDay).type - - return if month_overflow?(self.now.year, month.index, day) - - handle_m_d(month, day, tokens[2..tokens.size], options) - end - - # Handle repeater-month-name/scalar-day with separator-on - def handle_rmn_sd_on(tokens, options) - if tokens.size > 3 - month = tokens[2].get_tag(RepeaterMonthName) - day = tokens[3].get_tag(ScalarDay).type - token_range = 0..1 - else - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[2].get_tag(ScalarDay).type - token_range = 0..0 - end - - return if month_overflow?(self.now.year, month.index, day) - - handle_m_d(month, day, tokens[token_range], options) - end - - # Handle repeater-month-name/ordinal-day - def handle_rmn_od(tokens, options) - month = tokens[0].get_tag(RepeaterMonthName) - day = tokens[1].get_tag(OrdinalDay).type - - return if month_overflow?(self.now.year, month.index, day) - - handle_m_d(month, day, tokens[2..tokens.size], options) - end - - # Handle ordinal this month - def handle_od_rm(tokens, options) - day = tokens[0].get_tag(OrdinalDay).type - month = tokens[2].get_tag(RepeaterMonth) - handle_m_d(month, day, tokens[3..tokens.size], options) - end - - # Handle ordinal-day/repeater-month-name - def handle_od_rmn(tokens, options) - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[0].get_tag(OrdinalDay).type - - return if month_overflow?(self.now.year, month.index, day) - - handle_m_d(month, day, tokens[2..tokens.size], options) - end - - def handle_sy_rmn_od(tokens, options) - year = tokens[0].get_tag(ScalarYear).type - month = tokens[1].get_tag(RepeaterMonthName).index - day = tokens[2].get_tag(OrdinalDay).type - time_tokens = tokens.last(tokens.size - 3) - - return if month_overflow?(year, month, day) - - begin - day_start = Chronic.time_class.local(year, month, day) - day_or_time(day_start, time_tokens, options) - rescue ArgumentError - nil - end - end - - # Handle scalar-day/repeater-month-name - def handle_sd_rmn(tokens, options) - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[0].get_tag(ScalarDay).type - - return if month_overflow?(self.now.year, month.index, day) - - handle_m_d(month, day, tokens[2..tokens.size], options) - end - - # Handle repeater-month-name/ordinal-day with separator-on - def handle_rmn_od_on(tokens, options) - if tokens.size > 3 - month = tokens[2].get_tag(RepeaterMonthName) - day = tokens[3].get_tag(OrdinalDay).type - token_range = 0..1 - else - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[2].get_tag(OrdinalDay).type - token_range = 0..0 - end - - return if month_overflow?(self.now.year, month.index, day) - - handle_m_d(month, day, tokens[token_range], options) - end - - # Handle scalar-year/repeater-quarter-name - def handle_sy_rqn(tokens, options) - handle_rqn_sy(tokens[0..1].reverse, options) - end - - # Handle repeater-quarter-name/scalar-year - def handle_rqn_sy(tokens, options) - year = tokens[1].get_tag(ScalarYear).type - quarter_tag = tokens[0].get_tag(RepeaterQuarterName) - quarter_tag.start = Chronic.construct(year) - quarter_tag.this(:none) - end - - # Handle repeater-month-name/scalar-year - def handle_rmn_sy(tokens, options) - month = tokens[0].get_tag(RepeaterMonthName).index - year = tokens[1].get_tag(ScalarYear).type - - if month == 12 - next_month_year = year + 1 - next_month_month = 1 - else - next_month_year = year - next_month_month = month + 1 - end - - begin - end_time = Chronic.time_class.local(next_month_year, next_month_month) - Span.new(Chronic.time_class.local(year, month), end_time) - rescue ArgumentError - nil - end - end - - # Handle generic timestamp (ruby 1.8) - def handle_generic(tokens, options) - t = Chronic.time_class.parse(options[:text]) - Span.new(t, t + 1) - rescue ArgumentError => e - raise e unless e.message =~ /out of range/ - end - - # Handle repeater-month-name/scalar-day/scalar-year - def handle_rmn_sd_sy(tokens, options) - month = tokens[0].get_tag(RepeaterMonthName).index - day = tokens[1].get_tag(ScalarDay).type - year = tokens[2].get_tag(ScalarYear).type - time_tokens = tokens.last(tokens.size - 3) - - return if month_overflow?(year, month, day) - - begin - day_start = Chronic.time_class.local(year, month, day) - day_or_time(day_start, time_tokens, options) - rescue ArgumentError - nil - end - end - - # Handle repeater-month-name/ordinal-day/scalar-year - def handle_rmn_od_sy(tokens, options) - month = tokens[0].get_tag(RepeaterMonthName).index - day = tokens[1].get_tag(OrdinalDay).type - year = tokens[2].get_tag(ScalarYear).type - time_tokens = tokens.last(tokens.size - 3) - - return if month_overflow?(year, month, day) - - begin - day_start = Chronic.time_class.local(year, month, day) - day_or_time(day_start, time_tokens, options) - rescue ArgumentError - nil - end - end - - # Handle oridinal-day/repeater-month-name/scalar-year - def handle_od_rmn_sy(tokens, options) - day = tokens[0].get_tag(OrdinalDay).type - month = tokens[1].get_tag(RepeaterMonthName).index - year = tokens[2].get_tag(ScalarYear).type - time_tokens = tokens.last(tokens.size - 3) - - return if month_overflow?(year, month, day) - - begin - day_start = Chronic.time_class.local(year, month, day) - day_or_time(day_start, time_tokens, options) - rescue ArgumentError - nil - end - end - - # Handle scalar-day/repeater-month-name/scalar-year - def handle_sd_rmn_sy(tokens, options) - new_tokens = [tokens[1], tokens[0], tokens[2]] - time_tokens = tokens.last(tokens.size - 3) - handle_rmn_sd_sy(new_tokens + time_tokens, options) - end - - # Handle scalar-month/scalar-day/scalar-year (endian middle) - def handle_sm_sd_sy(tokens, options) - month = tokens[0].get_tag(ScalarMonth).type - day = tokens[1].get_tag(ScalarDay).type - year = tokens[2].get_tag(ScalarYear).type - time_tokens = tokens.last(tokens.size - 3) - - return if month_overflow?(year, month, day) - - begin - day_start = Chronic.time_class.local(year, month, day) - day_or_time(day_start, time_tokens, options) - rescue ArgumentError - nil - end - end - - # Handle scalar-day/scalar-month/scalar-year (endian little) - def handle_sd_sm_sy(tokens, options) - new_tokens = [tokens[1], tokens[0], tokens[2]] - time_tokens = tokens.last(tokens.size - 3) - handle_sm_sd_sy(new_tokens + time_tokens, options) - end - - # Handle scalar-year/scalar-month/scalar-day - def handle_sy_sm_sd(tokens, options) - new_tokens = [tokens[1], tokens[2], tokens[0]] - time_tokens = tokens.last(tokens.size - 3) - handle_sm_sd_sy(new_tokens + time_tokens, options) - end - - # Handle scalar-month/scalar-day - def handle_sm_sd(tokens, options) - month = tokens[0].get_tag(ScalarMonth).type - day = tokens[1].get_tag(ScalarDay).type - year = self.now.year - time_tokens = tokens.last(tokens.size - 2) - - return if month_overflow?(year, month, day) - - begin - day_start = Chronic.time_class.local(year, month, day) - - if options[:context] == :future && day_start < now - day_start = Chronic.time_class.local(year + 1, month, day) - elsif options[:context] == :past && day_start > now - day_start = Chronic.time_class.local(year - 1, month, day) - end - - day_or_time(day_start, time_tokens, options) - rescue ArgumentError - nil - end - end - - # Handle scalar-day/scalar-month - def handle_sd_sm(tokens, options) - new_tokens = [tokens[1], tokens[0]] - time_tokens = tokens.last(tokens.size - 2) - handle_sm_sd(new_tokens + time_tokens, options) - end - - def handle_year_and_month(year, month) - if month == 12 - next_month_year = year + 1 - next_month_month = 1 - else - next_month_year = year - next_month_month = month + 1 - end - - begin - end_time = Chronic.time_class.local(next_month_year, next_month_month) - Span.new(Chronic.time_class.local(year, month), end_time) - rescue ArgumentError - nil - end - end - - # Handle scalar-month/scalar-year - def handle_sm_sy(tokens, options) - month = tokens[0].get_tag(ScalarMonth).type - year = tokens[1].get_tag(ScalarYear).type - handle_year_and_month(year, month) - end - - # Handle scalar-year/scalar-month - def handle_sy_sm(tokens, options) - year = tokens[0].get_tag(ScalarYear).type - month = tokens[1].get_tag(ScalarMonth).type - handle_year_and_month(year, month) - end - - # Handle RepeaterDayName RepeaterMonthName OrdinalDay - def handle_rdn_rmn_od(tokens, options) - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[2].get_tag(OrdinalDay).type - time_tokens = tokens.last(tokens.size - 3) - year = self.now.year - - return if month_overflow?(year, month.index, day) - - begin - if time_tokens.empty? - start_time = Chronic.time_class.local(year, month.index, day) - end_time = time_with_rollover(year, month.index, day + 1) - Span.new(start_time, end_time) - else - day_start = Chronic.time_class.local(year, month.index, day) - day_or_time(day_start, time_tokens, options) - end - rescue ArgumentError - nil - end - end - - # Handle RepeaterDayName RepeaterMonthName OrdinalDay ScalarYear - def handle_rdn_rmn_od_sy(tokens, options) - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[2].get_tag(OrdinalDay).type - year = tokens[3].get_tag(ScalarYear).type - - return if month_overflow?(year, month.index, day) - - begin - start_time = Chronic.time_class.local(year, month.index, day) - end_time = time_with_rollover(year, month.index, day + 1) - Span.new(start_time, end_time) - rescue ArgumentError - nil - end - end - - # Handle RepeaterDayName OrdinalDay - def handle_rdn_od(tokens, options) - day = tokens[1].get_tag(OrdinalDay).type - time_tokens = tokens.last(tokens.size - 2) - year = self.now.year - month = self.now.month - if options[:context] == :future - self.now.day > day ? month += 1 : month - end - - return if month_overflow?(year, month, day) - - begin - if time_tokens.empty? - start_time = Chronic.time_class.local(year, month, day) - end_time = time_with_rollover(year, month, day + 1) - Span.new(start_time, end_time) - else - day_start = Chronic.time_class.local(year, month, day) - day_or_time(day_start, time_tokens, options) - end - rescue ArgumentError - nil - end - end - - # Handle RepeaterDayName RepeaterMonthName ScalarDay - def handle_rdn_rmn_sd(tokens, options) - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[2].get_tag(ScalarDay).type - time_tokens = tokens.last(tokens.size - 3) - year = self.now.year - - return if month_overflow?(year, month.index, day) - - begin - if time_tokens.empty? - start_time = Chronic.time_class.local(year, month.index, day) - end_time = time_with_rollover(year, month.index, day + 1) - Span.new(start_time, end_time) - else - day_start = Chronic.time_class.local(year, month.index, day) - day_or_time(day_start, time_tokens, options) - end - rescue ArgumentError - nil - end - end - - # Handle RepeaterDayName RepeaterMonthName ScalarDay ScalarYear - def handle_rdn_rmn_sd_sy(tokens, options) - month = tokens[1].get_tag(RepeaterMonthName) - day = tokens[2].get_tag(ScalarDay).type - year = tokens[3].get_tag(ScalarYear).type - - return if month_overflow?(year, month.index, day) - - begin - start_time = Chronic.time_class.local(year, month.index, day) - end_time = time_with_rollover(year, month.index, day + 1) - Span.new(start_time, end_time) - rescue ArgumentError - nil - end - end - - def handle_sm_rmn_sy(tokens, options) - day = tokens[0].get_tag(ScalarDay).type - month = tokens[1].get_tag(RepeaterMonthName).index - year = tokens[2].get_tag(ScalarYear).type - if tokens.size > 3 - time = get_anchor([tokens.last], options).begin - h, m, s = time.hour, time.min, time.sec - time = Chronic.time_class.local(year, month, day, h, m, s) - end_time = Chronic.time_class.local(year, month, day + 1, h, m, s) - else - time = Chronic.time_class.local(year, month, day) - day += 1 unless day >= 31 - end_time = Chronic.time_class.local(year, month, day) - end - Span.new(time, end_time) - end - - # anchors - - # Handle repeaters - def handle_r(tokens, options) - dd_tokens = dealias_and_disambiguate_times(tokens, options) - get_anchor(dd_tokens, options) - end - - # Handle repeater/grabber/repeater - def handle_r_g_r(tokens, options) - new_tokens = [tokens[1], tokens[0], tokens[2]] - handle_r(new_tokens, options) - end - - # arrows - - # Handle scalar/repeater/pointer helper - def handle_srp(tokens, span, options) - distance = tokens[0].get_tag(Scalar).type - repeater = tokens[1].get_tag(Repeater) - pointer = tokens[2].get_tag(Pointer).type - - repeater.offset(span, distance, pointer) if repeater.respond_to?(:offset) - end - - # Handle scalar/repeater/pointer - def handle_s_r_p(tokens, options) - span = Span.new(self.now, self.now + 1) - - handle_srp(tokens, span, options) - end - - # Handle pointer/scalar/repeater - def handle_p_s_r(tokens, options) - new_tokens = [tokens[1], tokens[2], tokens[0]] - handle_s_r_p(new_tokens, options) - end - - # Handle scalar/repeater/pointer/anchor - def handle_s_r_p_a(tokens, options) - anchor_span = get_anchor(tokens[3..tokens.size - 1], options) - handle_srp(tokens, anchor_span, options) - end - - # Handle repeater/scalar/repeater/pointer - def handle_rmn_s_r_p(tokens, options) - handle_s_r_p_a(tokens[1..3] + tokens[0..0], options) - end - - def handle_s_r_a_s_r_p_a(tokens, options) - anchor_span = get_anchor(tokens[4..tokens.size - 1], options) - - span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options) - handle_srp(tokens[2..3]+tokens[4..6], span, options) - end - - # narrows - - # Handle oridinal repeaters - def handle_orr(tokens, outer_span, options) - repeater = tokens[1].get_tag(Repeater) - repeater.start = outer_span.begin - 1 - ordinal = tokens[0].get_tag(Ordinal).type - span = nil - - ordinal.times do - span = repeater.next(:future) - - if span.begin >= outer_span.end - span = nil - break - end - end - - span - end - - # Handle ordinal/repeater/separator/repeater - def handle_o_r_s_r(tokens, options) - outer_span = get_anchor([tokens[3]], options) - handle_orr(tokens[0..1], outer_span, options) - end - - # Handle ordinal/repeater/grabber/repeater - def handle_o_r_g_r(tokens, options) - outer_span = get_anchor(tokens[2..3], options) - handle_orr(tokens[0..1], outer_span, options) - end - - # support methods - - def day_or_time(day_start, time_tokens, options) - outer_span = Span.new(day_start, day_start + (24 * 60 * 60)) - - unless time_tokens.empty? - self.now = outer_span.begin - get_anchor(dealias_and_disambiguate_times(time_tokens, options), options.merge(:context => :future)) - else - outer_span - end - end - - def get_anchor(tokens, options) - grabber = Grabber.new(:this) - pointer = :future - repeaters = get_repeaters(tokens) - repeaters.size.times { tokens.pop } - - if tokens.first && tokens.first.get_tag(Grabber) - grabber = tokens.shift.get_tag(Grabber) - end - - head = repeaters.shift - head.start = self.now - - case grabber.type - when :last - outer_span = head.next(:past) - when :this - if options[:context] != :past and repeaters.size > 0 - outer_span = head.this(:none) - else - outer_span = head.this(options[:context]) - end - when :next - outer_span = head.next(:future) - else - raise 'Invalid grabber' - end - - if Chronic.debug - puts "Handler-class: #{head.class}" - puts "--#{outer_span}" - end - - find_within(repeaters, outer_span, pointer) - end - - def get_repeaters(tokens) - tokens.map { |token| token.get_tag(Repeater) }.compact.sort.reverse - end - - def month_overflow?(year, month, day) - if ::Date.leap?(year) - day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1] - else - day > RepeaterMonth::MONTH_DAYS[month - 1] - end - rescue ArgumentError - false - end - - # Recursively finds repeaters within other repeaters. - # Returns a Span representing the innermost time span - # or nil if no repeater union could be found - def find_within(tags, span, pointer) - puts "--#{span}" if Chronic.debug - return span if tags.empty? - - head = tags.shift - head.start = (pointer == :future ? span.begin : span.end) - h = head.this(:none) - - if span.cover?(h.begin) || span.cover?(h.end) - find_within(tags, h, pointer) - end - end - - def time_with_rollover(year, month, day) - date_parts = - if month_overflow?(year, month, day) - if month == 12 - [year + 1, 1, 1] - else - [year, month + 1, 1] - end - else - [year, month, day] - end - Chronic.time_class.local(*date_parts) - end - - def dealias_and_disambiguate_times(tokens, options) - # handle aliases of am/pm - # 5:00 in the morning -> 5:00 am - # 7:00 in the evening -> 7:00 pm - - day_portion_index = nil - tokens.each_with_index do |t, i| - if t.get_tag(RepeaterDayPortion) - day_portion_index = i - break - end - end - - time_index = nil - tokens.each_with_index do |t, i| - if t.get_tag(RepeaterTime) - time_index = i - break - end - end - - if day_portion_index && time_index - t1 = tokens[day_portion_index] - t1tag = t1.get_tag(RepeaterDayPortion) - - case t1tag.type - when :morning - puts '--morning->am' if Chronic.debug - t1.untag(RepeaterDayPortion) - t1.tag(RepeaterDayPortion.new(:am)) - when :afternoon, :evening, :night - puts "--#{t1tag.type}->pm" if Chronic.debug - t1.untag(RepeaterDayPortion) - t1.tag(RepeaterDayPortion.new(:pm)) - end - end - - # handle ambiguous times if :ambiguous_time_range is specified - if options[:ambiguous_time_range] != :none - ambiguous_tokens = [] - - tokens.each_with_index do |token, i| - ambiguous_tokens << token - next_token = tokens[i + 1] - - if token.get_tag(RepeaterTime) && token.get_tag(RepeaterTime).type.ambiguous? && (!next_token || !next_token.get_tag(RepeaterDayPortion)) - distoken = Token.new('disambiguator') - - distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range])) - ambiguous_tokens << distoken - end - end - - tokens = ambiguous_tokens - end - - tokens - end - - end - -end diff --git a/lib/chronic/handlers/anchor.rb b/lib/chronic/handlers/anchor.rb new file mode 100644 index 00000000..85c8b962 --- /dev/null +++ b/lib/chronic/handlers/anchor.rb @@ -0,0 +1,57 @@ +require 'chronic/handlers/general' + +module Chronic + module AnchorHandlers + include GeneralHandlers + # Handle grabber/time_special + # formats: gr ts + def handle_gr_ts + handle_gr + handle_possible(SeparatorSpace) + handle_ts + end + + # Handle grabber/day-name + # formats: gr dn + def handle_gr_dn + handle_gr + next_tag + handle_dn + end + + # Handle grabber/month-name + # formats: gr mn + def handle_gr_mn + handle_gr + next_tag + handle_mn + end + + # Handle grabber/season-name + # formats: gr sn + def handle_gr_sn + handle_gr + next_tag + handle_sn + end + + # Handle grabber/unit + # formats: gr u + def handle_gr_u + handle_gr + next_tag + handle_u + end + + # Handle keyword-in/scalar/unit + # formats: in s u + def handle_in_s_u + next_tag + next_tag + handle_s + next_tag + handle_u + end + + end +end diff --git a/lib/chronic/handlers/arrow.rb b/lib/chronic/handlers/arrow.rb new file mode 100644 index 00000000..b205b09e --- /dev/null +++ b/lib/chronic/handlers/arrow.rb @@ -0,0 +1,76 @@ +require 'chronic/handlers/general' + +module Chronic + module ArrowHandlers + include GeneralHandlers + # Handle pointer + # formats: p + def handle_p + @pointer = @tokens[@index].get_tag(Pointer).type + next_tag + end + + # Handle scalar/unit + # formats: su, s u + def handle_s_u + handle_s + handle_possible(SeparatorSpace) + handle_u + end + + # Handle unit/pointer + # formats: su, s u + def handle_u_p + handle_u + next_tag + handle_p + @count = 1 + end + + # Handle scalar/pointer + # formats: s p + def handle_s_p + handle_s + next_tag + handle_p + @unit = :minute + end + + # Handle rational/pointer + # formats: r p + def handle_r_p + handle_r + next_tag + handle_p + @count = (@count * 60).to_i + @unit = :minute + end + + # Handle pointer/scalar/unit + # formats: p su, s u + def handle_p_s_u + handle_p + handle_possible(SeparatorSpace) + handle_s_u + end + + # Handle scalar/day-name + # formats: s dn + def handle_s_dn + handle_s + handle_possible(SeparatorSpace) + handle_dn + @special = @wday + @unit = :wday + end + + # Handle scalar/day-name/pointer + # formats: s dn p + def handle_s_dn_p + handle_s_dn + next_tag + handle_p + end + + end +end \ No newline at end of file diff --git a/lib/chronic/handlers/date.rb b/lib/chronic/handlers/date.rb new file mode 100644 index 00000000..236e5134 --- /dev/null +++ b/lib/chronic/handlers/date.rb @@ -0,0 +1,263 @@ +require 'chronic/handlers/general' + +module Chronic + module DateHandlers + include GeneralHandlers + + # Handle scalar-day + # formats: dd + def handle_sd + @day = @tokens[@index].get_tag(ScalarDay).type + @have_day = true + @index += 1 + handle_possible(SeparatorComma) + @precision = :day + end + + # Handle scalar-month + # formats: mm + def handle_sm + @month = @tokens[@index].get_tag(ScalarMonth).type + @have_month = true + @index += 1 + @precision = :month + end + + # Handle ordinal-day + # formats: dd(st|nd|rd|th),? + def handle_od + @day = @tokens[@index].get_tag(OrdinalDay).type + @have_day = true + @index += 1 + handle_possible(SeparatorComma) + @precision = :day + end + + # Handle ordinal-month + # formats: mm(st|nd|rd|th) + def handle_om + @month = @tokens[@index].get_tag(OrdinalMonth).type + @have_month = true + @index += 1 + @precision = :month + end + + + + # Handle scalar-day/month-name + # formats: dd mn, dd-mn + def handle_sd_mn + handle_sd + handle_possible(SeparatorDash) + handle_mn + @precision = :day + end + + # Handle scalar-day/month-name/scalar-year + # formats: dd mn yyyy, dd-mn-yyyy + def handle_sd_mn_sy + handle_sd_mn + handle_possible(SeparatorDash) + handle_sy + @precision = :day + end + + # Handle scalar-day/scalar-month + # formats: dd/mm, dd.mm + def handle_sd_sm + handle_sd + next_tag + handle_sm + @precision = :day + end + + # Handle scalar-day/scalar-month/scalar-year + # formats: dd/mm/yyyy, dd.mm.yyyy + def handle_sd_sm_sy + handle_sd_sm + next_tag + handle_sy + @precision = :day + end + + + + # Handle ordinal-day/month-name + # formats: dd(st|nd|rd|th) mn + def handle_od_mn + handle_od + next_tag + handle_mn + @precision = :day + end + + # Handle ordinal-day/month-name/scalar-year + # formats: dd(st|nd|rd|th) mn yyyy + def handle_od_mn_sy + handle_od + next_tag + handle_mn + handle_possible(SeparatorSpace) + handle_sy + @precision = :day + end + + + + # Handle day-name/ordinal-day + # formats: dn od + def handle_dn_od + handle_dn + next_tag + handle_od + end + + # Handle day-name/month-name/scalar-day + # formats: dn month dd + def handle_dn_mn_sd + handle_dn + next_tag + handle_mn_sd + end + + # Handle day-name/month-name/scalar-day/scalar-year + # formats: dn month dd yyyy + def handle_dn_mn_sd_sy + handle_dn_mn_sd + next_tag + handle_sy + @precision = :day + end + + # Handle day-name/month-name/ordinal-day + # formats: dn month dd + def handle_dn_mn_od + handle_dn + next_tag + handle_mn_od + end + + # Handle day-name/month-name/ordinal-day/scalar-year + # formats: dn month dd yyyy + def handle_dn_mn_od_sy + handle_dn_mn_od + next_tag + handle_sy + @precision = :day + end + + + + # Handle month-name/scalar-day + # formats: month dd, month-dd + def handle_mn_sd + handle_mn + next_tag + handle_sd + end + + # Handle month-name/scalar-day/scalar-year + # formats: month dd yyyy + def handle_mn_sd_sy + handle_mn + next_tag + handle_sd + handle_possible(SeparatorComma) + handle_sy + @precision = :day + end + + # Handle month-name/ordinal-day + # formats: month dd, month-dd + def handle_mn_od + handle_mn + next_tag + handle_od + end + + # Handle month-name/ordinal-day/scalar-year + # formats: mn dd yyyy + def handle_mn_od_sy + handle_mn + next_tag + handle_od + handle_sy + @precision = :day + end + + # Handle month-name/scalar-year + # formats: month yyyy + def handle_mn_sy + handle_mn + next_tag + handle_sy + @precision = :month + end + + + + # Handle scalar-month/scalar-day + # formats: mm/dd + def handle_sm_sd + handle_sm + next_tag + handle_sd + @precision = :day + end + + # Handle scalar-month/scalar-day/scalar-year + # formats: mm/dd/yyyy + def handle_sm_sd_sy + handle_sm_sd + next_tag + handle_sy + @precision = :day + end + + + + # Handle scalar-year/scalar-month + # formats: yyyy-sm + def handle_sy_sm + handle_sy + next_tag + handle_sm + end + + # Handle scalar-year/scalar-month/scalar-day + # formats: yyyy-mm-dd, yyyy.mm.dd, yyyy/mm/dd, yyyy:mm:dd + def handle_sy_sm_sd + handle_sy + next_tag + handle_sm + next_tag + handle_sd + end + + # Handle scalar-year/month-name + # formats: yyyy mn + def handle_sy_mn + handle_sy + handle_mn + end + + # Handle scalar-year/month-name/scalar-day + # formats: yyyy mn dd + def handle_sy_mn_sd + handle_sy + handle_mn + next_tag + handle_sd + end + + # Handle scalar-year/month-name/ordinal-day + # formats: yyyy mn dd(st|nd|rd|th) + def handle_sy_mn_od + handle_sy + handle_mn + next_tag + handle_od + end + + end +end diff --git a/lib/chronic/handlers/date_time.rb b/lib/chronic/handlers/date_time.rb new file mode 100644 index 00000000..6cc66bf8 --- /dev/null +++ b/lib/chronic/handlers/date_time.rb @@ -0,0 +1,72 @@ +require 'chronic/handlers/date' +require 'chronic/handlers/time' +require 'chronic/handlers/time_zone' + +module Chronic + module DateTimeHandlers + include DateHandlers + include TimeHandlers + include TimeZoneHandlers + + # Handle scalar-year/scalar-month/scalar-day/scalar-hour/scalar-minute/scalar-second + # formats: yyyy-mm-ddTh:m:s + def handle_sy_sm_sd_h_m_s + handle_sy_sm_sd + next_tag # T + handle_h_m_s + end + + # Handle scalar-year/scalar-month/scalar-day/scalar-hour/scalar-minute/scalar-second + # formats: yyyy-mm-ddTh:m:sZ + def handle_sy_sm_sd_h_m_s_tz + handle_sy_sm_sd + next_tag # T + handle_h_m_s + handle_generic + end + + # Handle scalar-year/scalar-month/scalar-day/scalar-hour/scalar-minute/scalar-second/scalar-subsecond + # formats: yyyy-mm-ddTh:m:s.sss + def handle_sy_sm_sd_h_m_s_ss + handle_sy_sm_sd + next_tag + handle_h_m_s_ss + end + + # Handle scalar-year/scalar-month/scalar-day/scalar-hour/scalar-minute/scalar-second/scalar-subsecond + # formats: yyyy-mm-ddTh:m:s.sssZ + def handle_sy_sm_sd_h_m_s_ss_tz + handle_sy_sm_sd + next_tag + handle_h_m_s_ss + handle_generic + end + + # Handle scalar-year/scalar-month/scalar-day/scalar-hour/scalar-minute/scalar-second/sign/scalar-hour/scalar-minute + # formats: yyyy-mm-ddTh:m:s+hh:mm + def handle_sy_sm_sd_h_m_s_hh_mm + handle_sy_sm_sd_h_m_s + handle_hh_mm + end + + # Handle scalar-year/scalar-month/scalar-day/scalar-hour/scalar-minute/scalar-second/scalar-subsecond/sign/scalar-hour/scalar-minute + # formats: yyyy-mm-ddTh:m:s.sss +hh:mm + def handle_sy_sm_sd_h_m_s_ss_hh_mm + handle_sy_sm_sd_h_m_s_ss + handle_hh_mm + end + + # Handle day-name/month-name/scalar-day/scalar-hour/scalar-minute/scalar-second/abbr/scalar-year + # formats: dn month dd h:m:s abbr yyyy + def handle_dn_mn_sd_h_m_s_abbr_sy + handle_dn_mn_sd + next_tag + handle_h_m_s + next_tag + handle_abbr + next_tag + handle_sy + end + + end +end diff --git a/lib/chronic/handlers/general.rb b/lib/chronic/handlers/general.rb new file mode 100644 index 00000000..f6040903 --- /dev/null +++ b/lib/chronic/handlers/general.rb @@ -0,0 +1,119 @@ +module Chronic + module GeneralHandlers + def reset_handler + @index = @begin + @grabber = nil + @day_special = nil + @time_special = nil + @precision = nil + end + + # Handle at + # formats: at + def handle_at + handle_possible(KeywordAt) + handle_possible(SeparatorSpace) + end + + # Handle in + # formats: in + def handle_in + handle_possible(KeywordIn) + handle_possible(SeparatorSpace) + end + + # Handle grabber + # formats: gr + def handle_gr + @grabber = @tokens[@index].get_tag(Grabber).type + @index += 1 + end + + # Handle ordinal + # formats: o + def handle_o + @number = @tokens[@index].get_tag(Ordinal).type + @count = @number + @index += 1 + end + + # Handle scalar + # formats: s + def handle_s + @count = @tokens[@index].get_tag(Scalar).type + @index += 1 + end + + # Handle scalar-year + # formats: yyyy + def handle_sy + handle_possible(SeparatorSpace) + handle_possible(SeparatorApostrophe) + @year = @tokens[@index].get_tag(ScalarYear).type + @have_year = true + @index += 1 + @precision = :year + end + + # Handle rational + # formats: r + def handle_r + @count = @tokens[@index].get_tag(Rational).type + @index += 1 + end + + # Handle unit + # formats: u + def handle_u + @unit = @tokens[@index].get_tag(Unit).type + @index += 1 + @precision = :unit + end + + # Handle time-special + # formats: ts + def handle_ts + handle_at + handle_in + @time_special = @tokens[@index].get_tag(TimeSpecial).type + @index += 1 + @precision = :time_special + end + + # Handle day-special + # formats: ds + def handle_ds + @day_special = @tokens[@index].get_tag(DaySpecial).type + @index += 1 + @precision = :day_special + end + + # Handle day-name + # formats: dn + def handle_dn + @wday = Date::DAYS[@tokens[@index].get_tag(DayName).type] + @index += 1 + @precision = :day + end + + # Handle month-name + # formats: month + def handle_mn + handle_possible(SeparatorSpace) + handle_possible(SeparatorSpace) if handle_possible(KeywordIn) + @month = Date::MONTHS[@tokens[@index].get_tag(MonthName).type] + @index += 1 + @precision = :month + end + + # Handle season-name + # formats: season + def handle_sn + handle_possible(SeparatorSpace) + @season = @tokens[@index].get_tag(SeasonName).type + @index += 1 + @precision = :season + end + + end +end diff --git a/lib/chronic/handlers/narrow.rb b/lib/chronic/handlers/narrow.rb new file mode 100644 index 00000000..f429d64c --- /dev/null +++ b/lib/chronic/handlers/narrow.rb @@ -0,0 +1,54 @@ +require 'chronic/handlers/general' + +module Chronic + module NarrowHandlers + include GeneralHandlers + + # Handle ordinal/day-name + # formats: o u + def handle_o_dn + handle_o + handle_possible(SeparatorSpace) + handle_dn + end + + # Handle ordinal/unit + # formats: o u + def handle_o_u + handle_o + handle_possible(SeparatorSpace) + handle_u + end + + # Handle ordinal-day/grabber/unit-month + # formats: dd(st|nd|rd|th) of last/this/next month + def handle_od_gr + handle_od + next_tag + handle_gr + # ignore unit + @year, @month = Date::add_month(@local_date.year, @local_date.month, @modifier) + next_tag + end + + # Handle ordinal-month/grabber/unit-year + # formats: mm(st|nd|rd|th) of last/this/next year + def handle_om_gr + handle_om + next_tag + handle_gr + # ignore unit + @year = @local_date.year + @modifier + next_tag + end + + # Handle grabber/ordinal/unit + # formats: last/this/next o u + def handle_gr_o_u + handle_gr + next_tag + handle_o_u + end + + end +end diff --git a/lib/chronic/handlers/time.rb b/lib/chronic/handlers/time.rb new file mode 100644 index 00000000..2344a294 --- /dev/null +++ b/lib/chronic/handlers/time.rb @@ -0,0 +1,167 @@ +require 'chronic/handlers/general' + +module Chronic + module TimeHandlers + include GeneralHandlers + # Handle scalar-hour/scalar-minute/scalar-second/scalar-subsecond + # formats: h:m:s.SSS, h:m:s:SSS + def handle_h_m_s_ss + handle_h_m_s + next_tag + subsecond = @tokens[@index].get_tag(ScalarSubsecond) + @subsecond_size = 10 ** subsecond.width + @subsecond = subsecond.type.to_f / subsecond_size + next_tag + @precision = :subsecond + end + + # Handle scalar-hour/scalar-minute/scalar-second/day-portion + # formats: h:m:s dp + def handle_h_m_s_dp + handle_h_m_s + handle_dp + end + + # Handle scalar-hour/scalar-minute/scalar-second + # formats: h:m:s + def handle_h_m_s + handle_h_m + next_tag + handle_s + end + + # Handle scalar-wide/day-portion + # formats: hhmm dp + def handle_hhmm_dp + handle_hhmm + handle_dp + end + + # Handle scalar-hour/scalar-minute/time-special + # formats: h:m in ts + def handle_h_m_ts + handle_h_m + next_tag + next_tag # in + next_tag + handle_ts + @precision = :minute + end + + # Handle scalar-hour/scalar-minute/day-portion + # formats: h:m dp, h.m dp + def handle_h_m_dp + handle_h_m + handle_dp + end + + # Handle time-special/scalar-hour/scalar-minute + # formats: ts at h:m + def handle_ts_h_m + handle_ts + next_tag + handle_h_m + end + + # Handle time-special/scalar-hour + # formats: ts at h + def handle_ts_h + handle_ts + next_tag + handle_h + end + + # Handle scalar-hour/scalar-minute + # formats: h:m, h.m + def handle_h_m + handle_h + next_tag + handle_m + end + + # Handle scalar-wide + # formats: hhmm + def handle_hhmm + handle_at + wide = @tokens[@index].get_tag(ScalarWide).type + @hour = wide[0, 2].to_i + @minute = wide[2, 4].to_i + @ambiguous = false + next_tag + @precision = :minute + end + + # Handle grabber/day-portion + # formats: gr dp + def handle_gr_dp + handle_gr + next_tag + handle_dp + end + + # Handle scalar-hour/time-special + # formats: h at|in ts + def handle_h_ts + handle_h + next_tag + handle_ts + @precision = :hour + end + + # Handle scalar-hour/day-portion + # formats: h dp + def handle_h_dp + handle_h + handle_possible(SeparatorSpace) + handle_dp + end + + # Handle scalar-hour/keyword-to/scalar-hour + # formats: h to h + def handle_h_h + handle_h + next_tag # space + next_tag # to + next_tag # space + handle_h + end + + # Handle scalar-hour + # formats: h + def handle_h + handle_at + handle_possible(SeparatorSpace) + tag = @tokens[@index].get_tag(ScalarHour) + @hour = tag.type + @ambiguous = false if @hour.zero? or @hour > 12 or (tag.width >= 2 and @hour < 10 and @options[:hours24] != false) + next_tag + @precision = :hour + end + + # Handle scalar-minute + # formats: m + def handle_m + @minute = @tokens[@index].get_tag(ScalarMinute).type + next_tag + @precision = :minute + end + + # Handle scalar-second + # formats: s + def handle_s + @second = @tokens[@index].get_tag(ScalarSecond).type + next_tag + @precision = :second + end + + # Handle day-portion + # formats: dp + def handle_dp + handle_at + handle_in + @day_portion = @tokens[@index].get_tag(DayPortion).type + next_tag + end + + end +end diff --git a/lib/chronic/handlers/time_zone.rb b/lib/chronic/handlers/time_zone.rb new file mode 100644 index 00000000..303f529d --- /dev/null +++ b/lib/chronic/handlers/time_zone.rb @@ -0,0 +1,50 @@ +module Chronic + module TimeZoneHandlers + + # Handle offset + # formats: UTC, Z + def handle_generic + @offset = @tokens[@index].get_tag(TimeZoneGeneric).type + next_tag + end + + # Handle abbr + # formats: UTC, GMT, etc + def handle_abbr + @abbr = @tokens[@index].get_tag(TimeZoneAbbreviation).type + next_tag + end + + # Handle scalar-wide + # formats: hhmm + def handle_hhmm + handle_sign + wide = @tokens[@index].get_tag(ScalarWide).type + @tzhour = wide[0, 2].to_i + @tzminute = wide[2, 4].to_i + next_tag + end + + # Handle sign/scalar-hour/scalar-minute + # formats: +hh:mm, -hh:mm + def handle_hh_mm + handle_sign + @tzhour = @tokens[@index].get_tag(ScalarHour).type + next_tag + next_tag + @tzminute = @tokens[@index].get_tag(ScalarMinute).type + next_tag + end + + # Handle sign + # formats: +, - + def handle_sign + sign = @tokens[@index].get_tag(Sign) + if sign + @sign = sign + next_tag + end + end + + end +end diff --git a/lib/chronic/parser.rb b/lib/chronic/parser.rb index 4ba4bec3..0975ec2c 100644 --- a/lib/chronic/parser.rb +++ b/lib/chronic/parser.rb @@ -1,9 +1,7 @@ require 'chronic/dictionary' -require 'chronic/handlers' module Chronic class Parser - include Handlers # Hash of default configuration options. DEFAULT_OPTIONS = { @@ -63,8 +61,11 @@ def initialize(options = {}) # Parse "text" with the given options # Returns either a Time or Chronic::Span, depending on the value of options[:guess] def parse(text) - tokens = tokenize(text, options) - span = tokens_to_span(tokens, options.merge(:text => text)) + text = pre_normalize(text) + puts text.inspect if Chronic.debug + + tokens = Tokenizer::tokenize(' ' + text + ' ') + tag(tokens, options) puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" if Chronic.debug @@ -176,47 +177,24 @@ def validate_options!(options) raise ArgumentError, "Unsupported option(s): #{non_permitted.join(', ')}" if non_permitted.any? end - def tokenize(text, options) - text = pre_normalize(text) - tokens = Tokenizer::tokenize(text) - [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, Sign, TimeZone].each do |tok| + def tag(tokens, options) + [DayName, MonthName, SeasonName, DaySpecial, TimeSpecial, DayPortion, Grabber, Pointer, Rational, Keyword, Separator, Scalar, Ordinal, Sign, Unit, TimeZoneTag].each do |tok| tok.scan(tokens, options) end - tokens.select { |token| token.tagged? } - end - - def tokens_to_span(tokens, options) - definitions = definitions(options) - - (definitions[:endian] + definitions[:date]).each do |handler| - if handler.match(tokens, definitions) - good_tokens = tokens.select { |o| !o.get_tag Separator } - return handler.invoke(:date, good_tokens, self, options) - end - end - - definitions[:anchor].each do |handler| - if handler.match(tokens, definitions) - good_tokens = tokens.select { |o| !o.get_tag Separator } - return handler.invoke(:anchor, good_tokens, self, options) - end - end - - definitions[:arrow].each do |handler| - if handler.match(tokens, definitions) - good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlash) || o.get_tag(SeparatorDash) || o.get_tag(SeparatorComma) || o.get_tag(SeparatorAnd) } - return handler.invoke(:arrow, good_tokens, self, options) - end - end - - definitions[:narrow].each do |handler| - if handler.match(tokens, definitions) - return handler.invoke(:narrow, tokens, self, options) + previous = nil + tokens.select! do |token| + if token.tagged? + if !previous or !token.tags.first.kind_of?(Separator) or token.tags.first.class != previous.class + previous = token.tags.first + true + else + false + end + else + false end end - - puts '-none' if Chronic.debug - return nil end + end end diff --git a/lib/chronic/tokenizer.rb b/lib/chronic/tokenizer.rb index a7111bc0..0234a7f6 100644 --- a/lib/chronic/tokenizer.rb +++ b/lib/chronic/tokenizer.rb @@ -22,12 +22,16 @@ def self.tokenize(text) tokens = [] index = 0 previos_index = 0 + previos_type = nil text.each_char do |char| type = char_type(char) - if type == :space - tokens << Token.new(text[previos_index...index], text, previos_index) - previos_index = index+1 + if previos_type and type != previos_type + if not (previos_type == :letter and type == :period) + tokens << Token.new(text[previos_index...index], text, previos_index) + previos_index = index + end end + previos_type = type index += 1 end tokens << Token.new(text[previos_index...index], text, previos_index) @@ -35,4 +39,4 @@ def self.tokenize(text) end end -end \ No newline at end of file +end From 2ec151c9a0f6edee2ce5c6a844d2de9cbf0b3887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:09:02 +0300 Subject: [PATCH 07/23] Rewrite Repeaters as (Date/Anchor/Arrow, etc.) Object handlers --- lib/chronic.rb | 22 +- lib/chronic/arrow.rb | 257 +++++++++++++++++ lib/chronic/objects/anchor_object.rb | 241 ++++++++++++++++ lib/chronic/objects/arrow_object.rb | 27 ++ lib/chronic/objects/date_object.rb | 164 +++++++++++ lib/chronic/objects/date_time_object.rb | 64 +++++ lib/chronic/objects/handler_object.rb | 81 ++++++ lib/chronic/objects/narrow_object.rb | 75 +++++ lib/chronic/objects/time_object.rb | 96 +++++++ lib/chronic/objects/time_zone_object.rb | 27 ++ lib/chronic/parser.rb | 2 + lib/chronic/repeaters/repeater_day.rb | 54 ---- lib/chronic/repeaters/repeater_day_name.rb | 53 ---- lib/chronic/repeaters/repeater_day_portion.rb | 109 ------- lib/chronic/repeaters/repeater_fortnight.rb | 72 ----- lib/chronic/repeaters/repeater_hour.rb | 59 ---- lib/chronic/repeaters/repeater_minute.rb | 59 ---- lib/chronic/repeaters/repeater_month.rb | 80 ------ lib/chronic/repeaters/repeater_month_name.rb | 95 ------ lib/chronic/repeaters/repeater_quarter.rb | 59 ---- .../repeaters/repeater_quarter_name.rb | 40 --- lib/chronic/repeaters/repeater_season.rb | 111 ------- lib/chronic/repeaters/repeater_season_name.rb | 43 --- lib/chronic/repeaters/repeater_second.rb | 43 --- lib/chronic/repeaters/repeater_time.rb | 138 --------- lib/chronic/repeaters/repeater_week.rb | 76 ----- lib/chronic/repeaters/repeater_weekday.rb | 86 ------ lib/chronic/repeaters/repeater_weekend.rb | 67 ----- lib/chronic/repeaters/repeater_year.rb | 78 ----- lib/chronic/season.rb | 26 -- lib/chronic/span.rb | 1 + lib/chronic/tags/repeater.rb | 160 ----------- lib/chronic/token_group.rb | 271 ++++++++++++++++++ test/test_repeater_day_name.rb | 51 ---- test/test_repeater_day_portion.rb | 254 ---------------- test/test_repeater_fortnight.rb | 62 ---- test/test_repeater_hour.rb | 68 ----- test/test_repeater_minute.rb | 34 --- test/test_repeater_month.rb | 50 ---- test/test_repeater_month_name.rb | 56 ---- test/test_repeater_quarter.rb | 70 ----- test/test_repeater_quarter_name.rb | 198 ------------- test/test_repeater_season.rb | 40 --- test/test_repeater_time.rb | 88 ------ test/test_repeater_week.rb | 115 -------- test/test_repeater_weekday.rb | 55 ---- test/test_repeater_weekend.rb | 74 ----- test/test_repeater_year.rb | 69 ----- 48 files changed, 1307 insertions(+), 2813 deletions(-) create mode 100644 lib/chronic/arrow.rb create mode 100644 lib/chronic/objects/anchor_object.rb create mode 100644 lib/chronic/objects/arrow_object.rb create mode 100644 lib/chronic/objects/date_object.rb create mode 100644 lib/chronic/objects/date_time_object.rb create mode 100644 lib/chronic/objects/handler_object.rb create mode 100644 lib/chronic/objects/narrow_object.rb create mode 100644 lib/chronic/objects/time_object.rb create mode 100644 lib/chronic/objects/time_zone_object.rb delete mode 100644 lib/chronic/repeaters/repeater_day.rb delete mode 100644 lib/chronic/repeaters/repeater_day_name.rb delete mode 100644 lib/chronic/repeaters/repeater_day_portion.rb delete mode 100644 lib/chronic/repeaters/repeater_fortnight.rb delete mode 100644 lib/chronic/repeaters/repeater_hour.rb delete mode 100644 lib/chronic/repeaters/repeater_minute.rb delete mode 100644 lib/chronic/repeaters/repeater_month.rb delete mode 100644 lib/chronic/repeaters/repeater_month_name.rb delete mode 100644 lib/chronic/repeaters/repeater_quarter.rb delete mode 100644 lib/chronic/repeaters/repeater_quarter_name.rb delete mode 100644 lib/chronic/repeaters/repeater_season.rb delete mode 100644 lib/chronic/repeaters/repeater_season_name.rb delete mode 100644 lib/chronic/repeaters/repeater_second.rb delete mode 100644 lib/chronic/repeaters/repeater_time.rb delete mode 100644 lib/chronic/repeaters/repeater_week.rb delete mode 100644 lib/chronic/repeaters/repeater_weekday.rb delete mode 100644 lib/chronic/repeaters/repeater_weekend.rb delete mode 100644 lib/chronic/repeaters/repeater_year.rb delete mode 100644 lib/chronic/season.rb delete mode 100644 lib/chronic/tags/repeater.rb create mode 100644 lib/chronic/token_group.rb delete mode 100644 test/test_repeater_day_name.rb delete mode 100644 test/test_repeater_day_portion.rb delete mode 100644 test/test_repeater_fortnight.rb delete mode 100644 test/test_repeater_hour.rb delete mode 100644 test/test_repeater_minute.rb delete mode 100644 test/test_repeater_month.rb delete mode 100644 test/test_repeater_month_name.rb delete mode 100644 test/test_repeater_quarter.rb delete mode 100644 test/test_repeater_quarter_name.rb delete mode 100644 test/test_repeater_season.rb delete mode 100644 test/test_repeater_time.rb delete mode 100644 test/test_repeater_week.rb delete mode 100644 test/test_repeater_weekday.rb delete mode 100644 test/test_repeater_weekend.rb delete mode 100644 test/test_repeater_year.rb diff --git a/lib/chronic.rb b/lib/chronic.rb index d2227c31..b03a7d6e 100644 --- a/lib/chronic.rb +++ b/lib/chronic.rb @@ -11,13 +11,13 @@ require 'chronic/date' require 'chronic/time' require 'chronic/time_zone' +require 'chronic/arrow' require 'chronic/handler' require 'chronic/span' require 'chronic/token' require 'chronic/token_group' require 'chronic/tokenizer' -require 'chronic/season' require 'chronic/tag' require 'chronic/tags/day_name' @@ -37,26 +37,6 @@ require 'chronic/tags/time_zone' require 'chronic/tags/unit' -require 'chronic/tags/repeater' -require 'chronic/repeaters/repeater_year' -require 'chronic/repeaters/repeater_quarter' -require 'chronic/repeaters/repeater_quarter_name' -require 'chronic/repeaters/repeater_season' -require 'chronic/repeaters/repeater_season_name' -require 'chronic/repeaters/repeater_month' -require 'chronic/repeaters/repeater_month_name' -require 'chronic/repeaters/repeater_fortnight' -require 'chronic/repeaters/repeater_week' -require 'chronic/repeaters/repeater_weekend' -require 'chronic/repeaters/repeater_weekday' -require 'chronic/repeaters/repeater_day' -require 'chronic/repeaters/repeater_day_name' -require 'chronic/repeaters/repeater_day_portion' -require 'chronic/repeaters/repeater_hour' -require 'chronic/repeaters/repeater_minute' -require 'chronic/repeaters/repeater_second' -require 'chronic/repeaters/repeater_time' - # Parse natural language dates and times into Time or Chronic::Span objects. # # Examples: diff --git a/lib/chronic/arrow.rb b/lib/chronic/arrow.rb new file mode 100644 index 00000000..4952b2e5 --- /dev/null +++ b/lib/chronic/arrow.rb @@ -0,0 +1,257 @@ +module Chronic + class Arrow + BASE_UNITS = [ + :years, + :months, + :days, + :hours, + :minutes, + :seconds + ] + + UNITS = BASE_UNITS + [ + :miliseconds, + :mornings, + :noons, + :afternoons, + :evenings, + :nights, + :midnights, + :weeks, + :weekdays, + :wdays, + :weekends, + :fortnights, + :seasons + ] + + PRECISION = [ + :miliseconds, + :seconds, + :minutes, + :noons, + :midnights, + :hours, + :mornings, + :afternoons, + :evenings, + :nights, + :days, + :weekdays, + :weekends, + :wdays, + :weeks, + :fortnights, + :months, + :seasons, + :years + ] + + attr_reader :begin + attr_reader :end + + UNITS.each { |u| attr_reader u } + + attr_reader :pointer + attr_reader :order + + def initialize(arrows, options) + @options = options + @ordered = [] + build(arrows) + end + + def range + @begin..@end + end + + def width + @end - @begin + end + + def overlap?(r2) + r1 = range + (r1.begin <= r2.end) and (r2.begin <= r1.end) + end + + def translate_unit(unit) + unit.to_s + 's' + end + + def to_span(span, timezone = nil) + precision = PRECISION.length - 1 + ds = Date::split(span.begin) + ds += Time::split(span.begin) + case span.precision + when :year + precision = PRECISION.index(:years) + ds[3] = ds[4] = ds[5] = 0 + when :month + precision = PRECISION.index(:months) + ds[3] = ds[4] = ds[5] = 0 + when :day, :day_special + precision = PRECISION.index(:days) + ds[3] = ds[4] = ds[5] = 0 + when :hour + precision = PRECISION.index(:hours) + when :minute + precision = PRECISION.index(:minutes) + when :second + precision = PRECISION.index(:seconds) + when :time_special + precision = 0 + end + + sign = (@pointer == :past) ? -1 : 1 + @ordered.each do |data| + unit = data.first + count = data[1] + special = data.last + case unit + when *BASE_UNITS + ds[BASE_UNITS.index(unit)] += sign * count + ds[2], ds[3], ds[4], ds[5] = Time::normalize(ds[2], ds[3], ds[4], ds[5]) + ds[0], ds[1], ds[2] = Date::normalize(ds[0], ds[1], ds[2]) + precision = update_precision(precision, unit) + when :seasons + # TODO + raise "Not Implemented Arrow #{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) + when :weeks + ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], sign * count * Date::WEEK_DAYS) + precision = update_precision(precision, unit) + when :wdays + date = Chronic.time_class.new(ds[0], ds[1], ds[2]) + diff = Date::wday_diff(date, special, sign) + ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], diff + sign * (count - 1) * Date::WEEK_DAYS) + precision = update_precision(precision, unit) + when :weekends + date = Chronic.time_class.new(ds[0], ds[1], ds[2]) + diff = Date::wday_diff(date, Date::DAYS[:saturday], sign) + ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], diff + sign * (count - 1) * Date::WEEK_DAYS) + ds[3], ds[4], ds[5] = 0, 0, 0 + precision = update_precision(precision, unit) + when :weekdays + # TODO + raise "Not Implemented Arrow #{unit}" + when :days + # TODO + raise "Not Implemented Arrow #{unit}" + when :mornings, :noons, :afternoons, :evenings, :nights, :midnights + name = n(unit) + count -= 1 if @pointer == :past and ds[3] > Time::SPECIAL[name].end + count += 1 if @pointer == :future and ds[3] < Time::SPECIAL[name].begin + ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], sign * count) + ds[3], ds[4], ds[5] = Time::SPECIAL[name].begin, 0, 0 + precision = PRECISION.index(unit) + when :miliseconds + # TODO + raise "Not Implemented Arrow #{unit}" + end + end + + de = ds.dup + case PRECISION[precision] + when :miliseconds + de[4], de[5] = Time::add_second(ds[4], ds[5], 0.001) + when :seconds + de[4], de[5] = Time::add_second(ds[4], ds[5]) + when :minutes + de[3], de[4] = Time::add_minute(ds[3], ds[4]) + de[5] = 0 + when :mornings, :noons, :afternoons, :evenings, :nights, :midnights + name = n(PRECISION[precision]) + de[3], de[4], de[5] = Time::SPECIAL[name].end, 0, 0 + when :hours + de[2], de[3] = Time::add_hour(ds[2], ds[3]) + de[4] = de[5] = 0 + when :days, :weekdays, :wdays + de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2]) + de[3] = de[4] = de[5] = 0 + when :weekends + end_date = Chronic.time_class.new(ds[0], ds[1], ds[2]) + diff = Date::wday_diff(end_date, Date::DAYS[:monday]) + de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2], diff) + de[3] = de[4] = de[5] = 0 + when :weeks + end_date = Chronic.time_class.new(ds[0], ds[1], ds[2]) + diff = Date::wday_diff(end_date, Date::DAYS[@options[:week_start]]) + de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2], diff) + de[3] = de[4] = de[5] = 0 + when :fortnights + end_date = Chronic.time_class.new(ds[0], ds[1], ds[2]) + diff = Date::wday_diff(end_date, Date::DAYS[@options[:week_start]]) + de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2], diff + Date::WEEK_DAYS) + de[3] = de[4] = de[5] = 0 + when :months + de[0], de[1] = Date::add_month(ds[0], ds[1]) + de[2] = 1 + de[3] = de[4] = de[5] = 0 + when :seasons + # TODO + raise "Not Implemented Arrow #{PRECISION[precision]}" + when :years + de[0] += 1 + de[1] = de[2] = 1 + de[3] = de[4] = de[5] = 0 + end + utc_offset = nil + end_utc_offset = nil + if timezone + utc_offset = timezone.to_offset(ds[0], ds[1], ds[2], ds[3], ds[4], ds[5]) + end_utc_offset = timezone.to_offset(de[0], de[1], de[2], de[3], de[4], de[5]) + end + span_start = Chronic.construct(ds[0], ds[1], ds[2], ds[3], ds[4], ds[5], utc_offset) + span_end = Chronic.construct(de[0], de[1], de[2], de[3], de[4], de[5], utc_offset) + Span.new(span_start, span_end) + end + + def to_s + full = [] + UNITS.each do |unit| + count = instance_variable_get('@'+unit.to_s) + full << [unit, count].join(' ') unless count.nil? + end + unless full.empty? + 'Arrow => ' + @pointer.inspect + ' ' + full.join(', ') + else + 'Arrow => empty' + end + end + + protected + + def n(unit) + s = unit.to_s + s.to_s[0, s.length - 1].to_sym + end + + def v(v) + '@'+v.to_s + end + + def update_precision(precision, unit) + new_precision = PRECISION.index(unit) + precision = new_precision if new_precision < precision + precision + end + + def build(arrows) + arrows.each do |arrow| + @begin = arrow.begin if @begin.nil? or @begin > arrow.begin + @end = arrow.end if @end.nil? or @end < arrow.end + @pointer = arrow.pointer if @pointer.nil? and not arrow.pointer.nil? + next if arrow.unit.nil? + unit = translate_unit(arrow.unit) + raise "Uknown unit #{unit.to_sym.inspect}" unless UNITS.include?(unit.to_sym) + count = instance_variable_get(v(unit)) + count ||= 0 + @ordered << [unit.to_sym, arrow.count, arrow.special] + instance_variable_set(v(unit), count+arrow.count) + end + end + + end +end \ No newline at end of file diff --git a/lib/chronic/objects/anchor_object.rb b/lib/chronic/objects/anchor_object.rb new file mode 100644 index 00000000..863c8ee0 --- /dev/null +++ b/lib/chronic/objects/anchor_object.rb @@ -0,0 +1,241 @@ +require 'chronic/handlers/anchor' + +module Chronic + class AnchorObject < HandlerObject + attr_reader :grabber + attr_reader :unit + attr_reader :season + attr_reader :month + attr_reader :wday + attr_reader :day_special + attr_reader :time_special + attr_reader :count + def initialize(tokens, token_index, definitions, local_date, options) + super + match(tokens, @index, definitions) + end + + def is_valid? + true + 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}" + end + + def to_span(span = nil, time = nil, timezone = nil) + modifier = get_modifier + if span + year, month, day = Date::split(span.begin) + else + year, month, day = local_day + end + time = TimeInfo.new(@local_date) unless time + hour, minute, second = time.to_a + time_precision = false + if time.is_a?(TimeObject) + @precision = time.precision + time_precision = true + end + date = Chronic.time_class.new(year, month, day) + end_year, end_month, end_day = year, month, day + end_hour, end_minute, end_second = time.get_end + if @count + modifier = @count - 1 + @context == :future + end + sign = get_sign + if @season + season = Date::SEASON_DATES[@season] + diff = Date::month_diff(month, season.first, modifier, sign) + year, month = Date::add_month(year, month, diff) + day = season.last + 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 @month + diff = Date::month_diff(month, @month, modifier, sign) + year, month = Date::add_month(year, month, diff) + end_year, end_month = Date::add_month(year, month) + day = end_day = 1 + hour = minute = second = end_hour = end_minute = end_second = 0 + elsif @wday + diff = Date::wday_diff(date, @wday, modifier.zero? ? 1 : modifier, 0) + year, month, day = Date::add_day(year, month, day, diff) unless diff.zero? + end_year, end_month, end_day = year, month, day + unless time_precision + end_year, end_month, end_day = Date::add_day(year, month, day) + hour = minute = second = 0 if [year, month, day] != local_date + end_hour = end_minute = end_second = 0 + end + elsif @day_special + case @day_special + when :yesterday + year, month, day = Date::add_day(year, month, day, -1) + if time_precision + end_year, end_month, end_day = year, month, day + else + end_year, end_month, end_day = Date::add_day(year, month, day) + hour = minute = second = end_hour = end_minute = end_second = 0 + end + when :today + unless time_precision + end_year, end_month, end_day = Date::add_day(year, month, day) + end_hour = end_minute = end_second = 0 + end + when :tomorrow + year, month, day = Date::add_day(year, month, day) + if time_precision + end_year, end_month, end_day = year, month, day + else + end_year, end_month, end_day = Date::add_day(year, month, day) + hour = minute = second = end_hour = end_minute = end_second = 0 + end + else + raise "Uknown day special #{@day_special.inspect}" + end + elsif @time_special + year, month, day = Date::add_day(year, month, day, modifier) + end_year, end_month, end_day = year, month, day + hour = Time::SPECIAL[@time_special].begin + end_hour = Time::SPECIAL[@time_special].end + minute = second = end_minute = end_second = 0 + else + case @unit + when :year + year += modifier + unless modifier.zero? and @context == :future + month = 1 + day = 1 + hour = minute = second = 0 + end + unless modifier.zero? and @context == :past + end_year = year + 1 + end_month = 1 + 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 + day = 1 + hour = minute = second = 0 + end + unless modifier.zero? and @context == :past + end_year, end_month = Date::add_month(year, month) + end_day = 1 + end_hour = end_minute = end_second = 0 + end + when :fortnight + unless modifier.zero? and @context == :future + diff = Date::wday_diff(date, Date::DAYS[@options[:week_start]], modifier, sign) + diff -= Date::WEEK_DAYS + year, month, day = Date::add_day(year, month, day, diff) + date = Chronic.time_class.new(year, month, day) + hour = minute = second = 0 + end + unless modifier.zero? and @context == :past + end_date = Chronic.time_class.new(year, month, day) + diff = Date::wday_diff(end_date, Date::DAYS[@options[:week_start]], modifier, sign) + end_year, end_month, end_day = Date::add_day(year, month, day, diff + Date::WEEK_DAYS) + end_hour = end_minute = end_second = 0 + end + when :week + unless modifier.zero? and @context == :future + diff = Date::wday_diff(date, Date::DAYS[@options[:week_start]], 0, 0) + diff += Date::WEEK_DAYS * modifier + year, month, day = Date::add_day(year, month, day, diff) + hour = minute = second = 0 + end + unless modifier.zero? and @context == :past + end_date = Chronic.time_class.new(year, month, day) + diff = Date::wday_diff(end_date, Date::DAYS[@options[:week_start]], 1, 0) + end_year, end_month, end_day = Date::add_day(year, month, day, diff) + end_hour = end_minute = end_second = 0 + end + when :weekend + diff = Date::wday_diff(date, Date::DAYS[:saturday], modifier, sign) + year, month, day = Date::add_day(year, month, day, diff) + if [year, month, day] != local_date or @context == :past + hour = minute = second = 0 + end + if [year, month, day] != local_date or @context == :future + end_date = Chronic.time_class.new(year, month, day) + diff = Date::wday_diff(end_date, Date::DAYS[:monday], 1, 1) + end_year, end_month, end_day = Date::add_day(year, month, day, diff) + end_hour = end_minute = end_second = 0 + end + when :weekday + diff = Date::wd_diff(date, modifier) + year, month, day = Date::add_day(year, month, day, diff) + if [year, month, day] != local_date or @context == :past + hour = minute = second = 0 + end + if [year, month, day] != local_date or @context == :future + end_year, end_month, end_day = Date::add_day(year, month, day) + end_hour = end_minute = end_second = 0 + end + when :day + unless modifier.zero? and @context == :future + year, month, day = Date::add_day(year, month, day, modifier) + hour = minute = second = 0 + end + unless modifier.zero? and @context == :past or time_precision + end_year, end_month, end_day = Date::add_day(year, month, day) + end_hour = end_minute = end_second = 0 + end + when :morning, :noon, :afternoon, :evening, :night, :midnight + unless modifier.zero? and @context == :future + year, month, day = Date::add_day(year, month, day, modifier) + hour = Time::SPECIAL[@unit].begin + minute = second = 0 + end + unless modifier.zero? and @context == :past + end_year, end_month, end_day = Date::add_day(year, month, day) + end_hour = Time::SPECIAL[@unit].end + end_minute = end_second = 0 + end + when :hour + unless modifier.zero? and @context == :future + day, hour = Time::add_hour(day, hour, modifier) + minute = second = 0 + end + unless modifier.zero? and @context == :past + end_day, end_hour = Time::add_hour(day, hour) + end_minute = end_second = 0 + end + when :minute + unless modifier.zero? and @context == :future + hour, minute = Time::add_minute(hour, minute, modifier) + second = 0 + end + unless modifier.zero? and @context == :past + end_hour, end_minute = Time::add_minute(hour, minute) + end_second = 0 + end + when :second + unless modifier.zero? and @context == :future + minute, second = Time::add_second(minute, second, modifier) + hour, minute = Time::add_minute(hour, minute, 0) + end + unless modifier.zero? and @context == :past + end_hour = hour + end_minute, end_second = Time::add_second(minute, second) + end + else + raise "Uknown unit #{unit.inspect}" + end + end + span_start = Chronic.construct(year, month, day, hour, minute, second, timezone) + span_end = Chronic.construct(end_year, end_month, end_day, end_hour, end_minute, end_second, timezone) + span = Span.new(span_start, span_end, true) + span.precision = @precision + span + end + + protected + + include AnchorHandlers + + end +end \ No newline at end of file diff --git a/lib/chronic/objects/arrow_object.rb b/lib/chronic/objects/arrow_object.rb new file mode 100644 index 00000000..234433c4 --- /dev/null +++ b/lib/chronic/objects/arrow_object.rb @@ -0,0 +1,27 @@ +require 'chronic/handlers/arrow' + +module Chronic + class ArrowObject < HandlerObject + attr_reader :count + attr_reader :unit + attr_reader :special + attr_reader :pointer + def initialize(tokens, token_index, definitions, local_date, options) + super + match(tokens, @index, definitions) + end + + def is_valid? + true + end + + def to_s + "count #{@count.inspect}, unit #{@unit.inspect}, pointer #{@pointer.inspect}, special #{@special.inspect}" + end + + protected + + include ArrowHandlers + + end +end diff --git a/lib/chronic/objects/date_object.rb b/lib/chronic/objects/date_object.rb new file mode 100644 index 00000000..18c404e5 --- /dev/null +++ b/lib/chronic/objects/date_object.rb @@ -0,0 +1,164 @@ +require 'chronic/handlers/date' + +module Chronic + class DateObject < HandlerObject + include DateStructure + attr_reader :wday + attr_reader :day_special + def initialize(tokens, token_index, definitions, local_date, options) + super + handle_possible(KeywordOn) + unless handle_possible(KeywordIn) + handle_possible(SeparatorSpace) + end + @normalized = false + match(tokens, @index, definitions) + end + + def normalize! + return if @normalized + adjust! + @normalized = true + end + + def force_normalize! + @year = @local_date.year unless @have_year + @month = @local_date.month unless @have_month + @day = @local_date.day unless @have_day + adjust! + @normalized = true + end + + def is_valid? + normalize! + return false if @year.nil? or @month.nil? or @day.nil? + ::Date.valid_date?(@year, @month, @day) + end + + def get_end + year = @year + month = @month + day = @day + case @precision + when :year + year += 1 + month = 1 + day = 1 + when :month + year, month = Date::add_month(year, month) + day = 1 + when :day + year, month, day = Date::add_day(year, month, day) + else + # BUG! Should never happen + raise "Uknown precision #{@precision}" + end + [year, month, day] + end + + def to_s + "year #{@year.inspect}, month #{@month.inspect}, day #{@day.inspect}, wday #{@wday.inspect}, day special #{@day_special.inspect}, precision #{@precision.inspect}" + end + + protected + + def get_date_compare(sign) + sign.zero? ? false : ((sign == -1) ? (@day > @local_date.day) : (@day < @local_date.day)) + end + + def adjust! + sign = get_sign + if @day_special + set_date + case @day_special + when :yesterday + @year, @month, @day = Date::add_day(@year, @month, @day, -1) + when :today + # do nothing + when :tomorrow + @year, @month, @day = Date::add_day(@year, @month, @day, 1) + else + raise "Uknown special day #{@day_special.inspect}" + end + @precision = :day + elsif @wday + year, month, day = local_day + year = @year if @year + month = @month if @month + day = @day if @day + date = ::Date.new(year, month, day) + s = 0 + s = sign if not @have_year and not @have_month and not @have_day + diff = Date::wday_diff(date, @wday, s, 0) + actual_year, actual_month, actual_day = Date::add_day(year, month, day, diff) + if @have_day and @day != actual_day + actual_day = @day + n = 0 + s = sign + if not @month + begin + n += s + actual_year, actual_month = Date::add_month(year, month, n) + date = ::Date.new(actual_year, actual_month, actual_day) + actual_year = nil if date.wday != @wday or (@year and @year != actual_year) + end while actual_year.nil? and n*s < 20 + elsif not @year + actual_month = @month + begin + actual_year = year + n*s + date = ::Date.new(actual_year, actual_month, actual_day) + actual_year = nil if date.wday != @wday + n += 1 if s == sign + s *= -1 + end while actual_year.nil? and n < 200 + else + actual_year = nil + end + elsif @have_month and @month != actual_month + if not @year + n = 0 + begin + n += 1 + year += sign + date = ::Date.new(year, month, day) + diff = Date::wday_diff(date, @wday, sign) + actual_year, actual_month, actual_day = Date::add_day(year, month, day, diff) + actual_year = nil if @month != actual_month + end while actual_year.nil? and n < 50 + else + actual_year = nil + end + elsif @have_year and @year != actual_year + actual_year = nil + end + @year, @month, @day = [actual_year, actual_month, actual_day] + @precision = :day + else + @month = 1 if not @month and not @day + @day ||= 1 + date_compare = get_date_compare(sign) + if @year.nil? + @year = @local_date.year + if @month.nil? + @month = @local_date.month + @year, @month = Date::add_month(@year, @month, sign) if date_compare + elsif not sign.zero? + month_compare = (sign == -1) ? (@month > @local_date.month) : (@month < @local_date.month) + @year += sign if month_compare or (@month == @local_date.month and date_compare) + end + elsif @month.nil? and date_compare + @year, @month = Date::add_month(@year, @local_date.month, sign) + elsif @month.nil? + @month = @local_date.month + end + end + end + + def set_date + @year, @month, @day = local_day + end + + include DateHandlers + + end +end diff --git a/lib/chronic/objects/date_time_object.rb b/lib/chronic/objects/date_time_object.rb new file mode 100644 index 00000000..b47f2840 --- /dev/null +++ b/lib/chronic/objects/date_time_object.rb @@ -0,0 +1,64 @@ +require 'chronic/handlers/date_time' + +module Chronic + class DateTimeObject < HandlerObject + include DateStructure + include TimeStructure + include TimeZoneStructure + + def initialize(tokens, token_index, definitions, local_date, options) + super + @normalized = false + match(tokens, @index, definitions) + end + + def normalize! + return if @normalized + adjust! + @normalized = true + end + + def is_valid? + normalize! + return false if @year.nil? or @month.nil? or @day.nil? or @hour.nil? or @minute.nil? or @second.nil? + ::Date.valid_date?(@year, @month, @day) + end + + def get_end + year = @year + month = @month + day = @day + hour = @hour + minute = @minute + second = @second + if @precision == :subsecond + minute, second = Time::add_second(minute, second, 1.0 / @subsecond_size) + else + minute, second = Time::add_second(minute, second) + end + [year, month, day, hour, minute, second] + end + + def to_span + span_start = Chronic.construct(@year, @month, @day, @hour, @minute, @second, self) + end_year, end_month, end_day, end_hour, end_minute, end_second = get_end + span_end = Chronic.construct(end_year, end_month, end_day, end_hour, end_minute, end_second, self) + Span.new(span_start, span_end, true) + end + + def to_s + "year #{@year.inspect}, month #{@month.inspect}, day #{@day.inspect}, hour #{@hour.inspect}, minute #{@minute.inspect}, second #{@second.inspect}, subsecond #{@subsecond.inspect}" + end + + protected + + def adjust! + @second ||= 0 + @second += @subsecond if @subsecond + @subsecond = 0 + end + + include DateTimeHandlers + + end +end diff --git a/lib/chronic/objects/handler_object.rb b/lib/chronic/objects/handler_object.rb new file mode 100644 index 00000000..d6ff9509 --- /dev/null +++ b/lib/chronic/objects/handler_object.rb @@ -0,0 +1,81 @@ +require 'chronic/handlers/general' + +module Chronic + class HandlerObject + + attr_accessor :local_date + attr_reader :begin + attr_reader :width + attr_reader :precision + def initialize(tokens, token_index, definitions, local_date, options) + @tokens = tokens + @begin = token_index + @local_date = local_date + @options = options + @context = @options[:context] + @width = 0 + @index = token_index + end + + def end + @begin + @width - 1 + end + + def range + @begin..(@begin + @width - 1) + end + + def overlap?(r2) + r1 = range + (r1.begin <= r2.end) and (r2.begin <= r1.end) + end + + def is_valid? + false + end + + def local_day + [@local_date.year, @local_date.month, @local_date.day] + end + + def local_time + [@local_date.hour, @local_date.min, @local_date.sec + @local_date.usec.to_f / (10 ** 6)] + end + + protected + + def match(tokens, token_index, definitions) + definitions.each do |definition| + if Handler.match(tokens, token_index, definition.first) + self.method(definition.last).call + @width = @index - @begin + puts "\nHandler: #{self.class.name}.#{definition.last.to_s} @ #{@begin}-#{self.end} =>\n=======> #{self}" if Chronic.debug and @width > 0 + return + end + end + @width = 0 + end + + def get_sign + (@context == :past) ? -1 : ((@context == :future) ? 1 : 0) + end + + def get_modifier + (@grabber == :last) ? -1 : ((@grabber == :next) ? 1 : 0) + end + + def next_tag + @index += 1 + end + + def handle_possible(tag) + if @tokens[@index].get_tag(tag) + next_tag + return true + end + false + end + + end + +end diff --git a/lib/chronic/objects/narrow_object.rb b/lib/chronic/objects/narrow_object.rb new file mode 100644 index 00000000..453a9bd8 --- /dev/null +++ b/lib/chronic/objects/narrow_object.rb @@ -0,0 +1,75 @@ +require 'chronic/handlers/narrow' + +module Chronic + class NarrowObject < HandlerObject + attr_reader :number + attr_reader :wday + attr_reader :unit + def initialize(tokens, token_index, definitions, local_date, options) + super + handle_possible(SeparatorSpace) if handle_possible(KeywordOn) + match(tokens, @index, definitions) + end + + def is_valid? + true + end + + def to_s + "number #{@number.inspect}, wday #{@wday.inspect}, unit #{@unit.inspect}, grabber #{@grabber.inspect}" + end + + def to_span(span = nil, timezone = nil) + start = @local_date + start = span.begin if span + hour, minute, second, utc_offset = Time::split(start) + end_hour = end_minute = end_second = 0 + modifier = get_modifier + sign = get_sign + if @wday + diff = Date::wday_diff(start, @wday, 0, 0) + diff += Date::WEEK_DAYS if diff < 0 + diff += Date::WEEK_DAYS * (@number - 1) if @number > 1 + year, month, day = Date::add_day(start.year, start.month, start.day, diff) + end_year, end_month, end_day = year, month, day + end_hour += Date::DAY_HOURS + else + case @unit + when :year + # TODO + raise "Not Implemented NarrowObject #{@unit.inspect}" + when :season + # TODO + raise "Not Implemented NarrowObject #{@unit.inspect}" + when :month + day = start.day + year, month = Date::add_month(start.year, start.month, @number - 1) + end_year, end_month, end_day = year, month, day + end_year, end_month = Date::add_month(year, month, 1) + when :fortnight, :week, :weekend, :weekday + # TODO + raise "Not Implemented NarrowObject #{@unit.inspect}" + when :day + year, month, day = Date::add_day(start.year, start.month, start.day, @number - 1) + end_year, end_month, end_day = year, month, day + end_hour += Date::DAY_HOURS + when :morning, :noon, :afternoon, :evening, :night, :midnight, :hour, :minute, :second, :milisecond + # TODO + raise "Not Implemented NarrowObject #{@unit.inspect}" + else + raise "Uknown unit #{@unit.inspect}!" + end + end + span_start = Chronic.construct(year, month, day, hour, minute, second, timezone) + return nil if span and span_start >= span.end + span_end = Chronic.construct(end_year, end_month, end_day, end_hour, end_minute, end_second, timezone) + span_end = span.end if span and span_end > span.end + Span.new(span_start, span_end, true) + end + + protected + + include NarrowHandlers + + end +end diff --git a/lib/chronic/objects/time_object.rb b/lib/chronic/objects/time_object.rb new file mode 100644 index 00000000..831b2022 --- /dev/null +++ b/lib/chronic/objects/time_object.rb @@ -0,0 +1,96 @@ +require 'chronic/handlers/time' + +module Chronic + class TimeObject < HandlerObject + include TimeStructure + attr_reader :time_special + attr_reader :day_portion + attr_reader :ambiguous + def initialize(tokens, token_index, definitions, local_date, options) + super + @ambiguous = true + @ambiguous = false if @options[:hours24] == true or @options[:ambiguous_time_range] == :none + @normalized = false + match(tokens, @index, definitions) + end + + def normalize! + return if @normalized + if @hour + if @day_portion == :am # 0am to 12pm + @hour = 0 if @hour == 12 + @ambiguous = false + elsif @time_special == :morning + @ambiguous = false + elsif @day_portion == :pm or # 12pm to 0am + @time_special == :afternoon or + @time_special == :evening + @hour += 12 if @hour < 12 + @ambiguous = false + elsif @time_special == :night + @hour = 0 if @hour == 12 + @hour += 12 if @hour < 12 + @ambiguous = false + end + @hour += 12 if @ambiguous and @hour != 12 and @hour <= @options[:ambiguous_time_range] + elsif @time_special + if @time_special == :now + set_time + else + @hour = Time::SPECIAL[@time_special].begin + end + else + @hour = @local_date.hour + end + @ambiguous = false + @minute ||= @second ? @local_date.minute : 0 + @second ||= 0 + @second += @subsecond if @subsecond + @subsecond = 0 + @normalized = true + end + + def is_valid? + normalize! + true + end + + def get_end + hour = @hour + minute = @minute + second = @second + case @precision + when :hour + hour += 1 + when :minute + hour, minute = Time::add_minute(hour, minute) + when :second + minute, second = Time::add_second(minute, second) + when :subsecond + minute, second = Time::add_second(minute, second, 1.0 / @subsecond_size) + when :time_special + if @time_special != :now + hour = Time::SPECIAL[@time_special].end + minute = second = 0 + end + else + # BUG! Should never happen + raise "Uknown precision #{@precision.inspect}" + end + [hour, minute, second] + end + + def to_s + "hour #{@hour.inspect}, minute #{@minute.inspect}, second #{@second.inspect}, subsecond #{@subsecond.inspect}, time special #{time_special.inspect}, day portion #{@day_portion.inspect}, precision #{@precision.inspect}, ambiguous #{ambiguous.inspect}" + end + + protected + + def set_time + @hour, @minute, @second = local_time + end + + include TimeHandlers + + end +end \ No newline at end of file diff --git a/lib/chronic/objects/time_zone_object.rb b/lib/chronic/objects/time_zone_object.rb new file mode 100644 index 00000000..adb3ef42 --- /dev/null +++ b/lib/chronic/objects/time_zone_object.rb @@ -0,0 +1,27 @@ +require 'chronic/handlers/time_zone' + +module Chronic + class TimeZoneObject < HandlerObject + include TimeZoneStructure + + def initialize(tokens, token_index, definitions, local_date, options) + super + match(tokens, @index, definitions) + end + + def normalize! + return if @normalized + @offset = to_offset + @normalized = true + end + + def is_valid? + true + end + + protected + + include TimeZoneHandlers + + end +end diff --git a/lib/chronic/parser.rb b/lib/chronic/parser.rb index 0975ec2c..a11fa5ad 100644 --- a/lib/chronic/parser.rb +++ b/lib/chronic/parser.rb @@ -69,6 +69,8 @@ def parse(text) puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" if Chronic.debug + token_group = TokenGroup.new(tokens, definitions(options), @now, options) + span = token_group.to_span guess(span, options[:guess]) if span end diff --git a/lib/chronic/repeaters/repeater_day.rb b/lib/chronic/repeaters/repeater_day.rb deleted file mode 100644 index 8eca8c30..00000000 --- a/lib/chronic/repeaters/repeater_day.rb +++ /dev/null @@ -1,54 +0,0 @@ -module Chronic - class RepeaterDay < Repeater #:nodoc: - DAY_SECONDS = 86_400 # (24 * 60 * 60) - - def initialize(type, width = nil, options = {}) - super - @current_day_start = nil - end - - def next(pointer) - super - - unless @current_day_start - @current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day) - end - - direction = pointer == :future ? 1 : -1 - @current_day_start += direction * DAY_SECONDS - - Span.new(@current_day_start, @current_day_start + DAY_SECONDS) - end - - def this(pointer = :future) - super - - case pointer - when :future - day_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) - day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS - when :past - day_begin = Chronic.construct(@now.year, @now.month, @now.day) - day_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) - when :none - day_begin = Chronic.construct(@now.year, @now.month, @now.day) - day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS - end - - Span.new(day_begin, day_end) - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - span + direction * amount * DAY_SECONDS - end - - def width - DAY_SECONDS - end - - def to_s - super << '-day' - end - end -end diff --git a/lib/chronic/repeaters/repeater_day_name.rb b/lib/chronic/repeaters/repeater_day_name.rb deleted file mode 100644 index 1f3cb554..00000000 --- a/lib/chronic/repeaters/repeater_day_name.rb +++ /dev/null @@ -1,53 +0,0 @@ -module Chronic - class RepeaterDayName < Repeater #:nodoc: - DAY_SECONDS = 86400 # (24 * 60 * 60) - - def initialize(type, width = nil, options = {}) - super - @current_date = nil - end - - def next(pointer) - super - - direction = pointer == :future ? 1 : -1 - - unless @current_date - @current_date = ::Date.new(@now.year, @now.month, @now.day) - @current_date += direction - - day_num = symbol_to_number(@type) - - while @current_date.wday != day_num - @current_date += direction - end - else - @current_date += direction * 7 - end - next_date = @current_date.succ - Span.new(Chronic.construct(@current_date.year, @current_date.month, @current_date.day), Chronic.construct(next_date.year, next_date.month, next_date.day)) - end - - def this(pointer = :future) - super - - pointer = :future if pointer == :none - self.next(pointer) - end - - def width - DAY_SECONDS - end - - def to_s - super << '-dayname-' << @type.to_s - end - - private - - def symbol_to_number(sym) - lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6} - lookup[sym] || raise('Invalid symbol specified') - end - end -end diff --git a/lib/chronic/repeaters/repeater_day_portion.rb b/lib/chronic/repeaters/repeater_day_portion.rb deleted file mode 100644 index 49384a60..00000000 --- a/lib/chronic/repeaters/repeater_day_portion.rb +++ /dev/null @@ -1,109 +0,0 @@ -module Chronic - class RepeaterDayPortion < Repeater #:nodoc: - PORTIONS = { - :am => 0..(12 * 60 * 60 - 1), - :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1), - :morning => (6 * 60 * 60)..(12 * 60 * 60), # 6am-12am, - :afternoon => (13 * 60 * 60)..(17 * 60 * 60), # 1pm-5pm, - :evening => (17 * 60 * 60)..(20 * 60 * 60), # 5pm-8pm, - :night => (20 * 60 * 60)..(24 * 60 * 60), # 8pm-12pm - } - - def initialize(type, width = nil, options = {}) - super - @current_span = nil - - if type.kind_of? Integer - @range = (@type * 60 * 60)..((@type + 12) * 60 * 60) - else - @range = PORTIONS[type] - @range || raise("Invalid type '#{type}' for RepeaterDayPortion") - end - - @range || raise('Range should have been set by now') - end - - def next(pointer) - super - - unless @current_span - now_seconds = @now - Chronic.construct(@now.year, @now.month, @now.day) - if now_seconds < @range.begin - case pointer - when :future - range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin - when :past - range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin - end - elsif now_seconds > @range.end - case pointer - when :future - range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin - when :past - range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin - end - else - case pointer - when :future - range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin - when :past - range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin - end - end - offset = (@range.end - @range.begin) - range_end = construct_date_from_reference_and_offset(range_start, offset) - @current_span = Span.new(range_start, range_end) - else - days_to_shift_window = - case pointer - when :future - 1 - when :past - -1 - end - - new_begin = Chronic.construct(@current_span.begin.year, @current_span.begin.month, @current_span.begin.day + days_to_shift_window, @current_span.begin.hour, @current_span.begin.min, @current_span.begin.sec) - new_end = Chronic.construct(@current_span.end.year, @current_span.end.month, @current_span.end.day + days_to_shift_window, @current_span.end.hour, @current_span.end.min, @current_span.end.sec) - @current_span = Span.new(new_begin, new_end) - end - end - - def this(context = :future) - super - - range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin - range_end = construct_date_from_reference_and_offset(range_start) - @current_span = Span.new(range_start, range_end) - end - - def offset(span, amount, pointer) - @now = span.begin - portion_span = self.next(pointer) - direction = pointer == :future ? 1 : -1 - portion_span + (direction * (amount - 1) * RepeaterDay::DAY_SECONDS) - end - - def width - @range || raise('Range has not been set') - return @current_span.width if @current_span - if @type.kind_of? Integer - return (12 * 60 * 60) - else - @range.end - @range.begin - end - end - - def to_s - super << '-dayportion-' << @type.to_s - end - - private - def construct_date_from_reference_and_offset(reference, offset = nil) - elapsed_seconds_for_range = offset || (@range.end - @range.begin) - second_hand = ((elapsed_seconds_for_range - (12 * 60))) % 60 - minute_hand = (elapsed_seconds_for_range - second_hand) / (60) % 60 - hour_hand = (elapsed_seconds_for_range - minute_hand - second_hand) / (60 * 60) + reference.hour % 24 - Chronic.construct(reference.year, reference.month, reference.day, hour_hand, minute_hand, second_hand) - end - end -end diff --git a/lib/chronic/repeaters/repeater_fortnight.rb b/lib/chronic/repeaters/repeater_fortnight.rb deleted file mode 100644 index 908f3a91..00000000 --- a/lib/chronic/repeaters/repeater_fortnight.rb +++ /dev/null @@ -1,72 +0,0 @@ -module Chronic - class RepeaterFortnight < Repeater #:nodoc: - FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60) - - def initialize(type, width = nil, options = {}) - super - @current_fortnight_start = nil - end - - def next(pointer) - super - - unless @current_fortnight_start - case pointer - when :future - sunday_repeater = RepeaterDayName.new(:sunday) - sunday_repeater.start = @now - next_sunday_span = sunday_repeater.next(:future) - @current_fortnight_start = next_sunday_span.begin - when :past - sunday_repeater = RepeaterDayName.new(:sunday) - sunday_repeater.start = (@now + RepeaterDay::DAY_SECONDS) - 2.times { sunday_repeater.next(:past) } - last_sunday_span = sunday_repeater.next(:past) - @current_fortnight_start = last_sunday_span.begin - end - else - direction = pointer == :future ? 1 : -1 - @current_fortnight_start += direction * FORTNIGHT_SECONDS - end - - Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS) - end - - def this(pointer = :future) - super - - pointer = :future if pointer == :none - - case pointer - when :future - this_fortnight_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) + RepeaterHour::HOUR_SECONDS - sunday_repeater = RepeaterDayName.new(:sunday) - sunday_repeater.start = @now - sunday_repeater.this(:future) - this_sunday_span = sunday_repeater.this(:future) - this_fortnight_end = this_sunday_span.begin - Span.new(this_fortnight_start, this_fortnight_end) - when :past - this_fortnight_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) - sunday_repeater = RepeaterDayName.new(:sunday) - sunday_repeater.start = @now - last_sunday_span = sunday_repeater.next(:past) - this_fortnight_start = last_sunday_span.begin - Span.new(this_fortnight_start, this_fortnight_end) - end - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - span + direction * amount * FORTNIGHT_SECONDS - end - - def width - FORTNIGHT_SECONDS - end - - def to_s - super << '-fortnight' - end - end -end diff --git a/lib/chronic/repeaters/repeater_hour.rb b/lib/chronic/repeaters/repeater_hour.rb deleted file mode 100644 index a3c22bd7..00000000 --- a/lib/chronic/repeaters/repeater_hour.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Chronic - class RepeaterHour < Repeater #:nodoc: - HOUR_SECONDS = 3600 # 60 * 60 - - def initialize(type, width = nil, options = {}) - super - @current_hour_start = nil - end - - def next(pointer) - super - - unless @current_hour_start - case pointer - when :future - @current_hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour + 1) - when :past - @current_hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour - 1) - end - else - direction = pointer == :future ? 1 : -1 - @current_hour_start += direction * HOUR_SECONDS - end - - Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS) - end - - def this(pointer = :future) - super - - case pointer - when :future - hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1) - hour_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour + 1) - when :past - hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) - hour_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) - when :none - hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) - hour_end = hour_start + HOUR_SECONDS - end - - Span.new(hour_start, hour_end) - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - span + direction * amount * HOUR_SECONDS - end - - def width - HOUR_SECONDS - end - - def to_s - super << '-hour' - end - end -end diff --git a/lib/chronic/repeaters/repeater_minute.rb b/lib/chronic/repeaters/repeater_minute.rb deleted file mode 100644 index 5503a635..00000000 --- a/lib/chronic/repeaters/repeater_minute.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Chronic - class RepeaterMinute < Repeater #:nodoc: - MINUTE_SECONDS = 60 - - def initialize(type, width = nil, options = {}) - super - @current_minute_start = nil - end - - def next(pointer = :future) - super - - unless @current_minute_start - case pointer - when :future - @current_minute_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1) - when :past - @current_minute_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1) - end - else - direction = pointer == :future ? 1 : -1 - @current_minute_start += direction * MINUTE_SECONDS - end - - Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS) - end - - def this(pointer = :future) - super - - case pointer - when :future - minute_begin = @now - minute_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) - when :past - minute_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) - minute_end = @now - when :none - minute_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) - minute_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS - end - - Span.new(minute_begin, minute_end) - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - span + direction * amount * MINUTE_SECONDS - end - - def width - MINUTE_SECONDS - end - - def to_s - super << '-minute' - end - end -end diff --git a/lib/chronic/repeaters/repeater_month.rb b/lib/chronic/repeaters/repeater_month.rb deleted file mode 100644 index d2c9ab1c..00000000 --- a/lib/chronic/repeaters/repeater_month.rb +++ /dev/null @@ -1,80 +0,0 @@ -module Chronic - class RepeaterMonth < Repeater #:nodoc: - MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60 - YEAR_MONTHS = 12 - MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - - def initialize(type, width = nil, options = {}) - super - @current_month_start = nil - end - - def next(pointer) - super - - unless @current_month_start - @current_month_start = offset_by(Chronic.construct(@now.year, @now.month), 1, pointer) - else - @current_month_start = offset_by(Chronic.construct(@current_month_start.year, @current_month_start.month), 1, pointer) - end - - Span.new(@current_month_start, Chronic.construct(@current_month_start.year, @current_month_start.month + 1)) - end - - def this(pointer = :future) - super - - case pointer - when :future - month_start = Chronic.construct(@now.year, @now.month, @now.day + 1) - month_end = self.offset_by(Chronic.construct(@now.year, @now.month), 1, :future) - when :past - month_start = Chronic.construct(@now.year, @now.month) - month_end = Chronic.construct(@now.year, @now.month, @now.day) - when :none - month_start = Chronic.construct(@now.year, @now.month) - month_end = self.offset_by(Chronic.construct(@now.year, @now.month), 1, :future) - end - - Span.new(month_start, month_end) - end - - def offset(span, amount, pointer) - Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer)) - end - - def offset_by(time, amount, pointer) - direction = pointer == :future ? 1 : -1 - - amount_years = direction * amount / YEAR_MONTHS - amount_months = direction * amount % YEAR_MONTHS - - new_year = time.year + amount_years - new_month = time.month + amount_months - if new_month > YEAR_MONTHS - new_year += 1 - new_month -= YEAR_MONTHS - end - - days = month_days(new_year, new_month) - new_day = time.day > days ? days : time.day - - Chronic.construct(new_year, new_month, new_day, time.hour, time.min, time.sec) - end - - def width - MONTH_SECONDS - end - - def to_s - super << '-month' - end - - private - - def month_days(year, month) - ::Date.leap?(year) ? MONTH_DAYS_LEAP[month - 1] : MONTH_DAYS[month - 1] - end - end -end diff --git a/lib/chronic/repeaters/repeater_month_name.rb b/lib/chronic/repeaters/repeater_month_name.rb deleted file mode 100644 index 457e0c68..00000000 --- a/lib/chronic/repeaters/repeater_month_name.rb +++ /dev/null @@ -1,95 +0,0 @@ -module Chronic - class RepeaterMonthName < Repeater #:nodoc: - MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60 - MONTHS = { - :january => 1, - :february => 2, - :march => 3, - :april => 4, - :may => 5, - :june => 6, - :july => 7, - :august => 8, - :september => 9, - :october => 10, - :november => 11, - :december => 12 - } - - def initialize(type, width = nil, options = {}) - super - @current_month_begin = nil - end - - def next(pointer) - super - - unless @current_month_begin - case pointer - when :future - if @now.month < index - @current_month_begin = Chronic.construct(@now.year, index) - elsif @now.month > index - @current_month_begin = Chronic.construct(@now.year + 1, index) - end - when :none - if @now.month <= index - @current_month_begin = Chronic.construct(@now.year, index) - elsif @now.month > index - @current_month_begin = Chronic.construct(@now.year + 1, index) - end - when :past - if @now.month >= index - @current_month_begin = Chronic.construct(@now.year, index) - elsif @now.month < index - @current_month_begin = Chronic.construct(@now.year - 1, index) - end - end - @current_month_begin || raise('Current month should be set by now') - else - case pointer - when :future - @current_month_begin = Chronic.construct(@current_month_begin.year + 1, @current_month_begin.month) - when :past - @current_month_begin = Chronic.construct(@current_month_begin.year - 1, @current_month_begin.month) - end - end - - cur_month_year = @current_month_begin.year - cur_month_month = @current_month_begin.month - - if cur_month_month == 12 - next_month_year = cur_month_year + 1 - next_month_month = 1 - else - next_month_year = cur_month_year - next_month_month = cur_month_month + 1 - end - - Span.new(@current_month_begin, Chronic.construct(next_month_year, next_month_month)) - end - - def this(pointer = :future) - super - - case pointer - when :past - self.next(pointer) - when :future, :none - self.next(:none) - end - end - - def width - MONTH_SECONDS - end - - def index - @index ||= MONTHS[@type] - end - - def to_s - super << '-monthname-' << @type.to_s - end - end -end diff --git a/lib/chronic/repeaters/repeater_quarter.rb b/lib/chronic/repeaters/repeater_quarter.rb deleted file mode 100644 index 55fde0c3..00000000 --- a/lib/chronic/repeaters/repeater_quarter.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Chronic - class RepeaterQuarter < Repeater #:nodoc: - MONTHS_PER_QUARTER = 3 - QUARTER_SECONDS = 7_776_000 # 3 * 30 * 24 * 60 * 60 - - def next(pointer) - @current_span ||= quarter(@now) - offset_quarter_amount = pointer == :future ? 1 : -1 - @current_span = offset_quarter(@current_span.begin, offset_quarter_amount) - end - - def this(*) - @current_span = quarter(@now) - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - offset_quarter(span.begin, amount * direction) - end - - def width - @current_span ? @current_span.width : QUARTER_SECONDS - end - - def to_s - super << '-quarter' - end - - protected - - def quarter_index(month) - (month - 1) / MONTHS_PER_QUARTER - end - - def quarter(time) - year, month = time.year, time.month - - quarter_index = quarter_index(month) - quarter_month_start = (quarter_index * MONTHS_PER_QUARTER) + 1 - quarter_month_end = quarter_month_start + MONTHS_PER_QUARTER - - quarter_start = Chronic.construct(year, quarter_month_start) - quarter_end = Chronic.construct(year, quarter_month_end) - - Span.new(quarter_start, quarter_end) - end - - def offset_quarter(time, amount) - new_month = time.month - 1 - new_month = new_month + MONTHS_PER_QUARTER * amount - new_year = time.year + new_month / 12 - new_month = new_month % 12 + 1 - - offset_time_basis = Chronic.construct(new_year, new_month) - - quarter(offset_time_basis) - end - end -end diff --git a/lib/chronic/repeaters/repeater_quarter_name.rb b/lib/chronic/repeaters/repeater_quarter_name.rb deleted file mode 100644 index 872a10c1..00000000 --- a/lib/chronic/repeaters/repeater_quarter_name.rb +++ /dev/null @@ -1,40 +0,0 @@ -module Chronic - class RepeaterQuarterName < RepeaterQuarter #:nodoc: - QUARTERS = { - :q1 => 0, - :q2 => 1, - :q3 => 2, - :q4 => 3 - } - - def next(pointer) - unless @current_span - @current_span = this(pointer) - else - year_offset = pointer == :future ? 1 : -1 - new_year = @current_span.begin.year + year_offset - time_basis = Chronic.construct(new_year, @current_span.begin.month) - @current_span = quarter(time_basis) - end - - @current_span - end - - def this(pointer = :future) - current_quarter_index = quarter_index(@now.month) - target_quarter_index = QUARTERS[type] - - year_basis_offset = case pointer - when :past then current_quarter_index > target_quarter_index ? 0 : -1 - when :future then current_quarter_index < target_quarter_index ? 0 : 1 - else 0 - end - - year_basis = @now.year + year_basis_offset - month_basis = (MONTHS_PER_QUARTER * target_quarter_index) + 1 - time_basis = Chronic.construct(year_basis, month_basis) - - @current_span = quarter(time_basis) - end - end -end diff --git a/lib/chronic/repeaters/repeater_season.rb b/lib/chronic/repeaters/repeater_season.rb deleted file mode 100644 index 524824e2..00000000 --- a/lib/chronic/repeaters/repeater_season.rb +++ /dev/null @@ -1,111 +0,0 @@ -module Chronic - class RepeaterSeason < Repeater #:nodoc: - SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60 - SEASONS = { - :spring => Season.new(MiniDate.new(3,20), MiniDate.new(6,20)), - :summer => Season.new(MiniDate.new(6,21), MiniDate.new(9,22)), - :autumn => Season.new(MiniDate.new(9,23), MiniDate.new(12,21)), - :winter => Season.new(MiniDate.new(12,22), MiniDate.new(3,19)) - } - - def initialize(type, width = nil, options = {}) - super - @next_season_start = nil - @next_season_end = nil - end - - def next(pointer) - super - - direction = pointer == :future ? 1 : -1 - next_season = Season.find_next_season(find_current_season(MiniDate.from_time(@now)), direction) - - find_next_season_span(direction, next_season) - end - - def this(pointer = :future) - super - - direction = pointer == :future ? 1 : -1 - - today = Chronic.construct(@now.year, @now.month, @now.day) - this_ssn = find_current_season(MiniDate.from_time(@now)) - case pointer - when :past - this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction) - this_ssn_end = today - when :future - this_ssn_start = today + RepeaterDay::DAY_SECONDS - this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction) - when :none - this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction) - this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction) - end - - construct_season(this_ssn_start, this_ssn_end) - end - - def offset(span, amount, pointer) - Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer)) - end - - def offset_by(time, amount, pointer) - direction = pointer == :future ? 1 : -1 - time + amount * direction * SEASON_SECONDS - end - - def width - SEASON_SECONDS - end - - def to_s - super << '-season' - end - - private - - def find_next_season_span(direction, next_season) - unless @next_season_start || @next_season_end - @next_season_start = Chronic.construct(@now.year, @now.month, @now.day) - @next_season_end = Chronic.construct(@now.year, @now.month, @now.day) - end - - @next_season_start += direction * num_seconds_til_start(next_season, direction) - @next_season_end += direction * num_seconds_til_end(next_season, direction) - - construct_season(@next_season_start, @next_season_end) - end - - def find_current_season(md) - [:spring, :summer, :autumn, :winter].find do |season| - md.is_between?(SEASONS[season].start, SEASONS[season].end) - end - end - - def num_seconds_til(goal, direction) - start = Chronic.construct(@now.year, @now.month, @now.day) - seconds = 0 - - until MiniDate.from_time(start + direction * seconds).equals?(goal) - seconds += RepeaterDay::DAY_SECONDS - end - - seconds - end - - def num_seconds_til_start(season_symbol, direction) - num_seconds_til(SEASONS[season_symbol].start, direction) - end - - def num_seconds_til_end(season_symbol, direction) - num_seconds_til(SEASONS[season_symbol].end, direction) - end - - def construct_season(start, finish) - Span.new( - Chronic.construct(start.year, start.month, start.day), - Chronic.construct(finish.year, finish.month, finish.day) - ) - end - end -end diff --git a/lib/chronic/repeaters/repeater_season_name.rb b/lib/chronic/repeaters/repeater_season_name.rb deleted file mode 100644 index f92beff1..00000000 --- a/lib/chronic/repeaters/repeater_season_name.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Chronic - class RepeaterSeasonName < RepeaterSeason #:nodoc: - SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60 - DAY_SECONDS = 86_400 # (24 * 60 * 60) - - def next(pointer) - direction = pointer == :future ? 1 : -1 - find_next_season_span(direction, @type) - end - - def this(pointer = :future) - direction = pointer == :future ? 1 : -1 - - today = Chronic.construct(@now.year, @now.month, @now.day) - goal_ssn_start = today + direction * num_seconds_til_start(@type, direction) - goal_ssn_end = today + direction * num_seconds_til_end(@type, direction) - curr_ssn = find_current_season(MiniDate.from_time(@now)) - case pointer - when :past - this_ssn_start = goal_ssn_start - this_ssn_end = (curr_ssn == @type) ? today : goal_ssn_end - when :future - this_ssn_start = (curr_ssn == @type) ? today + RepeaterDay::DAY_SECONDS : goal_ssn_start - this_ssn_end = goal_ssn_end - when :none - this_ssn_start = goal_ssn_start - this_ssn_end = goal_ssn_end - end - - construct_season(this_ssn_start, this_ssn_end) - end - - def offset(span, amount, pointer) - Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer)) - end - - def offset_by(time, amount, pointer) - direction = pointer == :future ? 1 : -1 - time + amount * direction * RepeaterYear::YEAR_SECONDS - end - - end -end \ No newline at end of file diff --git a/lib/chronic/repeaters/repeater_second.rb b/lib/chronic/repeaters/repeater_second.rb deleted file mode 100644 index ec1c56e4..00000000 --- a/lib/chronic/repeaters/repeater_second.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Chronic - class RepeaterSecond < Repeater #:nodoc: - SECOND_SECONDS = 1 # haha, awesome - - def initialize(type, width = nil, options = {}) - super - @second_start = nil - end - - def next(pointer = :future) - super - - direction = pointer == :future ? 1 : -1 - - unless @second_start - @second_start = @now + (direction * SECOND_SECONDS) - else - @second_start += SECOND_SECONDS * direction - end - - Span.new(@second_start, @second_start + SECOND_SECONDS) - end - - def this(pointer = :future) - super - - Span.new(@now, @now + 1) - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - span + direction * amount * SECOND_SECONDS - end - - def width - SECOND_SECONDS - end - - def to_s - super << '-second' - end - end -end diff --git a/lib/chronic/repeaters/repeater_time.rb b/lib/chronic/repeaters/repeater_time.rb deleted file mode 100644 index 0a496e73..00000000 --- a/lib/chronic/repeaters/repeater_time.rb +++ /dev/null @@ -1,138 +0,0 @@ -module Chronic - class RepeaterTime < Repeater #:nodoc: - class Tick #:nodoc: - attr_accessor :time - - def initialize(time, ambiguous = false) - @time = time - @ambiguous = ambiguous - end - - def ambiguous? - @ambiguous - end - - def *(other) - Tick.new(@time * other, @ambiguous) - end - - def to_f - @time.to_f - end - - def to_s - @time.to_s + (@ambiguous ? '?' : '') - end - - end - - def initialize(time, width = nil, options = {}) - @current_time = nil - @options = options - time_parts = time.split(':') - raise ArgumentError, "Time cannot have more than 4 groups of ':'" if time_parts.count > 4 - - if time_parts.first.length > 2 and time_parts.count == 1 - if time_parts.first.length > 4 - second_index = time_parts.first.length - 2 - time_parts.insert(1, time_parts.first[second_index..time_parts.first.length]) - time_parts[0] = time_parts.first[0..second_index - 1] - end - minute_index = time_parts.first.length - 2 - time_parts.insert(1, time_parts.first[minute_index..time_parts.first.length]) - time_parts[0] = time_parts.first[0..minute_index - 1] - end - - ambiguous = false - hours = time_parts.first.to_i - - if @options[:hours24].nil? or (not @options[:hours24].nil? and @options[:hours24] != true) - ambiguous = true if (time_parts.first.length == 1 and hours > 0) or (hours >= 10 and hours <= 12) or (@options[:hours24] == false and hours > 0) - hours = 0 if hours == 12 and ambiguous - end - - hours *= 60 * 60 - minutes = 0 - seconds = 0 - subseconds = 0 - - minutes = time_parts[1].to_i * 60 if time_parts.count > 1 - seconds = time_parts[2].to_i if time_parts.count > 2 - subseconds = time_parts[3].to_f / (10 ** time_parts[3].length) if time_parts.count > 3 - - @type = Tick.new(hours + minutes + seconds + subseconds, ambiguous) - end - - # Return the next past or future Span for the time that this Repeater represents - # pointer - Symbol representing which temporal direction to fetch the next day - # must be either :past or :future - def next(pointer) - super - - half_day = 60 * 60 * 12 - full_day = 60 * 60 * 24 - - first = false - - unless @current_time - first = true - midnight = Chronic.time_class.local(@now.year, @now.month, @now.day) - - yesterday_midnight = midnight - full_day - tomorrow_midnight = midnight + full_day - - offset_fix = midnight.gmt_offset - tomorrow_midnight.gmt_offset - tomorrow_midnight += offset_fix - - catch :done do - if pointer == :future - if @type.ambiguous? - [midnight + @type.time + offset_fix, midnight + half_day + @type.time + offset_fix, tomorrow_midnight + @type.time].each do |t| - (@current_time = t; throw :done) if t >= @now - end - else - [midnight + @type.time + offset_fix, tomorrow_midnight + @type.time].each do |t| - (@current_time = t; throw :done) if t >= @now - end - end - else # pointer == :past - if @type.ambiguous? - [midnight + half_day + @type.time + offset_fix, midnight + @type.time + offset_fix, yesterday_midnight + @type.time + half_day].each do |t| - (@current_time = t; throw :done) if t <= @now - end - else - [midnight + @type.time + offset_fix, yesterday_midnight + @type.time].each do |t| - (@current_time = t; throw :done) if t <= @now - end - end - end - end - - @current_time || raise('Current time cannot be nil at this point') - end - - unless first - increment = @type.ambiguous? ? half_day : full_day - @current_time += pointer == :future ? increment : -increment - end - - Span.new(@current_time, @current_time + width) - end - - def this(context = :future) - super - - context = :future if context == :none - - self.next(context) - end - - def width - 1 - end - - def to_s - super << '-time-' << @type.to_s - end - end -end diff --git a/lib/chronic/repeaters/repeater_week.rb b/lib/chronic/repeaters/repeater_week.rb deleted file mode 100644 index c06b96b6..00000000 --- a/lib/chronic/repeaters/repeater_week.rb +++ /dev/null @@ -1,76 +0,0 @@ -module Chronic - class RepeaterWeek < Repeater #:nodoc: - WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60) - - def initialize(type, width = nil, options = {}) - super - @repeater_day_name = options[:week_start] || :sunday - @current_week_start = nil - end - - def next(pointer) - super - - unless @current_week_start - case pointer - when :future - first_week_day_repeater = RepeaterDayName.new(@repeater_day_name) - first_week_day_repeater.start = @now - next_span = first_week_day_repeater.next(:future) - @current_week_start = next_span.begin - when :past - first_week_day_repeater = RepeaterDayName.new(@repeater_day_name) - first_week_day_repeater.start = (@now + RepeaterDay::DAY_SECONDS) - first_week_day_repeater.next(:past) - last_span = first_week_day_repeater.next(:past) - @current_week_start = last_span.begin - end - else - direction = pointer == :future ? 1 : -1 - @current_week_start += direction * WEEK_SECONDS - end - - Span.new(@current_week_start, @current_week_start + WEEK_SECONDS) - end - - def this(pointer = :future) - super - - case pointer - when :future - this_week_start = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour) + RepeaterHour::HOUR_SECONDS - first_week_day_repeater = RepeaterDayName.new(@repeater_day_name) - first_week_day_repeater.start = @now - this_span = first_week_day_repeater.this(:future) - this_week_end = this_span.begin - Span.new(this_week_start, this_week_end) - when :past - this_week_end = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour) - first_week_day_repeater = RepeaterDayName.new(@repeater_day_name) - first_week_day_repeater.start = @now - last_span = first_week_day_repeater.next(:past) - this_week_start = last_span.begin - Span.new(this_week_start, this_week_end) - when :none - first_week_day_repeater = RepeaterDayName.new(@repeater_day_name) - first_week_day_repeater.start = @now - last_span = first_week_day_repeater.next(:past) - this_week_start = last_span.begin - Span.new(this_week_start, this_week_start + WEEK_SECONDS) - end - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - span + direction * amount * WEEK_SECONDS - end - - def width - WEEK_SECONDS - end - - def to_s - super << '-week' - end - end -end diff --git a/lib/chronic/repeaters/repeater_weekday.rb b/lib/chronic/repeaters/repeater_weekday.rb deleted file mode 100644 index 6c986283..00000000 --- a/lib/chronic/repeaters/repeater_weekday.rb +++ /dev/null @@ -1,86 +0,0 @@ -module Chronic - class RepeaterWeekday < Repeater #:nodoc: - DAY_SECONDS = 86400 # (24 * 60 * 60) - DAYS = { - :sunday => 0, - :monday => 1, - :tuesday => 2, - :wednesday => 3, - :thursday => 4, - :friday => 5, - :saturday => 6 - } - - def initialize(type, width = nil, options = {}) - super - @current_weekday_start = nil - end - - def next(pointer) - super - - direction = pointer == :future ? 1 : -1 - - unless @current_weekday_start - @current_weekday_start = Chronic.construct(@now.year, @now.month, @now.day) - @current_weekday_start += direction * DAY_SECONDS - - until is_weekday?(@current_weekday_start.wday) - @current_weekday_start += direction * DAY_SECONDS - end - else - loop do - @current_weekday_start += direction * DAY_SECONDS - break if is_weekday?(@current_weekday_start.wday) - end - end - - Span.new(@current_weekday_start, @current_weekday_start + DAY_SECONDS) - end - - def this(pointer = :future) - super - - case pointer - when :past - self.next(:past) - when :future, :none - self.next(:future) - end - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - - num_weekdays_passed = 0; offset = 0 - until num_weekdays_passed == amount - offset += direction * DAY_SECONDS - num_weekdays_passed += 1 if is_weekday?((span.begin+offset).wday) - end - - span + offset - end - - def width - DAY_SECONDS - end - - def to_s - super << '-weekday' - end - - private - - def is_weekend?(day) - day == symbol_to_number(:saturday) || day == symbol_to_number(:sunday) - end - - def is_weekday?(day) - !is_weekend?(day) - end - - def symbol_to_number(sym) - DAYS[sym] || raise('Invalid symbol specified') - end - end -end diff --git a/lib/chronic/repeaters/repeater_weekend.rb b/lib/chronic/repeaters/repeater_weekend.rb deleted file mode 100644 index 3811e03a..00000000 --- a/lib/chronic/repeaters/repeater_weekend.rb +++ /dev/null @@ -1,67 +0,0 @@ -module Chronic - class RepeaterWeekend < Repeater #:nodoc: - WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60) - - def initialize(type, width = nil, options = {}) - super - @current_week_start = nil - end - - def next(pointer) - super - - unless @current_week_start - case pointer - when :future - saturday_repeater = RepeaterDayName.new(:saturday) - saturday_repeater.start = @now - next_saturday_span = saturday_repeater.next(:future) - @current_week_start = next_saturday_span.begin - when :past - saturday_repeater = RepeaterDayName.new(:saturday) - saturday_repeater.start = (@now + RepeaterDay::DAY_SECONDS) - last_saturday_span = saturday_repeater.next(:past) - @current_week_start = last_saturday_span.begin - end - else - direction = pointer == :future ? 1 : -1 - @current_week_start += direction * RepeaterWeek::WEEK_SECONDS - end - - Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS) - end - - def this(pointer = :future) - super - - case pointer - when :future, :none - saturday_repeater = RepeaterDayName.new(:saturday) - saturday_repeater.start = @now - this_saturday_span = saturday_repeater.this(:future) - Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS) - when :past - saturday_repeater = RepeaterDayName.new(:saturday) - saturday_repeater.start = @now - last_saturday_span = saturday_repeater.this(:past) - Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS) - end - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - weekend = RepeaterWeekend.new(:weekend) - weekend.start = span.begin - start = weekend.next(pointer).begin + (amount - 1) * direction * RepeaterWeek::WEEK_SECONDS - Span.new(start, start + (span.end - span.begin)) - end - - def width - WEEKEND_SECONDS - end - - def to_s - super << '-weekend' - end - end -end diff --git a/lib/chronic/repeaters/repeater_year.rb b/lib/chronic/repeaters/repeater_year.rb deleted file mode 100644 index eb77d709..00000000 --- a/lib/chronic/repeaters/repeater_year.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Chronic - class RepeaterYear < Repeater #:nodoc: - YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60 - - def initialize(type, width = nil, options = {}) - super - @current_year_start = nil - end - - def next(pointer) - super - - unless @current_year_start - case pointer - when :future - @current_year_start = Chronic.construct(@now.year + 1) - when :past - @current_year_start = Chronic.construct(@now.year - 1) - end - else - diff = pointer == :future ? 1 : -1 - @current_year_start = Chronic.construct(@current_year_start.year + diff) - end - - Span.new(@current_year_start, Chronic.construct(@current_year_start.year + 1)) - end - - def this(pointer = :future) - super - - case pointer - when :future - this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1) - this_year_end = Chronic.construct(@now.year + 1, 1, 1) - when :past - this_year_start = Chronic.construct(@now.year, 1, 1) - this_year_end = Chronic.construct(@now.year, @now.month, @now.day) - when :none - this_year_start = Chronic.construct(@now.year, 1, 1) - this_year_end = Chronic.construct(@now.year + 1, 1, 1) - end - - Span.new(this_year_start, this_year_end) - end - - def offset(span, amount, pointer) - direction = pointer == :future ? 1 : -1 - new_begin = build_offset_time(span.begin, amount, direction) - new_end = build_offset_time(span.end, amount, direction) - Span.new(new_begin, new_end) - end - - def width - YEAR_SECONDS - end - - def to_s - super << '-year' - end - - private - - def build_offset_time(time, amount, direction) - year = time.year + (amount * direction) - days = month_days(year, time.month) - day = time.day > days ? days : time.day - Chronic.construct(year, time.month, day, time.hour, time.min, time.sec) - end - - def month_days(year, month) - if ::Date.leap?(year) - RepeaterMonth::MONTH_DAYS_LEAP[month - 1] - else - RepeaterMonth::MONTH_DAYS[month - 1] - end - end - end -end diff --git a/lib/chronic/season.rb b/lib/chronic/season.rb deleted file mode 100644 index 43937a8b..00000000 --- a/lib/chronic/season.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Chronic - class Season - - attr_reader :start - attr_reader :end - - def initialize(start_date, end_date) - @start = start_date - @end = end_date - end - - def self.find_next_season(season, pointer) - lookup = [:spring, :summer, :autumn, :winter] - next_season_num = (lookup.index(season) + 1 * pointer) % 4 - lookup[next_season_num] - end - - def self.season_after(season) - find_next_season(season, +1) - end - - def self.season_before(season) - find_next_season(season, -1) - end - end -end \ No newline at end of file diff --git a/lib/chronic/span.rb b/lib/chronic/span.rb index c780dd82..1ac8a786 100644 --- a/lib/chronic/span.rb +++ b/lib/chronic/span.rb @@ -3,6 +3,7 @@ module Chronic # Range, you can use #begin and #end to get the beginning and # ending times of the span (they will be of class Time) class Span < Range + attr_accessor :precision # Returns the width of this span in seconds def width (self.end - self.begin).to_i diff --git a/lib/chronic/tags/repeater.rb b/lib/chronic/tags/repeater.rb deleted file mode 100644 index a22b18c3..00000000 --- a/lib/chronic/tags/repeater.rb +++ /dev/null @@ -1,160 +0,0 @@ -module Chronic - class Repeater < Tag - - # Scan an Array of Token objects and apply any necessary Repeater - # tags to each token. - # - # tokens - An Array of tokens to scan. - # options - The Hash of options specified in Chronic::parse. - # - # Returns an Array of tokens. - def self.scan(tokens, options) - tokens.each do |token| - token.tag scan_for_quarter_names(token, options) - token.tag scan_for_season_names(token, options) - token.tag scan_for_month_names(token, options) - token.tag scan_for_day_names(token, options) - token.tag scan_for_day_portions(token, options) - token.tag scan_for_times(token, options) - token.tag scan_for_units(token, options) - end - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_quarter_names(token, options = {}) - scan_for token, RepeaterQuarterName, - { - /^q1$/ => :q1, - /^q2$/ => :q2, - /^q3$/ => :q3, - /^q4$/ => :q4 - }, options - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_season_names(token, options = {}) - scan_for token, RepeaterSeasonName, - { - /^springs?$/ => :spring, - /^summers?$/ => :summer, - /^(autumn)|(fall)s?$/ => :autumn, - /^winters?$/ => :winter - }, options - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_month_names(token, options = {}) - scan_for token, RepeaterMonthName, - { - /^jan[:\.]?(uary)?$/ => :january, - /^feb[:\.]?(ruary)?$/ => :february, - /^mar[:\.]?(ch)?$/ => :march, - /^apr[:\.]?(il)?$/ => :april, - /^may$/ => :may, - /^jun[:\.]?e?$/ => :june, - /^jul[:\.]?y?$/ => :july, - /^aug[:\.]?(ust)?$/ => :august, - /^sep[:\.]?(t[:\.]?|tember)?$/ => :september, - /^oct[:\.]?(ober)?$/ => :october, - /^nov[:\.]?(ember)?$/ => :november, - /^dec[:\.]?(ember)?$/ => :december - }, options - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_day_names(token, options = {}) - scan_for token, RepeaterDayName, - { - /^m[ou]n(day)?$/ => :monday, - /^t(ue|eu|oo|u)s?(day)?$/ => :tuesday, - /^we(d|dnes|nds|nns)(day)?$/ => :wednesday, - /^th(u|ur|urs|ers)(day)?$/ => :thursday, - /^fr[iy](day)?$/ => :friday, - /^sat(t?[ue]rday)?$/ => :saturday, - /^su[nm](day)?$/ => :sunday - }, options - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_day_portions(token, options = {}) - scan_for token, RepeaterDayPortion, - { - /^ams?$/ => :am, - /^pms?$/ => :pm, - /^mornings?$/ => :morning, - /^afternoons?$/ => :afternoon, - /^evenings?$/ => :evening, - /^(night|nite)s?$/ => :night - }, options - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_times(token, options = {}) - scan_for token, RepeaterTime, /^\d{1,2}(:?\d{1,2})?([\.:]?\d{1,2}([\.:]\d{1,6})?)?$/, options - end - - # token - The Token object we want to scan. - # - # Returns a new Repeater object. - def self.scan_for_units(token, options = {}) - { - /^years?$/ => :year, - /^q$/ => :quarter, - /^seasons?$/ => :season, - /^months?$/ => :month, - /^fortnights?$/ => :fortnight, - /^weeks?$/ => :week, - /^weekends?$/ => :weekend, - /^(week|business)days?$/ => :weekday, - /^days?$/ => :day, - /^hrs?$/ => :hour, - /^hours?$/ => :hour, - /^mins?$/ => :minute, - /^minutes?$/ => :minute, - /^secs?$/ => :second, - /^seconds?$/ => :second - }.each do |item, symbol| - if item =~ token.word - klass_name = 'Repeater' + symbol.to_s.capitalize - klass = Chronic.const_get(klass_name) - return klass.new(symbol, nil, options) - end - end - return nil - end - - def <=>(other) - width <=> other.width - end - - # returns the width (in seconds or months) of this repeatable. - def width - raise('Repeater#width must be overridden in subclasses') - end - - # returns the next occurance of this repeatable. - def next(pointer) - raise('Start point must be set before calling #next') unless @now - end - - def this(pointer) - raise('Start point must be set before calling #this') unless @now - end - - def to_s - 'repeater' - end - end -end diff --git a/lib/chronic/token_group.rb b/lib/chronic/token_group.rb new file mode 100644 index 00000000..0f4448e9 --- /dev/null +++ b/lib/chronic/token_group.rb @@ -0,0 +1,271 @@ +require 'chronic/objects/handler_object' +require 'chronic/objects/anchor_object' +require 'chronic/objects/arrow_object' +require 'chronic/objects/narrow_object' +require 'chronic/objects/date_object' +require 'chronic/objects/date_time_object' +require 'chronic/objects/time_object' +require 'chronic/objects/time_zone_object' + +module Chronic + class TokenGroup + + attr_reader :arrows + attr_reader :narrows + attr_reader :dates + attr_reader :times + attr_reader :timezones + def initialize(tokens, definitions, local_date, options) + @anchors = [] + @arrows = [] + @narrows = [] + @datetime = [] + @dates = [] + @times = [] + @timezones = [] + @local_date = local_date + @options = options + + date_definitions = definitions[:date] + definitions[:endian] + definitions[:short_date] + + tokens.each_index do |i| + + if @anchors.last.nil? or i > @anchors.last.end + anchor = AnchorObject.new(tokens, i, definitions[:anchor], local_date, options) + @anchors << anchor if anchor.width > 0 + end + @anchors.sort_by! { |a| a.width }.reverse! + + if @arrows.last.nil? or i > @arrows.last.end + arrow = ArrowObject.new(tokens, i, definitions[:arrow], local_date, options) + @arrows << arrow if arrow.width > 0 + end + + if @narrows.last.nil? or i > @narrows.last.end + narrow = NarrowObject.new(tokens, i, definitions[:narrow], local_date, options) + @narrows << narrow if narrow.width > 0 + end + + if @datetime.last.nil? or i > @datetime.last.end + date = DateTimeObject.new(tokens, i, definitions[:date_time], local_date, options) + @datetime << date if date.width > 0 + end + + if @dates.last.nil? or i > @dates.last.end + date = DateObject.new(tokens, i, date_definitions, local_date, options) + @dates << date if date.width > 0 + end + + if @times.last.nil? or i > @times.last.end or not tokens[@times.last.begin].get_tag(ScalarWide).nil? + time = TimeObject.new(tokens, i, definitions[:time], local_date, options) + @times << time if time.width > 0 + end + @times.sort_by! { |a| a.width }.reverse! + + if @timezones.last.nil? or i > @timezones.last.end + timezone = TimeZoneObject.new(tokens, i, definitions[:timezone], local_date, options) + @timezones << timezone if timezone.width > 0 + end + + end + end + + def get_anchor + @anchors.find do |anchor| + anchor.is_valid? + end + end + + def get_arrow(anchor = nil) + arrows = @arrows.select do |arrow| + (anchor.nil? or (not arrow.overlap?(anchor.range) or arrow.width > anchor.width)) and + arrow.is_valid? + end + Arrow.new(arrows, @options) unless arrows.empty? + end + + def get_narrow(anchor = nil, arrow = nil) + @narrows.find do |narrow| + (anchor.nil? or (not narrow.overlap?(anchor.range) or narrow.width >= anchor.width)) and + (arrow.nil? or not narrow.overlap?(arrow.range) or narrow.width >= arrow.width) and + narrow.is_valid? + end + end + + def get_datetime(anchor = nil, arrow = nil, narrow = nil) + @datetime.find do |datetime| + (anchor.nil? or not datetime.overlap?(anchor.range)) and + (arrow.nil? or not datetime.overlap?(arrow.range)) and + (narrow.nil? or not datetime.overlap?(narrow.range)) and + datetime.is_valid? + end + end + + def get_date(anchor = nil, arrow = nil, narrow = nil, datetime = nil) + @dates.find do |date| + (anchor.nil? or not date.overlap?(anchor.range)) and + (arrow.nil? or (not date.overlap?(arrow.range) or date.width > arrow.width or narrow)) and + (narrow.nil? or not date.overlap?(narrow.range)) and + (datetime.nil? or not date.overlap?(datetime.range)) and + date.is_valid? + end + end + + def get_next_date(anchor = nil, arrow, narrow, datetime, date, time, timezone) + @dates.find do |nextdate| + not nextdate.overlap?(date.range) and + (anchor.nil? or not nextdate.overlap?(anchor.range)) and + (arrow.nil? or not nextdate.overlap?(arrow.range)) and + (narrow.nil? or not nextdate.overlap?(narrow.range)) and + (datetime.nil? or not nextdate.overlap?(datetime.range)) and + (time.nil? or not nextdate.overlap?(time.range)) and + (timezone.nil? or not nextdate.overlap?(timezone.range)) and + nextdate.is_valid? and not nextdate.year.nil? + end + end + + def get_time(anchor = nil, arrow = nil, narrow = nil, datetime = nil, date = nil) + @times.find do |time| + (anchor.nil? or (not time.overlap?(anchor.range) or time.width > anchor.width)) and + (arrow.nil? or (not time.overlap?(arrow.range) or time.end >= arrow.end)) and + (narrow.nil? or not time.overlap?(narrow.range)) and + (datetime.nil? or not time.overlap?(datetime.range)) and + (date.nil? or (not time.overlap?(date.range) or time.width >= date.width )) and + time.is_valid? + end + end + + def get_timezone(anchor = nil, arrow = nil, narrow = nil, datetime = nil, date = nil, time = nil) + @timezones.find do |timezone| + (anchor.nil? or not timezone.overlap?(anchor.range)) and + (arrow.nil? or not timezone.overlap?(arrow.range)) and + (narrow.nil? or not timezone.overlap?(narrow.range)) and + (datetime.nil? or not timezone.overlap?(datetime.range)) and + (date.nil? or not timezone.overlap?(date.range)) and + (time.nil? or not timezone.overlap?(time.range)) and + timezone.is_valid? + end + end + + def update_date(anchor, arrow, narrow, datetime, date, time, timezone) + return if not date or date.year.nil? + next_date = get_next_date(anchor, arrow, narrow, datetime, date, time, timezone) + date.year = next_date.year if next_date + end + + def refresh(anchor, arrow, narrow, datetime, date, time, timezone) + anchor = nil if anchor and ( + (arrow and anchor.overlap?(arrow.range)) or + (datetime and anchor.overlap?(datetime.range)) or + (date and anchor.overlap?(date.range)) or + (time and anchor.overlap?(time.range)) or + (timezone and anchor.overlap?(timezone.range)) + ) + arrow = nil if arrow and ( + (narrow and arrow.overlap?(narrow.range)) or + (datetime and arrow.overlap?(datetime.range)) or + (date and arrow.overlap?(date.range)) or + (time and arrow.overlap?(time.range)) or + (timezone and arrow.overlap?(timezone.range)) + ) + narrow = nil if narrow and ( + (datetime and narrow.overlap?(datetime.range)) or + (date and narrow.overlap?(date.range)) or + (time and narrow.overlap?(time.range)) or + (timezone and narrow.overlap?(timezone.range)) + ) + + if anchor and narrow and anchor.overlap?(narrow.range) + if date or narrow.width > anchor.width + anchor = nil + else + narrow = nil + end + end + + if date and time and date.overlap?(time.range) + if time.width > date.width or anchor + date = nil + else + time = nil + end + end + [anchor, arrow, narrow, datetime, date, time, timezone] + end + + def get_all + anchor = get_anchor + arrow = get_arrow(anchor) + narrow = get_narrow(anchor, arrow) + datetime = get_datetime(anchor, arrow, narrow) + date = get_date(anchor, arrow, narrow, datetime) + time = get_time(anchor, arrow, narrow, datetime, date) + timezone = get_timezone(anchor, arrow, narrow, datetime, date, time) + update_date(anchor, arrow, narrow, datetime, date, time, timezone) + refresh(anchor, arrow, narrow, datetime, date, time, timezone) + end + + def get_sign + (@options[:context] == :past) ? -1 : ((@options[:context] == :future) ? 1 : 0) + end + + def to_span + span = nil + objects = get_all + anchor, arrow, narrow, datetime, date, time, timezone = objects + if datetime + puts "\nUse " + datetime.class.to_s if Chronic.debug + span = datetime.to_span + elsif anchor or arrow or narrow or datetime or date or time + puts "\nUse " + objects.reject(&:nil?).sort_by { |o| o.begin }.map(&:class).join(' + ') if Chronic.debug + + if arrow + time = TimeInfo.new(@local_date) if not time + arrow_date = date + arrow_date = DateInfo.new(@local_date) unless arrow_date + span = arrow_date.to_span(time, timezone) + span = arrow.to_span(span, timezone) + end + + if anchor + time = TimeInfo.new(@local_date) if not time + span = anchor.to_span(span, time, timezone) + if date + sbegin = span.begin + date.local_date = sbegin + date.force_normalize! + span = date.to_span(nil, timezone) + sign = get_sign + if not sign.zero? and date.wday.nil? and (span.begin - @local_date) * sign < 0 + year, month, day = Date::split(sbegin) + date.local_date = ::Time::mktime(year + sign, month, day) + date.force_normalize! + span = date.to_span(nil, timezone) + end + end + end + + if not arrow and not anchor and time + sign = get_sign + time_compare = sign.zero? ? false : ((sign == -1) ? (time > @local_date) : (time < @local_date)) + date = DateInfo.new(@local_date) unless date + date.add_day(sign) if not date.is_a?(DateObject) and date.is_equal?(@local_date) and time_compare + span = time.to_span(date, timezone) + end + + span = date.to_span(nil, timezone) if span.nil? and date + + span = narrow.to_span(span, timezone) if narrow + + end + + if Chronic.debug + puts "\n-- Span" + span.to_s if span + puts '-none' unless span + end + + span + end + + end +end diff --git a/test/test_repeater_day_name.rb b/test/test_repeater_day_name.rb deleted file mode 100644 index 7fa7f0da..00000000 --- a/test/test_repeater_day_name.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'helper' - -class TestRepeaterDayName < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_match - token = Chronic::Token.new('saturday') - repeater = Chronic::Repeater.scan_for_day_names(token) - assert_equal Chronic::RepeaterDayName, repeater.class - assert_equal :saturday, repeater.type - - token = Chronic::Token.new('sunday') - repeater = Chronic::Repeater.scan_for_day_names(token) - assert_equal Chronic::RepeaterDayName, repeater.class - assert_equal :sunday, repeater.type - end - - def test_next_future - mondays = Chronic::RepeaterDayName.new(:monday) - mondays.start = @now - - span = mondays.next(:future) - - assert_equal Time.local(2006, 8, 21), span.begin - assert_equal Time.local(2006, 8, 22), span.end - - span = mondays.next(:future) - - assert_equal Time.local(2006, 8, 28), span.begin - assert_equal Time.local(2006, 8, 29), span.end - end - - def test_next_past - mondays = Chronic::RepeaterDayName.new(:monday) - mondays.start = @now - - span = mondays.next(:past) - - assert_equal Time.local(2006, 8, 14), span.begin - assert_equal Time.local(2006, 8, 15), span.end - - span = mondays.next(:past) - - assert_equal Time.local(2006, 8, 7), span.begin - assert_equal Time.local(2006, 8, 8), span.end - end - -end diff --git a/test/test_repeater_day_portion.rb b/test/test_repeater_day_portion.rb deleted file mode 100644 index 1959c21e..00000000 --- a/test/test_repeater_day_portion.rb +++ /dev/null @@ -1,254 +0,0 @@ -require 'helper' - -class TestRepeaterDayPortion < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_am_future - day_portion = Chronic::RepeaterDayPortion.new(:am) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 17, 00), next_time.begin - assert_equal Time.local(2006, 8, 17, 11, 59, 59), next_time.end - - next_next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 18, 00), next_next_time.begin - assert_equal Time.local(2006, 8, 18, 11, 59, 59), next_next_time.end - end - - def test_am_past - day_portion = Chronic::RepeaterDayPortion.new(:am) - day_portion.start = @now - - next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 16, 00), next_time.begin - assert_equal Time.local(2006, 8, 16, 11, 59, 59), next_time.end - - next_next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 15, 00), next_next_time.begin - assert_equal Time.local(2006, 8, 15, 11, 59, 59), next_next_time.end - end - - def test_am_future_with_daylight_savings_time_boundary - @now = Time.local(2012,11,3,0,0,0) - day_portion = Chronic::RepeaterDayPortion.new(:am) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2012, 11, 4, 00), next_time.begin - assert_equal Time.local(2012, 11, 4, 11, 59, 59), next_time.end - - next_next_time = day_portion.next(:future) - - assert_equal Time.local(2012, 11, 5, 00), next_next_time.begin - assert_equal Time.local(2012, 11, 5, 11, 59, 59), next_next_time.end - end - - def test_pm_future - day_portion = Chronic::RepeaterDayPortion.new(:pm) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 17, 12), next_time.begin - assert_equal Time.local(2006, 8, 17, 23, 59, 59), next_time.end - - next_next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 18, 12), next_next_time.begin - assert_equal Time.local(2006, 8, 18, 23, 59, 59), next_next_time.end - end - - def test_pm_past - day_portion = Chronic::RepeaterDayPortion.new(:pm) - day_portion.start = @now - - next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 15, 12), next_time.begin - assert_equal Time.local(2006, 8, 15, 23, 59, 59), next_time.end - - next_next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 14, 12), next_next_time.begin - assert_equal Time.local(2006, 8, 14, 23, 59, 59), next_next_time.end - end - - def test_pm_future_with_daylight_savings_time_boundary - @now = Time.local(2012,11,3,0,0,0) - day_portion = Chronic::RepeaterDayPortion.new(:pm) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2012, 11, 3, 12), next_time.begin - assert_equal Time.local(2012, 11, 3, 23, 59, 59), next_time.end - - next_next_time = day_portion.next(:future) - - assert_equal Time.local(2012, 11, 4, 12), next_next_time.begin - assert_equal Time.local(2012, 11, 4, 23, 59, 59), next_next_time.end - end - - def test_morning_future - day_portion = Chronic::RepeaterDayPortion.new(:morning) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 17, 6), next_time.begin - assert_equal Time.local(2006, 8, 17, 12), next_time.end - - next_next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 18, 6), next_next_time.begin - assert_equal Time.local(2006, 8, 18, 12), next_next_time.end - end - - def test_morning_past - day_portion = Chronic::RepeaterDayPortion.new(:morning) - day_portion.start = @now - - next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 16, 6), next_time.begin - assert_equal Time.local(2006, 8, 16, 12), next_time.end - - next_next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 15, 6), next_next_time.begin - assert_equal Time.local(2006, 8, 15, 12), next_next_time.end - end - - def test_morning_future_with_daylight_savings_time_boundary - @now = Time.local(2012,11,3,0,0,0) - day_portion = Chronic::RepeaterDayPortion.new(:morning) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2012, 11, 3, 6), next_time.begin - assert_equal Time.local(2012, 11, 3, 12), next_time.end - - next_next_time = day_portion.next(:future) - - assert_equal Time.local(2012, 11, 4, 6), next_next_time.begin - assert_equal Time.local(2012, 11, 4, 12), next_next_time.end - end - - def test_afternoon_future - day_portion = Chronic::RepeaterDayPortion.new(:afternoon) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 17, 13), next_time.begin - assert_equal Time.local(2006, 8, 17, 17), next_time.end - - next_next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 18, 13), next_next_time.begin - assert_equal Time.local(2006, 8, 18, 17), next_next_time.end - end - - def test_afternoon_past - day_portion = Chronic::RepeaterDayPortion.new(:afternoon) - day_portion.start = @now - - next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 15, 13), next_time.begin - assert_equal Time.local(2006, 8, 15, 17), next_time.end - - next_next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 14, 13), next_next_time.begin - assert_equal Time.local(2006, 8, 14, 17), next_next_time.end - end - - def test_afternoon_future_with_daylight_savings_time_boundary - @now = Time.local(2012,11,3,0,0,0) - day_portion = Chronic::RepeaterDayPortion.new(:afternoon) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2012, 11, 3, 13), next_time.begin - assert_equal Time.local(2012, 11, 3, 17), next_time.end - - next_next_time = day_portion.next(:future) - - assert_equal Time.local(2012, 11, 4, 13), next_next_time.begin - assert_equal Time.local(2012, 11, 4, 17), next_next_time.end - end - - def test_evening_future - day_portion = Chronic::RepeaterDayPortion.new(:evening) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 16, 17), next_time.begin - assert_equal Time.local(2006, 8, 16, 20), next_time.end - - next_next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 17, 17), next_next_time.begin - assert_equal Time.local(2006, 8, 17, 20), next_next_time.end - end - - def test_evening_past - day_portion = Chronic::RepeaterDayPortion.new(:evening) - day_portion.start = @now - - next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 15, 17), next_time.begin - assert_equal Time.local(2006, 8, 15, 20), next_time.end - - next_next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 14, 17), next_next_time.begin - assert_equal Time.local(2006, 8, 14, 20), next_next_time.end - end - - def test_evening_future_with_daylight_savings_time_boundary - @now = Time.local(2012,11,3,0,0,0) - day_portion = Chronic::RepeaterDayPortion.new(:evening) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2012, 11, 3, 17), next_time.begin - assert_equal Time.local(2012, 11, 3, 20), next_time.end - - next_next_time = day_portion.next(:future) - - assert_equal Time.local(2012, 11, 4, 17), next_next_time.begin - assert_equal Time.local(2012, 11, 4, 20), next_next_time.end - end - - def test_night_future - day_portion = Chronic::RepeaterDayPortion.new(:night) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 16, 20), next_time.begin - assert_equal Time.local(2006, 8, 17, 0), next_time.end - - next_next_time = day_portion.next(:future) - assert_equal Time.local(2006, 8, 17, 20), next_next_time.begin - assert_equal Time.local(2006, 8, 18, 0), next_next_time.end - end - - def test_night_past - day_portion = Chronic::RepeaterDayPortion.new(:night) - day_portion.start = @now - - next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 15, 20), next_time.begin - assert_equal Time.local(2006, 8, 16, 0), next_time.end - - next_next_time = day_portion.next(:past) - assert_equal Time.local(2006, 8, 14, 20), next_next_time.begin - assert_equal Time.local(2006, 8, 15, 0), next_next_time.end - end - - def test_night_future_with_daylight_savings_time_boundary - @now = Time.local(2012,11,3,0,0,0) - day_portion = Chronic::RepeaterDayPortion.new(:night) - day_portion.start = @now - - next_time = day_portion.next(:future) - assert_equal Time.local(2012, 11, 3, 20), next_time.begin - assert_equal Time.local(2012, 11, 4, 0), next_time.end - - next_next_time = day_portion.next(:future) - - assert_equal Time.local(2012, 11, 4, 20), next_next_time.begin - assert_equal Time.local(2012, 11, 5, 0), next_next_time.end - end -end diff --git a/test/test_repeater_fortnight.rb b/test/test_repeater_fortnight.rb deleted file mode 100644 index d1fb0cf5..00000000 --- a/test/test_repeater_fortnight.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'helper' - -class TestRepeaterFortnight < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next_future - fortnights = Chronic::RepeaterFortnight.new(:fortnight) - fortnights.start = @now - - next_fortnight = fortnights.next(:future) - assert_equal Time.local(2006, 8, 20), next_fortnight.begin - assert_equal Time.local(2006, 9, 3), next_fortnight.end - - next_next_fortnight = fortnights.next(:future) - assert_equal Time.local(2006, 9, 3), next_next_fortnight.begin - assert_equal Time.local(2006, 9, 17), next_next_fortnight.end - end - - def test_next_past - fortnights = Chronic::RepeaterFortnight.new(:fortnight) - fortnights.start = @now - - last_fortnight = fortnights.next(:past) - assert_equal Time.local(2006, 7, 30), last_fortnight.begin - assert_equal Time.local(2006, 8, 13), last_fortnight.end - - last_last_fortnight = fortnights.next(:past) - assert_equal Time.local(2006, 7, 16), last_last_fortnight.begin - assert_equal Time.local(2006, 7, 30), last_last_fortnight.end - end - - def test_this_future - fortnights = Chronic::RepeaterFortnight.new(:fortnight) - fortnights.start = @now - - this_fortnight = fortnights.this(:future) - assert_equal Time.local(2006, 8, 16, 15), this_fortnight.begin - assert_equal Time.local(2006, 8, 27), this_fortnight.end - end - - def test_this_past - fortnights = Chronic::RepeaterFortnight.new(:fortnight) - fortnights.start = @now - - this_fortnight = fortnights.this(:past) - assert_equal Time.local(2006, 8, 13, 0), this_fortnight.begin - assert_equal Time.local(2006, 8, 16, 14), this_fortnight.end - end - - def test_offset - span = Chronic::Span.new(@now, @now + 1) - - offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future) - - assert_equal Time.local(2006, 9, 6, 14), offset_span.begin - assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end - end - -end diff --git a/test/test_repeater_hour.rb b/test/test_repeater_hour.rb deleted file mode 100644 index d729e5ce..00000000 --- a/test/test_repeater_hour.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'helper' - -class TestRepeaterHour < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next_future - hours = Chronic::RepeaterHour.new(:hour) - hours.start = @now - - next_hour = hours.next(:future) - assert_equal Time.local(2006, 8, 16, 15), next_hour.begin - assert_equal Time.local(2006, 8, 16, 16), next_hour.end - - next_next_hour = hours.next(:future) - assert_equal Time.local(2006, 8, 16, 16), next_next_hour.begin - assert_equal Time.local(2006, 8, 16, 17), next_next_hour.end - end - - def test_next_past - hours = Chronic::RepeaterHour.new(:hour) - hours.start = @now - - past_hour = hours.next(:past) - assert_equal Time.local(2006, 8, 16, 13), past_hour.begin - assert_equal Time.local(2006, 8, 16, 14), past_hour.end - - past_past_hour = hours.next(:past) - assert_equal Time.local(2006, 8, 16, 12), past_past_hour.begin - assert_equal Time.local(2006, 8, 16, 13), past_past_hour.end - end - - def test_this - @now = Time.local(2006, 8, 16, 14, 30) - - hours = Chronic::RepeaterHour.new(:hour) - hours.start = @now - - this_hour = hours.this(:future) - assert_equal Time.local(2006, 8, 16, 14, 31), this_hour.begin - assert_equal Time.local(2006, 8, 16, 15), this_hour.end - - this_hour = hours.this(:past) - assert_equal Time.local(2006, 8, 16, 14), this_hour.begin - assert_equal Time.local(2006, 8, 16, 14, 30), this_hour.end - - this_hour = hours.this(:none) - assert_equal Time.local(2006, 8, 16, 14), this_hour.begin - assert_equal Time.local(2006, 8, 16, 15), this_hour.end - end - - def test_offset - span = Chronic::Span.new(@now, @now + 1) - - offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 3, :future) - - assert_equal Time.local(2006, 8, 16, 17), offset_span.begin - assert_equal Time.local(2006, 8, 16, 17, 0, 1), offset_span.end - - offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 24, :past) - - assert_equal Time.local(2006, 8, 15, 14), offset_span.begin - assert_equal Time.local(2006, 8, 15, 14, 0, 1), offset_span.end - end - -end diff --git a/test/test_repeater_minute.rb b/test/test_repeater_minute.rb deleted file mode 100644 index a04f8b0e..00000000 --- a/test/test_repeater_minute.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'helper' - -class TestRepeaterMinute < TestCase - - def setup - @now = Time.local(2008, 6, 25, 7, 15, 30, 0) - end - - def test_next_future - minutes = Chronic::RepeaterMinute.new(:minute) - minutes.start = @now - - next_minute = minutes.next(:future) - assert_equal Time.local(2008, 6, 25, 7, 16), next_minute.begin - assert_equal Time.local(2008, 6, 25, 7, 17), next_minute.end - - next_next_minute = minutes.next(:future) - assert_equal Time.local(2008, 6, 25, 7, 17), next_next_minute.begin - assert_equal Time.local(2008, 6, 25, 7, 18), next_next_minute.end - end - - def test_next_past - minutes = Chronic::RepeaterMinute.new(:minute) - minutes.start = @now - - prev_minute = minutes.next(:past) - assert_equal Time.local(2008, 6, 25, 7, 14), prev_minute.begin - assert_equal Time.local(2008, 6, 25, 7, 15), prev_minute.end - - prev_prev_minute = minutes.next(:past) - assert_equal Time.local(2008, 6, 25, 7, 13), prev_prev_minute.begin - assert_equal Time.local(2008, 6, 25, 7, 14), prev_prev_minute.end - end -end diff --git a/test/test_repeater_month.rb b/test/test_repeater_month.rb deleted file mode 100644 index 73f3dfc9..00000000 --- a/test/test_repeater_month.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'helper' - -class TestRepeaterMonth < TestCase - - def setup - # Wed Aug 16 14:00:00 2006 - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_offset_by - # future - - time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :future) - assert_equal Time.local(2006, 9, 16, 14), time - - time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 5, :future) - assert_equal Time.local(2007, 1, 16, 14), time - - # past - - time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :past) - assert_equal Time.local(2006, 7, 16, 14), time - - time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past) - assert_equal Time.local(2005, 10, 16, 14), time - - time = Chronic::RepeaterMonth.new(:month).offset_by(Time.local(2010, 3, 29), 1, :past) - assert_equal 2, time.month - assert_equal 28, time.day - end - - def test_offset - # future - - span = Chronic::Span.new(@now, @now + 60) - offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :future) - - assert_equal Time.local(2006, 9, 16, 14), offset_span.begin - assert_equal Time.local(2006, 9, 16, 14, 1), offset_span.end - - # past - - span = Chronic::Span.new(@now, @now + 60) - offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :past) - - assert_equal Time.local(2006, 7, 16, 14), offset_span.begin - assert_equal Time.local(2006, 7, 16, 14, 1), offset_span.end - end - -end diff --git a/test/test_repeater_month_name.rb b/test/test_repeater_month_name.rb deleted file mode 100644 index 45a7c8c4..00000000 --- a/test/test_repeater_month_name.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'helper' - -class TestRepeaterMonthName < TestCase - - def setup - # Wed Aug 16 14:00:00 2006 - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next - # future - - mays = Chronic::RepeaterMonthName.new(:may) - mays.start = @now - - next_may = mays.next(:future) - assert_equal Time.local(2007, 5), next_may.begin - assert_equal Time.local(2007, 6), next_may.end - - next_next_may = mays.next(:future) - assert_equal Time.local(2008, 5), next_next_may.begin - assert_equal Time.local(2008, 6), next_next_may.end - - decembers = Chronic::RepeaterMonthName.new(:december) - decembers.start = @now - - next_december = decembers.next(:future) - assert_equal Time.local(2006, 12), next_december.begin - assert_equal Time.local(2007, 1), next_december.end - - # past - - mays = Chronic::RepeaterMonthName.new(:may) - mays.start = @now - - assert_equal Time.local(2006, 5), mays.next(:past).begin - assert_equal Time.local(2005, 5), mays.next(:past).begin - end - - def test_this - octobers = Chronic::RepeaterMonthName.new(:october) - octobers.start = @now - - this_october = octobers.this(:future) - assert_equal Time.local(2006, 10, 1), this_october.begin - assert_equal Time.local(2006, 11, 1), this_october.end - - aprils = Chronic::RepeaterMonthName.new(:april) - aprils.start = @now - - this_april = aprils.this(:past) - assert_equal Time.local(2006, 4, 1), this_april.begin - assert_equal Time.local(2006, 5, 1), this_april.end - end - -end diff --git a/test/test_repeater_quarter.rb b/test/test_repeater_quarter.rb deleted file mode 100644 index 1cccb592..00000000 --- a/test/test_repeater_quarter.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'helper' - -class TestRepeaterQuarter < TestCase - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_match - token = Chronic::Token.new('q') - repeater = Chronic::Repeater.scan_for_units(token) - assert_equal Chronic::RepeaterQuarter, repeater.class - assert_equal :quarter, repeater.type - end - - def test_this - quarter = Chronic::RepeaterQuarter.new(:quarter) - quarter.start = @now - - time = quarter.this - assert_equal Time.local(2006, 7, 1), time.begin - assert_equal Time.local(2006, 10, 1), time.end - end - - def test_next_future - quarter = Chronic::RepeaterQuarter.new(:quarter) - quarter.start = @now - - time = quarter.next(:future) - assert_equal Time.local(2006, 10, 1), time.begin - assert_equal Time.local(2007, 1, 1), time.end - - time = quarter.next(:future) - assert_equal Time.local(2007, 1, 1), time.begin - assert_equal Time.local(2007, 4, 1), time.end - - time = quarter.next(:future) - assert_equal Time.local(2007, 4, 1), time.begin - assert_equal Time.local(2007, 7, 1), time.end - end - - def test_next_past - quarter = Chronic::RepeaterQuarter.new(:quarter) - quarter.start = @now - - time = quarter.next(:past) - assert_equal Time.local(2006, 4, 1), time.begin - assert_equal Time.local(2006, 7, 1), time.end - - time = quarter.next(:past) - assert_equal Time.local(2006, 1, 1), time.begin - assert_equal Time.local(2006, 4, 1), time.end - - time = quarter.next(:past) - assert_equal Time.local(2005, 10, 1), time.begin - assert_equal Time.local(2006, 1, 1), time.end - end - - def test_offset - quarter = Chronic::RepeaterQuarter.new(:quarter) - span = Chronic::Span.new(@now, @now + 1) - - time = quarter.offset(span, 1, :future) - assert_equal Time.local(2006, 10, 1), time.begin - assert_equal Time.local(2007, 1, 1), time.end - - time = quarter.offset(span, 1, :past) - assert_equal Time.local(2006, 4, 1), time.begin - assert_equal Time.local(2006, 7, 1), time.end - end -end diff --git a/test/test_repeater_quarter_name.rb b/test/test_repeater_quarter_name.rb deleted file mode 100644 index 47999c15..00000000 --- a/test/test_repeater_quarter_name.rb +++ /dev/null @@ -1,198 +0,0 @@ -require 'helper' - -class TestRepeaterQuarterName < TestCase - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_match - %w[q1 q2 q3 q4].each do |string| - token = Chronic::Token.new(string) - repeater = Chronic::Repeater.scan_for_quarter_names(token) - assert_equal Chronic::RepeaterQuarterName, repeater.class - assert_equal string.to_sym, repeater.type - end - end - - def test_this_none - quarter = Chronic::RepeaterQuarterName.new(:q1) - quarter.start = @now - - time = quarter.this(:none) - assert_equal Time.local(2006, 1, 1), time.begin - assert_equal Time.local(2006, 4, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q2) - quarter.start = @now - - time = quarter.this(:none) - assert_equal Time.local(2006, 4, 1), time.begin - assert_equal Time.local(2006, 7, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q3) - quarter.start = @now - - time = quarter.this(:none) - assert_equal Time.local(2006, 7, 1), time.begin - assert_equal Time.local(2006, 10, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q4) - quarter.start = @now - - time = quarter.this(:none) - assert_equal Time.local(2006, 10, 1), time.begin - assert_equal Time.local(2007, 1, 1), time.end - end - - def test_this_past - quarter = Chronic::RepeaterQuarterName.new(:q1) - quarter.start = @now - - time = quarter.this(:past) - assert_equal Time.local(2006, 1, 1), time.begin - assert_equal Time.local(2006, 4, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q2) - quarter.start = @now - - time = quarter.this(:past) - assert_equal Time.local(2006, 4, 1), time.begin - assert_equal Time.local(2006, 7, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q3) - quarter.start = @now - - time = quarter.this(:past) - assert_equal Time.local(2005, 7, 1), time.begin - assert_equal Time.local(2005, 10, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q4) - quarter.start = @now - - time = quarter.this(:past) - assert_equal Time.local(2005, 10, 1), time.begin - assert_equal Time.local(2006, 1, 1), time.end - end - - def test_this_future - quarter = Chronic::RepeaterQuarterName.new(:q1) - quarter.start = @now - - time = quarter.this(:future) - assert_equal Time.local(2007, 1, 1), time.begin - assert_equal Time.local(2007, 4, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q2) - quarter.start = @now - - time = quarter.this(:future) - assert_equal Time.local(2007, 4, 1), time.begin - assert_equal Time.local(2007, 7, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q3) - quarter.start = @now - - time = quarter.this(:future) - assert_equal Time.local(2007, 7, 1), time.begin - assert_equal Time.local(2007, 10, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q4) - quarter.start = @now - - time = quarter.this(:future) - assert_equal Time.local(2006, 10, 1), time.begin - assert_equal Time.local(2007, 1, 1), time.end - end - - def test_next_future - quarter = Chronic::RepeaterQuarterName.new(:q1) - quarter.start = @now - - time = quarter.next(:future) - assert_equal Time.local(2007, 1, 1), time.begin - assert_equal Time.local(2007, 4, 1), time.end - - time = quarter.next(:future) - assert_equal Time.local(2008, 1, 1), time.begin - assert_equal Time.local(2008, 4, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q2) - quarter.start = @now - - time = quarter.next(:future) - assert_equal Time.local(2007, 4, 1), time.begin - assert_equal Time.local(2007, 7, 1), time.end - - time = quarter.next(:future) - assert_equal Time.local(2008, 4, 1), time.begin - assert_equal Time.local(2008, 7, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q3) - quarter.start = @now - - time = quarter.next(:future) - assert_equal Time.local(2007, 7, 1), time.begin - assert_equal Time.local(2007, 10, 1), time.end - - time = quarter.next(:future) - assert_equal Time.local(2008, 7, 1), time.begin - assert_equal Time.local(2008, 10, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q4) - quarter.start = @now - - time = quarter.next(:future) - assert_equal Time.local(2006, 10, 1), time.begin - assert_equal Time.local(2007, 1, 1), time.end - - time = quarter.next(:future) - assert_equal Time.local(2007, 10, 1), time.begin - assert_equal Time.local(2008, 1, 1), time.end - end - - def test_next_past - quarter = Chronic::RepeaterQuarterName.new(:q1) - quarter.start = @now - - time = quarter.next(:past) - assert_equal Time.local(2006, 1, 1), time.begin - assert_equal Time.local(2006, 4, 1), time.end - - time = quarter.next(:past) - assert_equal Time.local(2005, 1, 1), time.begin - assert_equal Time.local(2005, 4, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q2) - quarter.start = @now - - time = quarter.next(:past) - assert_equal Time.local(2006, 4, 1), time.begin - assert_equal Time.local(2006, 7, 1), time.end - - time = quarter.next(:past) - assert_equal Time.local(2005, 4, 1), time.begin - assert_equal Time.local(2005, 7, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q3) - quarter.start = @now - - time = quarter.next(:past) - assert_equal Time.local(2005, 7, 1), time.begin - assert_equal Time.local(2005, 10, 1), time.end - - time = quarter.next(:past) - assert_equal Time.local(2004, 7, 1), time.begin - assert_equal Time.local(2004, 10, 1), time.end - - quarter = Chronic::RepeaterQuarterName.new(:q4) - quarter.start = @now - - time = quarter.next(:past) - assert_equal Time.local(2005, 10, 1), time.begin - assert_equal Time.local(2006, 1, 1), time.end - - time = quarter.next(:past) - assert_equal Time.local(2004, 10, 1), time.begin - assert_equal Time.local(2005, 1, 1), time.end - end -end diff --git a/test/test_repeater_season.rb b/test/test_repeater_season.rb deleted file mode 100644 index e76fa72c..00000000 --- a/test/test_repeater_season.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'helper' - -class TestRepeaterSeason < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next_future - seasons = Chronic::RepeaterSeason.new(:season) - seasons.start = @now - - next_season = seasons.next(:future) - assert_equal Time.local(2006, 9, 23), next_season.begin - assert_equal Time.local(2006, 12, 21), next_season.end - end - - def test_next_past - seasons = Chronic::RepeaterSeason.new(:season) - seasons.start = @now - - last_season = seasons.next(:past) - assert_equal Time.local(2006, 3, 20), last_season.begin - assert_equal Time.local(2006, 6, 20), last_season.end - end - - def test_this - seasons = Chronic::RepeaterSeason.new(:season) - seasons.start = @now - - this_season = seasons.this(:future) - assert_equal Time.local(2006, 8, 17), this_season.begin - assert_equal Time.local(2006, 9, 22), this_season.end - - this_season = seasons.this(:past) - assert_equal Time.local(2006, 6, 21), this_season.begin - assert_equal Time.local(2006, 8, 16), this_season.end - end - -end diff --git a/test/test_repeater_time.rb b/test/test_repeater_time.rb deleted file mode 100644 index 26b2bcb3..00000000 --- a/test/test_repeater_time.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'helper' - -class TestRepeaterTime < TestCase - - def setup - # Wed Aug 16 14:00:00 2006 - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_generic - assert_raises(ArgumentError) do - Chronic::RepeaterTime.new('00:01:02:03:004') - end - end - - def test_next_future - t = Chronic::RepeaterTime.new('4:00') - t.start = @now - - assert_equal Time.local(2006, 8, 16, 16), t.next(:future).begin - assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin - - t = Chronic::RepeaterTime.new('13:00') - t.start = @now - - assert_equal Time.local(2006, 8, 17, 13), t.next(:future).begin - assert_equal Time.local(2006, 8, 18, 13), t.next(:future).begin - - t = Chronic::RepeaterTime.new('0400') - t.start = @now - - assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin - assert_equal Time.local(2006, 8, 18, 4), t.next(:future).begin - - t = Chronic::RepeaterTime.new('0000') - t.start = @now - - assert_equal Time.local(2006, 8, 17, 0), t.next(:future).begin - assert_equal Time.local(2006, 8, 18, 0), t.next(:future).begin - end - - def test_next_past - t = Chronic::RepeaterTime.new('4:00') - t.start = @now - - assert_equal Time.local(2006, 8, 16, 4), t.next(:past).begin - assert_equal Time.local(2006, 8, 15, 16), t.next(:past).begin - - t = Chronic::RepeaterTime.new('13:00') - t.start = @now - - assert_equal Time.local(2006, 8, 16, 13), t.next(:past).begin - assert_equal Time.local(2006, 8, 15, 13), t.next(:past).begin - - t = Chronic::RepeaterTime.new('0:00.000') - t.start = @now - - assert_equal Time.local(2006, 8, 16, 0), t.next(:past).begin - assert_equal Time.local(2006, 8, 15, 0), t.next(:past).begin - end - - def test_type - t1 = Chronic::RepeaterTime.new('4') - assert_equal 14_400, t1.type.time - - t1 = Chronic::RepeaterTime.new('14') - assert_equal 50_400, t1.type.time - - t1 = Chronic::RepeaterTime.new('4:00') - assert_equal 14_400, t1.type.time - - t1 = Chronic::RepeaterTime.new('4:30') - assert_equal 16_200, t1.type.time - - t1 = Chronic::RepeaterTime.new('1400') - assert_equal 50_400, t1.type.time - - t1 = Chronic::RepeaterTime.new('0400') - assert_equal 14_400, t1.type.time - - t1 = Chronic::RepeaterTime.new('04') - assert_equal 14_400, t1.type.time - - t1 = Chronic::RepeaterTime.new('400') - assert_equal 14_400, t1.type.time - end - -end diff --git a/test/test_repeater_week.rb b/test/test_repeater_week.rb deleted file mode 100644 index a1767020..00000000 --- a/test/test_repeater_week.rb +++ /dev/null @@ -1,115 +0,0 @@ -require 'helper' - -class TestRepeaterWeek < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next_future - weeks = Chronic::RepeaterWeek.new(:week) - weeks.start = @now - - next_week = weeks.next(:future) - assert_equal Time.local(2006, 8, 20), next_week.begin - assert_equal Time.local(2006, 8, 27), next_week.end - - next_next_week = weeks.next(:future) - assert_equal Time.local(2006, 8, 27), next_next_week.begin - assert_equal Time.local(2006, 9, 3), next_next_week.end - end - - def test_next_past - weeks = Chronic::RepeaterWeek.new(:week) - weeks.start = @now - - last_week = weeks.next(:past) - assert_equal Time.local(2006, 8, 6), last_week.begin - assert_equal Time.local(2006, 8, 13), last_week.end - - last_last_week = weeks.next(:past) - assert_equal Time.local(2006, 7, 30), last_last_week.begin - assert_equal Time.local(2006, 8, 6), last_last_week.end - end - - def test_this_future - weeks = Chronic::RepeaterWeek.new(:week) - weeks.start = @now - - this_week = weeks.this(:future) - assert_equal Time.local(2006, 8, 16, 15), this_week.begin - assert_equal Time.local(2006, 8, 20), this_week.end - end - - def test_this_past - weeks = Chronic::RepeaterWeek.new(:week) - weeks.start = @now - - this_week = weeks.this(:past) - assert_equal Time.local(2006, 8, 13, 0), this_week.begin - assert_equal Time.local(2006, 8, 16, 14), this_week.end - end - - def test_offset - span = Chronic::Span.new(@now, @now + 1) - - offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future) - - assert_equal Time.local(2006, 9, 6, 14), offset_span.begin - assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end - end - - def test_next_future_starting_on_monday - weeks = Chronic::RepeaterWeek.new(:week, nil, :week_start => :monday) - weeks.start = @now - - next_week = weeks.next(:future) - assert_equal Time.local(2006, 8, 21), next_week.begin - assert_equal Time.local(2006, 8, 28), next_week.end - - next_next_week = weeks.next(:future) - assert_equal Time.local(2006, 8, 28), next_next_week.begin - assert_equal Time.local(2006, 9, 4), next_next_week.end - end - - def test_next_past_starting_on_monday - weeks = Chronic::RepeaterWeek.new(:week, nil, :week_start => :monday) - weeks.start = @now - - last_week = weeks.next(:past) - assert_equal Time.local(2006, 8, 7), last_week.begin - assert_equal Time.local(2006, 8, 14), last_week.end - - last_last_week = weeks.next(:past) - assert_equal Time.local(2006, 7, 31), last_last_week.begin - assert_equal Time.local(2006, 8, 7), last_last_week.end - end - - def test_this_future_starting_on_monday - weeks = Chronic::RepeaterWeek.new(:week, nil, :week_start => :monday) - weeks.start = @now - - this_week = weeks.this(:future) - assert_equal Time.local(2006, 8, 16, 15), this_week.begin - assert_equal Time.local(2006, 8, 21), this_week.end - end - - def test_this_past_starting_on_monday - weeks = Chronic::RepeaterWeek.new(:week, nil, :week_start => :monday) - weeks.start = @now - - this_week = weeks.this(:past) - assert_equal Time.local(2006, 8, 14, 0), this_week.begin - assert_equal Time.local(2006, 8, 16, 14), this_week.end - end - - def test_offset_starting_on_monday - weeks = Chronic::RepeaterWeek.new(:week, nil, :week_start => :monday) - - span = Chronic::Span.new(@now, @now + 1) - offset_span = weeks.offset(span, 3, :future) - - assert_equal Time.local(2006, 9, 6, 14), offset_span.begin - assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end - end -end diff --git a/test/test_repeater_weekday.rb b/test/test_repeater_weekday.rb deleted file mode 100644 index af981256..00000000 --- a/test/test_repeater_weekday.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'helper' - -class TestRepeaterWeekday < TestCase - - def setup - @now = Time.local(2007, 6, 11, 14, 0, 0, 0) # Mon - end - - def test_next_future - weekdays = Chronic::RepeaterWeekday.new(:weekday) - weekdays.start = @now - - next1_weekday = weekdays.next(:future) # Tues - assert_equal Time.local(2007, 6, 12), next1_weekday.begin - assert_equal Time.local(2007, 6, 13), next1_weekday.end - - next2_weekday = weekdays.next(:future) # Wed - assert_equal Time.local(2007, 6, 13), next2_weekday.begin - assert_equal Time.local(2007, 6, 14), next2_weekday.end - - next3_weekday = weekdays.next(:future) # Thurs - assert_equal Time.local(2007, 6, 14), next3_weekday.begin - assert_equal Time.local(2007, 6, 15), next3_weekday.end - - next4_weekday = weekdays.next(:future) # Fri - assert_equal Time.local(2007, 6, 15), next4_weekday.begin - assert_equal Time.local(2007, 6, 16), next4_weekday.end - - next5_weekday = weekdays.next(:future) # Mon - assert_equal Time.local(2007, 6, 18), next5_weekday.begin - assert_equal Time.local(2007, 6, 19), next5_weekday.end - end - - def test_next_past - weekdays = Chronic::RepeaterWeekday.new(:weekday) - weekdays.start = @now - - last1_weekday = weekdays.next(:past) # Fri - assert_equal Time.local(2007, 6, 8), last1_weekday.begin - assert_equal Time.local(2007, 6, 9), last1_weekday.end - - last2_weekday = weekdays.next(:past) # Thurs - assert_equal Time.local(2007, 6, 7), last2_weekday.begin - assert_equal Time.local(2007, 6, 8), last2_weekday.end - end - - def test_offset - span = Chronic::Span.new(@now, @now + 1) - - offset_span = Chronic::RepeaterWeekday.new(:weekday).offset(span, 5, :future) - - assert_equal Time.local(2007, 6, 18, 14), offset_span.begin - assert_equal Time.local(2007, 6, 18, 14, 0, 1), offset_span.end - end -end diff --git a/test/test_repeater_weekend.rb b/test/test_repeater_weekend.rb deleted file mode 100644 index 8727c5de..00000000 --- a/test/test_repeater_weekend.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'helper' - -class TestRepeaterWeekend < TestCase - - def setup - # Wed Aug 16 14:00:00 2006 - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next_future - weekend = Chronic::RepeaterWeekend.new(:weekend) - weekend.start = @now - - next_weekend = weekend.next(:future) - assert_equal Time.local(2006, 8, 19), next_weekend.begin - assert_equal Time.local(2006, 8, 21), next_weekend.end - end - - def test_next_past - weekend = Chronic::RepeaterWeekend.new(:weekend) - weekend.start = @now - - next_weekend = weekend.next(:past) - assert_equal Time.local(2006, 8, 12), next_weekend.begin - assert_equal Time.local(2006, 8, 14), next_weekend.end - end - - def test_this_future - weekend = Chronic::RepeaterWeekend.new(:weekend) - weekend.start = @now - - next_weekend = weekend.this(:future) - assert_equal Time.local(2006, 8, 19), next_weekend.begin - assert_equal Time.local(2006, 8, 21), next_weekend.end - end - - def test_this_past - weekend = Chronic::RepeaterWeekend.new(:weekend) - weekend.start = @now - - next_weekend = weekend.this(:past) - assert_equal Time.local(2006, 8, 12), next_weekend.begin - assert_equal Time.local(2006, 8, 14), next_weekend.end - end - - def test_this_none - weekend = Chronic::RepeaterWeekend.new(:weekend) - weekend.start = @now - - next_weekend = weekend.this(:future) - assert_equal Time.local(2006, 8, 19), next_weekend.begin - assert_equal Time.local(2006, 8, 21), next_weekend.end - end - - def test_offset - span = Chronic::Span.new(@now, @now + 1) - - offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 3, :future) - - assert_equal Time.local(2006, 9, 2), offset_span.begin - assert_equal Time.local(2006, 9, 2, 0, 0, 1), offset_span.end - - offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 1, :past) - - assert_equal Time.local(2006, 8, 12), offset_span.begin - assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end - - offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 0, :future) - - assert_equal Time.local(2006, 8, 12), offset_span.begin - assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end - end - -end diff --git a/test/test_repeater_year.rb b/test/test_repeater_year.rb deleted file mode 100644 index 916ef04c..00000000 --- a/test/test_repeater_year.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'helper' - -class TestRepeaterYear < TestCase - - def setup - @now = Time.local(2006, 8, 16, 14, 0, 0, 0) - end - - def test_next_future - years = Chronic::RepeaterYear.new(:year) - years.start = @now - - next_year = years.next(:future) - assert_equal Time.local(2007, 1, 1), next_year.begin - assert_equal Time.local(2008, 1, 1), next_year.end - - next_next_year = years.next(:future) - assert_equal Time.local(2008, 1, 1), next_next_year.begin - assert_equal Time.local(2009, 1, 1), next_next_year.end - end - - def test_next_past - years = Chronic::RepeaterYear.new(:year) - years.start = @now - - last_year = years.next(:past) - assert_equal Time.local(2005, 1, 1), last_year.begin - assert_equal Time.local(2006, 1, 1), last_year.end - - last_last_year = years.next(:past) - assert_equal Time.local(2004, 1, 1), last_last_year.begin - assert_equal Time.local(2005, 1, 1), last_last_year.end - end - - def test_this - years = Chronic::RepeaterYear.new(:year) - years.start = @now - - this_year = years.this(:future) - assert_equal Time.local(2006, 8, 17), this_year.begin - assert_equal Time.local(2007, 1, 1), this_year.end - - this_year = years.this(:past) - assert_equal Time.local(2006, 1, 1), this_year.begin - assert_equal Time.local(2006, 8, 16), this_year.end - end - - def test_offset - span = Chronic::Span.new(@now, @now + 1) - - offset_span = Chronic::RepeaterYear.new(:year).offset(span, 3, :future) - - assert_equal Time.local(2009, 8, 16, 14), offset_span.begin - assert_equal Time.local(2009, 8, 16, 14, 0, 1), offset_span.end - - offset_span = Chronic::RepeaterYear.new(:year).offset(span, 10, :past) - - assert_equal Time.local(1996, 8, 16, 14), offset_span.begin - assert_equal Time.local(1996, 8, 16, 14, 0, 1), offset_span.end - - now = Time.local(2008, 2, 29) - span = Chronic::Span.new(now, now + 1) - offset_span = Chronic::RepeaterYear.new(:year).offset(span, 1, :past) - - assert_equal Time.local(2007, 2, 28), offset_span.begin - assert_equal Time.local(2007, 2, 28, 0, 0, 1), offset_span.end - end - -end From a260544ef30d0abf07e446fc39ade54e13a0fef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:15:12 +0300 Subject: [PATCH 08/23] Remove pre_normalize regexp's --- lib/chronic/parser.rb | 63 ++----------------------------------------- test/test_parsing.rb | 8 ------ 2 files changed, 2 insertions(+), 69 deletions(-) diff --git a/lib/chronic/parser.rb b/lib/chronic/parser.rb index a11fa5ad..fee687ec 100644 --- a/lib/chronic/parser.rb +++ b/lib/chronic/parser.rb @@ -76,74 +76,15 @@ def parse(text) # Clean up the specified text ready for parsing. # - # Clean up the string by stripping unwanted characters, converting - # idioms to their canonical form, converting number words to numbers - # (three => 3), and converting ordinal words to numeric + # Clean up the string, convert number words to numbers + # (three => 3), and convert ordinal words to numeric # ordinals (third => 3rd) # # text - The String text to normalize. # - # Examples: - # - # Chronic.pre_normalize('first day in May') - # #=> "1st day in may" - # - # Chronic.pre_normalize('tomorrow after noon') - # #=> "next day future 12:00" - # - # Chronic.pre_normalize('one hundred and thirty six days from now') - # #=> "136 days future this second" - # # Returns a new String ready for Chronic to parse. def pre_normalize(text) - text = text.to_s.downcase - text.gsub!(/\b(\d{2})\.(\d{2})\.(\d{4})\b/, '\3 / \2 / \1') - text.gsub!(/\b([ap])\.m\.?/, '\1m') - text.gsub!(/(\s+|:\d{2}|:\d{2}\.\d+)\-(\d{2}:?\d{2})\b/, '\1tzminus\2') - text.gsub!(/\./, ':') - text.gsub!(/([ap]):m:?/, '\1m') - text.gsub!(/'(\d{2})\b/) do - number = $1.to_i - - if Chronic::Date::could_be_year?(number) - Chronic::Date::make_year(number, options[:ambiguous_year_future_bias]) - else - number - end - end - text.gsub!(/['"]/, '') - text.gsub!(/,/, ' ') - text.gsub!(/^second /, '2nd ') - text.gsub!(/\bsecond (of|day|month|hour|minute|second|quarter)\b/, '2nd \1') - text.gsub!(/\bthird quarter\b/, '3rd q') - text.gsub!(/\bfourth quarter\b/, '4th q') - text.gsub!(/quarters?(\s+|$)(?!to|till|past|after|before)/, 'q\1') text = Numerizer.numerize(text) - text.gsub!(/\b(\d)(?:st|nd|rd|th)\s+q\b/, 'q\1') - text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' } - text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1') - text.gsub!(/\btoday\b/, 'this day') - text.gsub!(/\btomm?orr?ow\b/, 'next day') - text.gsub!(/\byesterday\b/, 'last day') - text.gsub!(/\bnoon|midday\b/, '12:00pm') - text.gsub!(/\bmidnight\b/, '24:00') - text.gsub!(/\bnow\b/, 'this second') - text.gsub!('quarter', '15') - text.gsub!('half', '30') - text.gsub!(/(\d{1,2}) (to|till|prior to|before)\b/, '\1 minutes past') - text.gsub!(/(\d{1,2}) (after|past)\b/, '\1 minutes future') - text.gsub!(/\b(?:ago|before(?: now)?)\b/, 'past') - text.gsub!(/\bthis (?:last|past)\b/, 'last') - text.gsub!(/\b(?:in|during) the (morning)\b/, '\1') - text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1') - text.gsub!(/\btonight\b/, 'this night') - text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m') - text.gsub!(/\b(\d{2})(\d{2})(am|pm)\b/, '\1:\2\3') - text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2') - text.gsub!(/\b(hence|after|from)\b/, 'future') - text.gsub!(/^\s?an? /i, '1 ') - text.gsub!(/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3') # DTOriginal - text.gsub!(/\b0(\d+):(\d{2}):(\d{2}) ([ap]m)\b/, '\1:\2:\3 \4') text end diff --git a/test/test_parsing.rb b/test/test_parsing.rb index 982ec481..4401991f 100644 --- a/test/test_parsing.rb +++ b/test/test_parsing.rb @@ -1512,14 +1512,6 @@ def test_handle_rdn_rmn_od_sy assert_equal Time.local(2005, 12, 30, 12), time end - def test_normalizing_day_portions - assert_equal pre_normalize("8:00 pm February 11"), pre_normalize("8:00 p.m. February 11") - end - - def test_normalizing_time_of_day_phrases - assert_equal pre_normalize("midday February 11"), pre_normalize("12:00 p.m. February 11") - end - private def parse_now(string, options={}) Chronic.parse(string, {:now => TIME_2006_08_16_14_00_00 }.merge(options)) From b9f0fa5c63dd74207802c6c0af90f8dc7b1fb543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:17:17 +0300 Subject: [PATCH 09/23] Add `pre_proccess` step to reduce whitespace to single character --- lib/chronic/parser.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/chronic/parser.rb b/lib/chronic/parser.rb index fee687ec..59c74edb 100644 --- a/lib/chronic/parser.rb +++ b/lib/chronic/parser.rb @@ -61,6 +61,7 @@ def initialize(options = {}) # Parse "text" with the given options # Returns either a Time or Chronic::Span, depending on the value of options[:guess] def parse(text) + text = pre_proccess(text) text = pre_normalize(text) puts text.inspect if Chronic.debug @@ -71,9 +72,15 @@ def parse(text) token_group = TokenGroup.new(tokens, definitions(options), @now, options) span = token_group.to_span + guess(span, options[:guess]) if span end + # Replace any whitespace characters to single space + def pre_proccess(text) + text.to_s.strip.gsub(/[[:space:]]+/, ' ').gsub(/\s{2,}/, ' ') + end + # Clean up the specified text ready for parsing. # # Clean up the string, convert number words to numbers From 0330f1a9cb2153eddb825259c3109c0cc22fe69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:18:58 +0300 Subject: [PATCH 10/23] Set Span to be exclusive so that it doesn't include Span's end --- lib/chronic/span.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chronic/span.rb b/lib/chronic/span.rb index 1ac8a786..a7925a41 100644 --- a/lib/chronic/span.rb +++ b/lib/chronic/span.rb @@ -23,7 +23,7 @@ def -(seconds) # Prints this span in a nice fashion def to_s - '(' << self.begin.to_s << '..' << self.end.to_s << ')' + '(' << self.begin.to_s << '...' << self.end.to_s << ')' end alias :cover? :include? if RUBY_VERSION =~ /^1.8/ From e1af12294fae4a32e395eb2a0d7029356198b63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:23:50 +0300 Subject: [PATCH 11/23] Fix test times as Span's range is determined by precision --- test/test_parsing.rb | 184 +++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/test/test_parsing.rb b/test/test_parsing.rb index 4401991f..e4d00467 100644 --- a/test/test_parsing.rb +++ b/test/test_parsing.rb @@ -73,10 +73,10 @@ def test_handle_rmn_sd assert_equal Time.local(2006, 5, 28, 12), time time = parse_now("may 28 5pm", :context => :past) - assert_equal Time.local(2006, 5, 28, 17), time + assert_equal Time.local(2006, 5, 28, 17, 30), time time = parse_now("may 28 at 5pm", :context => :past) - assert_equal Time.local(2006, 5, 28, 17), time + assert_equal Time.local(2006, 5, 28, 17, 30), time time = parse_now("may 28 at 5:32.19pm", :context => :past) assert_equal Time.local(2006, 5, 28, 17, 32, 19), time @@ -87,13 +87,13 @@ def test_handle_rmn_sd def test_handle_rmn_sd_on time = parse_now("5pm on may 28") - assert_equal Time.local(2007, 5, 28, 17), time + assert_equal Time.local(2007, 5, 28, 17, 30), time time = parse_now("5pm may 28") - assert_equal Time.local(2007, 5, 28, 17), time + assert_equal Time.local(2007, 5, 28, 17, 30), time time = parse_now("5 on may 28", :ambiguous_time_range => :none) - assert_equal Time.local(2007, 5, 28, 05), time + assert_equal Time.local(2007, 5, 28, 05, 30), time end def test_handle_rmn_od @@ -104,13 +104,13 @@ def test_handle_rmn_od assert_equal Time.local(2006, 5, 27, 12), time time = parse_now("may 27th 5:00 pm", :context => :past) - assert_equal Time.local(2006, 5, 27, 17), time + assert_equal Time.local(2006, 5, 27, 17, 0, 30), time time = parse_now("may 27th at 5pm", :context => :past) - assert_equal Time.local(2006, 5, 27, 17), time + assert_equal Time.local(2006, 5, 27, 17, 30), time time = parse_now("may 27th at 5", :ambiguous_time_range => :none) - assert_equal Time.local(2007, 5, 27, 5), time + assert_equal Time.local(2007, 5, 27, 5, 30), time end def test_handle_od_rm @@ -123,10 +123,10 @@ def test_handle_od_rmn assert_equal Time.local(2007, 2, 22, 12), time time = parse_now("31st of may at 6:30pm") - assert_equal Time.local(2007, 5, 31, 18, 30), time + assert_equal Time.local(2007, 5, 31, 18, 30, 30), time time = parse_now("11th december 8am") - assert_equal Time.local(2006, 12, 11, 8), time + assert_equal Time.local(2006, 12, 11, 8, 30), time end def test_handle_sy_rmn_od @@ -145,24 +145,24 @@ def test_handle_sd_rmn assert_equal Time.local(2007, 2, 22, 12), time time = parse_now("31 of may at 6:30pm") - assert_equal Time.local(2007, 5, 31, 18, 30), time + assert_equal Time.local(2007, 5, 31, 18, 30, 30), time time = parse_now("11 december 8am") - assert_equal Time.local(2006, 12, 11, 8), time + assert_equal Time.local(2006, 12, 11, 8, 30), time end def test_handle_rmn_od_on time = parse_now("5:00 pm may 27th", :context => :past) - assert_equal Time.local(2006, 5, 27, 17), time + assert_equal Time.local(2006, 5, 27, 17, 0, 30), time time = parse_now("05:00 pm may 27th", :context => :past) - assert_equal Time.local(2006, 5, 27, 17), time + assert_equal Time.local(2006, 5, 27, 17, 0, 30), time time = parse_now("5pm on may 27th", :context => :past) - assert_equal Time.local(2006, 5, 27, 17), time + assert_equal Time.local(2006, 5, 27, 17, 30), time time = parse_now("5 on may 27th", :ambiguous_time_range => :none) - assert_equal Time.local(2007, 5, 27, 5), time + assert_equal Time.local(2007, 5, 27, 5, 30), time end def test_handle_rmn_sy @@ -221,7 +221,7 @@ def test_handle_rmn_sd_sy assert_equal Time.local(2010, 1, 4, 0), time time = parse_now("jan 3 2010 at 4", :ambiguous_time_range => :none) - assert_equal Time.local(2010, 1, 3, 4), time + assert_equal Time.local(2010, 1, 3, 4, 30), time time = parse_now("may 27, 1979") assert_equal Time.local(1979, 5, 27, 12), time @@ -230,16 +230,16 @@ def test_handle_rmn_sd_sy assert_equal Time.local(1979, 5, 27, 12), time time = parse_now("may 27 79 4:30") - assert_equal Time.local(1979, 5, 27, 16, 30), time + assert_equal Time.local(1979, 5, 27, 16, 30, 30), time time = parse_now("may 27 79 at 4:30", :ambiguous_time_range => :none) - assert_equal Time.local(1979, 5, 27, 4, 30), time + assert_equal Time.local(1979, 5, 27, 4, 30, 30), time time = parse_now("may 27 32") assert_equal Time.local(2032, 5, 27, 12, 0, 0), time time = parse_now("oct 5 2012 1045pm") - assert_equal Time.local(2012, 10, 5, 22, 45), time + assert_equal Time.local(2012, 10, 5, 22, 45, 30), time end def test_handle_rmn_od_sy @@ -259,10 +259,10 @@ def test_handle_rmn_od_sy assert_equal Time.local(2010, 11, 19, 0), time time = parse_now("November 18th 2010 at 4") - assert_equal Time.local(2010, 11, 18, 16), time + assert_equal Time.local(2010, 11, 18, 16, 30), time time = parse_now("November 18th 2010 at 4", :ambiguous_time_range => :none) - assert_equal Time.local(2010, 11, 18, 4), time + assert_equal Time.local(2010, 11, 18, 4, 30), time time = parse_now("March 30th, 1979") assert_equal Time.local(1979, 3, 30, 12), time @@ -271,10 +271,10 @@ def test_handle_rmn_od_sy assert_equal Time.local(1979, 3, 30, 12), time time = parse_now("March 30th 79 4:30") - assert_equal Time.local(1979, 3, 30, 16, 30), time + assert_equal Time.local(1979, 3, 30, 16, 30, 30), time time = parse_now("March 30th 79 at 4:30", :ambiguous_time_range => :none) - assert_equal Time.local(1979, 3, 30, 4, 30), time + assert_equal Time.local(1979, 3, 30, 4, 30, 30), time end def test_handle_od_rmn_sy @@ -290,10 +290,10 @@ def test_handle_sd_rmn_sy assert_equal Time.local(2010, 1, 3, 12), time time = parse_now("3 jan 2010 4pm") - assert_equal Time.local(2010, 1, 3, 16), time + assert_equal Time.local(2010, 1, 3, 16, 30), time time = parse_now("27 Oct 2006 7:30pm") - assert_equal Time.local(2006, 10, 27, 19, 30), time + assert_equal Time.local(2006, 10, 27, 19, 30, 30), time time = parse_now("3 jan 10") assert_equal Time.local(2010, 1, 3, 12), time @@ -310,7 +310,7 @@ def test_handle_sm_sd_sy assert_equal Time.local(1979, 5, 27, 12), time time = parse_now("5/27/1979 4am") - assert_equal Time.local(1979, 5, 27, 4), time + assert_equal Time.local(1979, 5, 27, 4, 30), time time = parse_now("7/12/11") assert_equal Time.local(2011, 7, 12, 12), time @@ -334,10 +334,10 @@ def test_handle_sd_sm_sy assert_equal Time.local(1979, 5, 27, 12), time time = parse_now("27/5/1979 @ 0700") - assert_equal Time.local(1979, 5, 27, 7), time + assert_equal Time.local(1979, 5, 27, 7, 0, 30), time time = parse_now("03/18/2012 09:26 pm") - assert_equal Time.local(2012, 3, 18, 21, 26), time + assert_equal Time.local(2012, 3, 18, 21, 26, 30), time time = parse_now("30.07.2013 16:34:22") assert_equal Time.local(2013, 7, 30, 16, 34, 22), time @@ -357,10 +357,10 @@ def test_handle_sy_sm_sd assert_equal Time.local(2006, 8, 20, 12), time time = parse_now("2006-08-20 7pm") - assert_equal Time.local(2006, 8, 20, 19), time + assert_equal Time.local(2006, 8, 20, 19, 30), time time = parse_now("2006-08-20 03:00") - assert_equal Time.local(2006, 8, 20, 3), time + assert_equal Time.local(2006, 8, 20, 3, 0, 30), time time = parse_now("2006-08-20 03:30:30") assert_equal Time.local(2006, 8, 20, 3, 30, 30), time @@ -463,7 +463,7 @@ def test_handle_sy_sm def test_handle_r time = parse_now("9am on Saturday") - assert_equal Time.local(2006, 8, 19, 9), time + assert_equal Time.local(2006, 8, 19, 9, 30), time time = parse_now("on Tuesday") assert_equal Time.local(2006, 8, 22, 12), time @@ -571,22 +571,22 @@ def test_parse_guess_r assert_equal Time.local(2006, 8, 22, 12), time time = parse_now("5") - assert_equal Time.local(2006, 8, 16, 17), time + assert_equal Time.local(2006, 8, 16, 17, 30), time time = Chronic.parse("5", :now => Time.local(2006, 8, 16, 3, 0, 0, 0), :ambiguous_time_range => :none, :guess => :begin) assert_equal Time.local(2006, 8, 16, 5), time time = parse_now("13:00") - assert_equal Time.local(2006, 8, 17, 13), time + assert_equal Time.local(2006, 8, 17, 13, 0, 30), time time = parse_now("13:45") - assert_equal Time.local(2006, 8, 17, 13, 45), time + assert_equal Time.local(2006, 8, 17, 13, 45, 30), time time = parse_now("1:01pm", :context => :past) - assert_equal Time.local(2006, 8, 16, 13, 01), time + assert_equal Time.local(2006, 8, 16, 13, 01, 30), time time = parse_now("2:01pm", :context => :none) - assert_equal Time.local(2006, 8, 16, 14, 01), time + assert_equal Time.local(2006, 8, 16, 14, 01, 30), time time = parse_now("november") assert_equal Time.local(2006, 11, 16), time @@ -597,37 +597,37 @@ def test_parse_guess_rr assert_equal Time.local(2006, 8, 18, 13), time time = parse_now("monday 4:00") - assert_equal Time.local(2006, 8, 21, 16), time + assert_equal Time.local(2006, 8, 21, 16, 0, 30), time time = parse_now("sat 4:00", :ambiguous_time_range => :none, :guess => :begin) assert_equal Time.local(2006, 8, 19, 4), time time = parse_now("sunday 4:20", :ambiguous_time_range => :none) - assert_equal Time.local(2006, 8, 20, 4, 20), time + assert_equal Time.local(2006, 8, 20, 4, 20, 30), time time = parse_now("4 pm") - assert_equal Time.local(2006, 8, 16, 16), time + assert_equal Time.local(2006, 8, 16, 16, 30), time time = parse_now("4 am", :ambiguous_time_range => :none) - assert_equal Time.local(2006, 8, 16, 4), time + assert_equal Time.local(2006, 8, 17, 4, 30), time time = parse_now("12 pm") - assert_equal Time.local(2006, 8, 16, 12), time + assert_equal Time.local(2006, 8, 17, 12, 30), time time = parse_now("12:01 pm") - assert_equal Time.local(2006, 8, 16, 12, 1), time + assert_equal Time.local(2006, 8, 17, 12, 1, 30), time time = parse_now("12:01 am") - assert_equal Time.local(2006, 8, 16, 0, 1), time + assert_equal Time.local(2006, 8, 17, 0, 1, 30), time time = parse_now("12 am") - assert_equal Time.local(2006, 8, 16), time + assert_equal Time.local(2006, 8, 17, 0, 30), time time = parse_now("4:00 in the morning") - assert_equal Time.local(2006, 8, 16, 4), time + assert_equal Time.local(2006, 8, 17, 4, 0, 30), time time = parse_now("0:10") - assert_equal Time.local(2006, 8, 17, 0, 10), time + assert_equal Time.local(2006, 8, 17, 0, 10, 30), time time = parse_now("november 4") assert_equal Time.local(2006, 11, 4, 12), time @@ -638,19 +638,19 @@ def test_parse_guess_rr def test_parse_guess_rrr time = parse_now("friday 1 pm") - assert_equal Time.local(2006, 8, 18, 13), time + assert_equal Time.local(2006, 8, 18, 13, 30), time time = parse_now("friday 11 at night") - assert_equal Time.local(2006, 8, 18, 23), time + assert_equal Time.local(2006, 8, 18, 23, 30), time time = parse_now("friday 11 in the evening") - assert_equal Time.local(2006, 8, 18, 23), time + assert_equal Time.local(2006, 8, 18, 23, 30), time time = parse_now("sunday 6am") - assert_equal Time.local(2006, 8, 20, 6), time + assert_equal Time.local(2006, 8, 20, 6, 30), time time = parse_now("friday evening at 7") - assert_equal Time.local(2006, 8, 18, 19), time + assert_equal Time.local(2006, 8, 18, 19, 30), time end def test_parse_guess_gr @@ -665,10 +665,10 @@ def test_parse_guess_gr # month time = parse_now("this month") - assert_equal Time.local(2006, 8, 24, 12), time + assert_equal Time.local(2006, 8, 24, 7), time time = parse_now("this month", :context => :past) - assert_equal Time.local(2006, 8, 8, 12), time + assert_equal Time.local(2006, 8, 8, 19), time time = Chronic.parse("next month", :now => Time.local(2006, 11, 15)) assert_equal Time.local(2006, 12, 16, 12), time @@ -681,15 +681,15 @@ def test_parse_guess_gr # fortnight time = parse_now("this fortnight") - assert_equal Time.local(2006, 8, 21, 19, 30), time + assert_equal Time.local(2006, 8, 21, 19), time time = parse_now("this fortnight", :context => :past) - assert_equal Time.local(2006, 8, 14, 19), time + assert_equal Time.local(2006, 8, 11, 7), time # week time = parse_now("this week") - assert_equal Time.local(2006, 8, 18, 7, 30), time + assert_equal Time.local(2006, 8, 18, 7, 0), time time = parse_now("this week", :context => :past) assert_equal Time.local(2006, 8, 14, 19), time @@ -830,28 +830,28 @@ def test_parse_guess_gr def test_parse_guess_grr time = parse_now("yesterday at 4:00") - assert_equal Time.local(2006, 8, 15, 16), time + assert_equal Time.local(2006, 8, 15, 16, 0, 30), time - time = parse_now("today at 9:00") - assert_equal Time.local(2006, 8, 16, 9), time + time = parse_now("today at 9:00", :hours24 => true) + assert_equal Time.local(2006, 8, 16, 9, 0, 30), time time = parse_now("today at 2100") - assert_equal Time.local(2006, 8, 16, 21), time + assert_equal Time.local(2006, 8, 16, 21, 0, 30), time time = parse_now("this day at 0900", :guess => :begin) assert_equal Time.local(2006, 8, 16, 9), time time = parse_now("tomorrow at 0900") - assert_equal Time.local(2006, 8, 17, 9), time + assert_equal Time.local(2006, 8, 17, 9, 0, 30), time time = parse_now("yesterday at 4:00", :ambiguous_time_range => :none) - assert_equal Time.local(2006, 8, 15, 4), time + assert_equal Time.local(2006, 8, 15, 4, 0, 30), time time = parse_now("last friday at 4:00") - assert_equal Time.local(2006, 8, 11, 16), time + assert_equal Time.local(2006, 8, 11, 16, 0, 30), time time = parse_now("next wed 4:00") - assert_equal Time.local(2006, 8, 23, 16), time + assert_equal Time.local(2006, 8, 23, 16, 0, 30), time time = parse_now("yesterday afternoon") assert_equal Time.local(2006, 8, 15, 15), time @@ -860,43 +860,43 @@ def test_parse_guess_grr assert_equal Time.local(2006, 8, 8, 12), time time = parse_now("tonight at 7") - assert_equal Time.local(2006, 8, 16, 19), time + assert_equal Time.local(2006, 8, 16, 19, 30), time time = parse_now("tonight 7") - assert_equal Time.local(2006, 8, 16, 19), time + assert_equal Time.local(2006, 8, 16, 19, 30), time time = parse_now("7 tonight") - assert_equal Time.local(2006, 8, 16, 19), time + assert_equal Time.local(2006, 8, 16, 19, 30), time end def test_parse_guess_grrr time = parse_now("today at 6:00pm") - assert_equal Time.local(2006, 8, 16, 18), time + assert_equal Time.local(2006, 8, 16, 18, 0, 30), time time = parse_now("today at 6:00am") - assert_equal Time.local(2006, 8, 16, 6), time + assert_equal Time.local(2006, 8, 16, 6, 0, 30), time time = parse_now("this day 1800") - assert_equal Time.local(2006, 8, 16, 18), time + assert_equal Time.local(2006, 8, 16, 18, 0, 30), time time = parse_now("yesterday at 4:00pm") - assert_equal Time.local(2006, 8, 15, 16), time + assert_equal Time.local(2006, 8, 15, 16, 0, 30), time time = parse_now("tomorrow evening at 7") - assert_equal Time.local(2006, 8, 17, 19), time + assert_equal Time.local(2006, 8, 17, 19, 30), time time = parse_now("tomorrow morning at 5:30") - assert_equal Time.local(2006, 8, 17, 5, 30), time + assert_equal Time.local(2006, 8, 17, 5, 30, 30), time time = parse_now("next monday at 12:01 am") - assert_equal Time.local(2006, 8, 21, 00, 1), time + assert_equal Time.local(2006, 8, 21, 00, 1, 30), time time = parse_now("next monday at 12:01 pm") - assert_equal Time.local(2006, 8, 21, 12, 1), time + assert_equal Time.local(2006, 8, 21, 12, 1, 30), time # with context time = parse_now("sunday at 8:15pm", :context => :past) - assert_equal Time.local(2006, 8, 13, 20, 15), time + assert_equal Time.local(2006, 8, 13, 20, 15, 30), time end def test_parse_guess_rgr @@ -1024,7 +1024,7 @@ def test_parse_guess_s_r_p_a assert_equal Time.local(2003, 8, 22, 12), time time = parse_now("3 months ago saturday at 5:00 pm") - assert_equal Time.local(2006, 5, 19, 17), time + assert_equal Time.local(2006, 5, 19, 17, 0, 30), time time = parse_now("2 days from this second") assert_equal Time.local(2006, 8, 18, 14), time @@ -1106,13 +1106,13 @@ def test_parse_words def test_relative_to_an_hour_before # example prenormalization "10 to 2" becomes "10 minutes past 2" - assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 to 2") - assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 till 2") - assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 before 2") + assert_equal Time.local(2006, 8, 16, 13, 50, 30), parse_now("10 to 2") + assert_equal Time.local(2006, 8, 16, 13, 50, 30), parse_now("10 till 2") assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 prior to 2", :guess => :begin) + assert_equal Time.local(2006, 8, 16, 13, 50, 30), parse_now("10 before 2") # uses the current hour, so 2006-08-16 13:50:00, not 14:50 - assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 to") + assert_equal Time.local(2006, 8, 16, 13, 50, 30), parse_now("10 to") assert_equal Time.local(2006, 8, 16, 13, 50), parse_now("10 till", :guess => :begin) assert_equal Time.local(2006, 8, 16, 15, 45), parse_now("quarter to 4", :guess => :begin) @@ -1132,13 +1132,13 @@ def test_parse_only_complete_pointers end def test_am_pm - assert_equal Time.local(2006, 8, 16), parse_now("8/16/2006 at 12am") - assert_equal Time.local(2006, 8, 16, 12), parse_now("8/16/2006 at 12pm") + assert_equal Time.local(2006, 8, 16, 0, 30), parse_now("8/16/2006 at 12am") + assert_equal Time.local(2006, 8, 16, 12, 30), parse_now("8/16/2006 at 12pm") end def test_a_p - assert_equal Time.local(2006, 8, 16, 0, 15), parse_now("8/16/2006 at 12:15a") - assert_equal Time.local(2006, 8, 16, 18, 30), parse_now("8/16/2006 at 6:30p") + assert_equal Time.local(2006, 8, 16, 0, 15, 30), parse_now("8/16/2006 at 12:15a") + assert_equal Time.local(2006, 8, 16, 18, 30, 30), parse_now("8/16/2006 at 6:30p") end def test_seasons @@ -1443,29 +1443,29 @@ def test_handle_rdn_rmn_sd def test_handle_rdn_rmn_sd_rt time = parse_now("Thu Aug 10 4pm") - assert_equal Time.local(2006, 8, 10, 16), time + assert_equal Time.local(2006, 8, 10, 16, 30), time time = parse_now("Thu Aug 10 at 4pm") - assert_equal Time.local(2006, 8, 10, 16), time + assert_equal Time.local(2006, 8, 10, 16, 30), time end def test_handle_rdn_rmn_od_rt time = parse_now("Thu Aug 10th at 4pm") - assert_equal Time.local(2006, 8, 10, 16), time + assert_equal Time.local(2006, 8, 10, 16, 30), time end def test_handle_rdn_od_rt time = parse_now("Thu 17th at 4pm") - assert_equal Time.local(2006, 8, 17, 16), time + assert_equal Time.local(2006, 8, 17, 16, 30), time time = parse_now("Sat 16th at 4pm", :guess => :begin) assert_equal Time.local(2006, 9, 16, 16), time time = parse_now("Thu 1st at 4pm") - assert_equal Time.local(2006, 9, 1, 16), time + assert_equal Time.local(2007, 2, 1, 16, 30), time time = parse_now("Tue 1st at 4pm", :context => :past) - assert_equal Time.local(2006, 8, 1, 16), time + assert_equal Time.local(2006, 8, 1, 16, 30), time end def test_handle_rdn_od From 6b5849e0963a5f0002aa48915b1987e8f9dea5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 03:32:27 +0300 Subject: [PATCH 12/23] Don't match partial date looking string --- lib/chronic/definition.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index 0dbb2909..fc8b7120 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -162,14 +162,14 @@ def prefered_endian options[:endian_precedence] ||= [:middle, :little] middle = [ - [[ScalarMonth, SeparatorSlash, ScalarDay, SeparatorSlash, ScalarYear], :handle_sm_sd_sy], - [[ScalarMonth, SeparatorDash, ScalarDay, SeparatorDash, ScalarYear], :handle_sm_sd_sy], - [[ScalarMonth, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarDay], :handle_sm_sd] + [[ScalarMonth, SeparatorSlash, ScalarDay, SeparatorSlash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sm_sd_sy], + [[ScalarMonth, SeparatorDash, ScalarDay, SeparatorDash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sm_sd_sy], + [[ScalarMonth, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarDay, [SeparatorSlash, :none]], :handle_sm_sd] ] little = [ - [[ScalarDay, SeparatorDash, ScalarMonth, SeparatorDash, ScalarYear], :handle_sd_sm_sy], - [[ScalarDay, SeparatorSlash, ScalarMonth, SeparatorSlash, ScalarYear], :handle_sd_sm_sy], - [[ScalarDay, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarMonth], :handle_sd_sm] + [[ScalarDay, SeparatorDash, ScalarMonth, SeparatorDash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sd_sm_sy], + [[ScalarDay, SeparatorSlash, ScalarMonth, SeparatorSlash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sd_sm_sy], + [[ScalarDay, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarMonth, [SeparatorSlash, :none]], :handle_sd_sm] ] case endian = Array(options[:endian_precedence]).first From 2a80f002a34d940abe98585b9db51b6f7e412fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 04:10:19 +0300 Subject: [PATCH 13/23] Implement Quarter parsing Formats: * Arrow: 3 quarters ago, before 2 quarters * Anchor: last/this/next quarter * Narrow: Q1, 2nd quarter, fourth quarter --- lib/chronic/arrow.rb | 13 +++++++++++++ lib/chronic/date.rb | 29 ++++++++++++++++++++++++++++ lib/chronic/definition.rb | 3 +++ lib/chronic/handlers/anchor.rb | 8 ++++++++ lib/chronic/handlers/general.rb | 11 +++++++++++ lib/chronic/objects/anchor_object.rb | 24 ++++++++++++++++++++++- lib/chronic/objects/narrow_object.rb | 10 ++++++++++ lib/chronic/parser.rb | 4 ++++ lib/chronic/tags/keyword.rb | 7 +++++++ lib/chronic/tags/unit.rb | 1 + 10 files changed, 109 insertions(+), 1 deletion(-) diff --git a/lib/chronic/arrow.rb b/lib/chronic/arrow.rb index 4952b2e5..572ce01c 100644 --- a/lib/chronic/arrow.rb +++ b/lib/chronic/arrow.rb @@ -22,6 +22,7 @@ class Arrow :wdays, :weekends, :fortnights, + :quarters, :seasons ] @@ -43,6 +44,7 @@ class Arrow :weeks, :fortnights, :months, + :quarters, :seasons, :years ] @@ -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) @@ -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]}" diff --git a/lib/chronic/date.rb b/lib/chronic/date.rb index 290976f3..e2c285f0 100644 --- a/lib/chronic/date.rb +++ b/lib/chronic/date.rb @@ -3,6 +3,7 @@ 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 @@ -10,6 +11,7 @@ class Date 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 @@ -27,6 +29,7 @@ class Date :autumn => [9, 23], :winter => [12, 22] } + QUARTERS = [nil, 1, 4, 7, 10] MONTHS = { :january => 1, :february => 2, @@ -101,6 +104,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 @@ -127,6 +139,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) @@ -184,6 +208,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 diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index fc8b7120..57197b8a 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -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 @@ -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 diff --git a/lib/chronic/handlers/anchor.rb b/lib/chronic/handlers/anchor.rb index 85c8b962..6a9cd99d 100644 --- a/lib/chronic/handlers/anchor.rb +++ b/lib/chronic/handlers/anchor.rb @@ -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 diff --git a/lib/chronic/handlers/general.rb b/lib/chronic/handlers/general.rb index f6040903..3abce998 100644 --- a/lib/chronic/handlers/general.rb +++ b/lib/chronic/handlers/general.rb @@ -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 diff --git a/lib/chronic/objects/anchor_object.rb b/lib/chronic/objects/anchor_object.rb index 863c8ee0..2ad379d6 100644 --- a/lib/chronic/objects/anchor_object.rb +++ b/lib/chronic/objects/anchor_object.rb @@ -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) @@ -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) @@ -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 diff --git a/lib/chronic/objects/narrow_object.rb b/lib/chronic/objects/narrow_object.rb index 453a9bd8..0e19d43e 100644 --- a/lib/chronic/objects/narrow_object.rb +++ b/lib/chronic/objects/narrow_object.rb @@ -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) diff --git a/lib/chronic/parser.rb b/lib/chronic/parser.rb index 59c74edb..47258a22 100644 --- a/lib/chronic/parser.rb +++ b/lib/chronic/parser.rb @@ -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 diff --git a/lib/chronic/tags/keyword.rb b/lib/chronic/tags/keyword.rb index 7be3c4b1..d2afa0e8 100644 --- a/lib/chronic/tags/keyword.rb +++ b/lib/chronic/tags/keyword.rb @@ -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 @@ -53,4 +54,10 @@ def to_s end end + class KeywordQ < Keyword #:nodoc: + def to_s + super << '-q' + end + end + end diff --git a/lib/chronic/tags/unit.rb b/lib/chronic/tags/unit.rb index becb954b..90b050a3 100644 --- a/lib/chronic/tags/unit.rb +++ b/lib/chronic/tags/unit.rb @@ -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, From 92037d314b75dfd2fd58b6112427230e59aea6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 04:11:41 +0300 Subject: [PATCH 14/23] Short-hand year support eg. '97 --- lib/chronic/definition.rb | 1 + lib/chronic/tags/separator.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index 57197b8a..60e62532 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -71,6 +71,7 @@ def definitions [[DayName, SeparatorSpace, OrdinalDay], :handle_dn_od], [[MonthName, SeparatorSpace, OrdinalDay], :handle_mn_od], [[MonthName, [SeparatorSpace, SeparatorDash], ScalarDay, [SeparatorSpace, Unit, :none]], :handle_mn_sd], + [[MonthName, SeparatorSpace, SeparatorApostrophe, ScalarYear], :handle_mn_sy], [[MonthName, SeparatorSpace, ScalarYear], :handle_mn_sy], [[ScalarYear, [SeparatorDash, SeparatorSlash], ScalarMonth], :handle_sy_sm], [[ScalarFullYear, SeparatorSpace, MonthName], :handle_sy_mn], diff --git a/lib/chronic/tags/separator.rb b/lib/chronic/tags/separator.rb index f99a721b..9c03d9a4 100644 --- a/lib/chronic/tags/separator.rb +++ b/lib/chronic/tags/separator.rb @@ -15,6 +15,7 @@ def self.scan(tokens, options) token.tag scan_for(token, SeparatorColon, { ':'.to_sym => :colon }) token.tag scan_for(token, SeparatorSpace, { ' '.to_sym => :space }) token.tag scan_for(token, SeparatorSlash, { '/'.to_sym => :slash }) + token.tag scan_for(token, SeparatorApostrophe, { '\''.to_sym => :apostrophe }) token.tag scan_for(token, SeparatorDash, { :- => :dash }) token.tag scan_for(token, SeparatorAt, { /^(at|@)$/i => :at }) token.tag scan_for(token, SeparatorIn, { 'in' => :in }) From 15f2156716ad32767ef8971d5507e39ae316ac78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 04:15:36 +0300 Subject: [PATCH 15/23] Support for scalar unit pointer --- lib/chronic/definition.rb | 1 + lib/chronic/handlers/arrow.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index 60e62532..abf67501 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -136,6 +136,7 @@ class ArrowDefinitions < SpanDefinitions def definitions [ [[Pointer, [SeparatorSpace, :optional], Scalar, [SeparatorSpace, :optional], Unit], :handle_p_s_u], + [[Scalar, [SeparatorSpace, :optional], Unit, SeparatorSpace, Pointer], :handle_s_u_p], [[Scalar, [SeparatorSpace, :optional], DayName, SeparatorSpace, Pointer], :handle_s_dn_p], [[Rational, SeparatorSpace, Pointer], :handle_r_p], [[Unit, SeparatorSpace, Pointer], :handle_u_p], diff --git a/lib/chronic/handlers/arrow.rb b/lib/chronic/handlers/arrow.rb index b205b09e..e43910b6 100644 --- a/lib/chronic/handlers/arrow.rb +++ b/lib/chronic/handlers/arrow.rb @@ -54,6 +54,14 @@ def handle_p_s_u handle_s_u end + # Handle scalar/unit/pointer + # formats: su p, s u p + def handle_s_u_p + handle_s_u + next_tag + handle_p + end + # Handle scalar/day-name # formats: s dn def handle_s_dn From 5fd4b42a652b1b652a31461bd5fa4e9cd19edfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Sun, 28 Sep 2014 04:29:22 +0300 Subject: [PATCH 16/23] Enable some disabled tests --- test/test_parsing.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/test_parsing.rb b/test/test_parsing.rb index e4d00467..af936b37 100644 --- a/test/test_parsing.rb +++ b/test/test_parsing.rb @@ -531,8 +531,8 @@ def test_handle_o_r_s_r time = parse_now("10th wednesday in november") assert_nil time - # time = parse_now("3rd wednesday in 2007") - # assert_equal Time.local(2007, 1, 20, 12), time + time = parse_now("3rd saturday in 2007") + assert_equal Time.local(2007, 1, 20, 12), time end def test_handle_o_r_g_r @@ -945,8 +945,8 @@ def test_parse_guess_s_r_p time = parse_now("3 days ago", :guess => :begin) assert_equal Time.local(2006, 8, 13, 14), time - #time = parse_now("1 monday ago") - #assert_equal Time.local(2006, 8, 14, 12), time + time = parse_now("1 monday ago", :guess => :begin) + assert_equal Time.local(2006, 8, 14, 14), time time = parse_now("5 mornings ago") assert_equal Time.local(2006, 8, 12, 9), time @@ -1409,13 +1409,11 @@ def test_days_in_november t1 = Chronic.parse('1st saturday in november', :now => Time.local(2007)) assert_equal Time.local(2007, 11, 3, 12), t1 - # t1 = Chronic.parse('1st sunday in november', :now => Time.local(2007)) - # assert_equal Time.local(2007, 11, 4, 12), t1 + t1 = Chronic.parse('1st sunday in november', :now => Time.local(2007)) + assert_equal Time.local(2007, 11, 4, 12), t1 - # Chronic.debug = true - # - # t1 = Chronic.parse('1st monday in november', :now => Time.local(2007)) - # assert_equal Time.local(2007, 11, 5, 11), t1 + t1 = Chronic.parse('1st monday in november', :now => Time.local(2007)) + assert_equal Time.local(2007, 11, 5, 12), t1 end def test_now_changes From 27a873f297d4f4a47191341bf3c1cec81a25e769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Mon, 25 Jan 2016 21:31:57 +0200 Subject: [PATCH 17/23] Fix endian order for EndianDefinitions --- lib/chronic/definition.rb | 33 +++++++++++++++++++++------------ test/test_chronic.rb | 16 +++++----------- test/test_parsing.rb | 4 ++-- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index abf67501..e2c86a25 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -163,28 +163,37 @@ def definitions prefered_endian end - def prefered_endian - options[:endian_precedence] ||= [:middle, :little] - - middle = [ + def self.middle + [ [[ScalarMonth, SeparatorSlash, ScalarDay, SeparatorSlash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sm_sd_sy], [[ScalarMonth, SeparatorDash, ScalarDay, SeparatorDash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sm_sd_sy], [[ScalarMonth, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarDay, [SeparatorSlash, :none]], :handle_sm_sd] ] - little = [ + end + + def self.little + [ [[ScalarDay, SeparatorDash, ScalarMonth, SeparatorDash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sd_sm_sy], [[ScalarDay, SeparatorSlash, ScalarMonth, SeparatorSlash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sd_sm_sy], [[ScalarDay, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarMonth, [SeparatorSlash, :none]], :handle_sd_sm] ] + end - case endian = Array(options[:endian_precedence]).first - when :little - little + middle - when :middle - middle + little - else - raise ArgumentError, "Unknown endian option '#{endian}'" + def prefered_endian + options[:endian_precedence] ||= [:middle, :little] + options[:endian_precedence] = [options[:endian_precedence]] unless options[:endian_precedence].is_a?(Array) + definition_list = [] + options[:endian_precedence].each do |endian| + case endian + when :little + definition_list += self.class.little + when :middle + definition_list += self.class.middle + else + raise ArgumentError, "Unknown endian option '#{endian}'" + end end + definition_list end end diff --git a/test/test_chronic.rb b/test/test_chronic.rb index eb62ad1e..00ead10f 100644 --- a/test/test_chronic.rb +++ b/test/test_chronic.rb @@ -56,22 +56,16 @@ def test_guess end def test_endian_definitions - # middle, little - endians = [ - Chronic::Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy), - Chronic::Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, :separator_at?, 'time?'], :handle_sm_sd), - Chronic::Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, :separator_at?, 'time?'], :handle_sd_sm), - Chronic::Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy), - Chronic::Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy) - ] + middle = Chronic::EndianDefinitions.middle + little = Chronic::EndianDefinitions.little - assert_equal endians, Chronic::SpanDictionary.new.definitions[:endian] + assert_equal middle + little, Chronic::SpanDictionary.new.definitions[:endian] defs = Chronic::SpanDictionary.new(:endian_precedence => :little).definitions - assert_equal endians.reverse, defs[:endian] + assert_equal little, defs[:endian] defs = Chronic::SpanDictionary.new(:endian_precedence => [:little, :middle]).definitions - assert_equal endians.reverse, defs[:endian] + assert_equal little + middle, defs[:endian] assert_raises(ArgumentError) do Chronic::SpanDictionary.new(:endian_precedence => :invalid).definitions diff --git a/test/test_parsing.rb b/test/test_parsing.rb index af936b37..52b1b184 100644 --- a/test/test_parsing.rb +++ b/test/test_parsing.rb @@ -392,13 +392,13 @@ def test_handle_sm_sd time = parse_now("05/06") assert_equal Time.local(2007, 5, 6, 12), time - time = parse_now("05/06", :endian_precedence => [:little, :medium]) + time = parse_now("05/06", :endian_precedence => [:little, :middle]) assert_equal Time.local(2007, 6, 5, 12), time time = parse_now("05/06 6:05:57 PM") assert_equal Time.local(2007, 5, 6, 18, 05, 57), time - time = parse_now("05/06 6:05:57 PM", :endian_precedence => [:little, :medium]) + time = parse_now("05/06 6:05:57 PM", :endian_precedence => [:little, :middle]) assert_equal Time.local(2007, 6, 5, 18, 05, 57), time time = parse_now("13/09") From 1fd36957b1cd0020607d1b6f7dc36fdebb78a94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Mon, 25 Jan 2016 21:33:34 +0200 Subject: [PATCH 18/23] Fix test_chronic tests --- test/test_chronic.rb | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/test/test_chronic.rb b/test/test_chronic.rb index 00ead10f..f41133a7 100644 --- a/test/test_chronic.rb +++ b/test/test_chronic.rb @@ -8,7 +8,10 @@ def setup end def test_pre_normalize - assert_equal Chronic::Parser.new.pre_normalize('12:55 pm'), Chronic::Parser.new.pre_normalize('12.55 pm') + assert_equal Chronic::Parser.new.pre_normalize('three quarters'), Chronic::Parser.new.pre_normalize('3 quarters') + assert_equal Chronic::Parser.new.pre_normalize('one second'), Chronic::Parser.new.pre_normalize('1 second') + assert_equal Chronic::Parser.new.pre_normalize('third'), Chronic::Parser.new.pre_normalize('3rd') + assert_equal Chronic::Parser.new.pre_normalize('fourth'), Chronic::Parser.new.pre_normalize('4th') end def test_pre_normalize_numerized_string @@ -19,28 +22,21 @@ def test_pre_normalize_numerized_string def test_post_normalize_am_pm_aliases # affect wanted patterns - tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")] - tokens[0].tag(Chronic::RepeaterTime.new("5:00")) - tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning)) + tokens = [Chronic::Token.new("5"), Chronic::Token.new("morning")] + tokens[0].tag(Chronic::ScalarHour.new("5", 1)) + tokens[1].tag(Chronic::TimeSpecial.new(:morning)) assert_equal :morning, tokens[1].tags[0].type - - tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {}) - - assert_equal :am, tokens[1].tags[0].type assert_equal 2, tokens.size # don't affect unwanted patterns tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")] - tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) - tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning)) + tokens[0].tag(Chronic::DayName.new(:friday)) + tokens[1].tag(Chronic::TimeSpecial.new(:morning)) assert_equal :morning, tokens[1].tags[0].type - tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {}) - - assert_equal :morning, tokens[1].tags[0].type assert_equal 2, tokens.size end @@ -78,7 +74,7 @@ def test_debug Chronic.debug = true Chronic.parse 'now' - assert $stdout.string.include?('this(grabber-this)') + assert $stdout.string.include?('now(timespecial-now)') ensure $stdout = STDOUT Chronic.debug = false @@ -113,10 +109,7 @@ def test_day_overflow assert_equal Time.local(2006, 3, 5), Chronic.construct(2006, 2, 33) assert_equal Time.local(2004, 3, 4), Chronic.construct(2004, 2, 33) assert_equal Time.local(2000, 3, 4), Chronic.construct(2000, 2, 33) - - assert_raises(RuntimeError) do - Chronic.construct(2006, 1, 57) - end + assert_equal Time.local(2006, 2, 26), Chronic.construct(2006, 1, 57) end def test_month_overflow From 27e6116cf8495d2bf657f8baf21c93c570fb81ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Tue, 26 Jan 2016 01:51:54 +0200 Subject: [PATCH 19/23] Fix test_handler tests --- test/test_handler.rb | 73 ++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/test/test_handler.rb b/test/test_handler.rb index 77e0c7fe..1e91958e 100644 --- a/test/test_handler.rb +++ b/test/test_handler.rb @@ -1,4 +1,4 @@ -require 'helper' +require_relative 'helper' class TestHandler < TestCase @@ -12,109 +12,95 @@ def definitions end def test_handler_class_1 - handler = Chronic::Handler.new([:repeater], :handler) - tokens = [Chronic::Token.new('friday')] - tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + tokens[0].tag(Chronic::DayName.new(:friday)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::DayName]) tokens << Chronic::Token.new('afternoon') - tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + tokens[1].tag(Chronic::TimeSpecial.new(:afternoon)) - assert !handler.match(tokens, definitions) + assert !Chronic::Handler.match(tokens, 1, [Chronic::DayName]) end def test_handler_class_2 - handler = Chronic::Handler.new([:repeater, :repeater?], :handler) - tokens = [Chronic::Token.new('friday')] - tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + tokens[0].tag(Chronic::DayName.new(:friday)) - assert handler.match(tokens, definitions) + assert !Chronic::Handler.match(tokens, 0, [Chronic::TimeSpecial]) tokens << Chronic::Token.new('afternoon') - tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + tokens[1].tag(Chronic::TimeSpecial.new(:afternoon)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 1, [Chronic::TimeSpecial]) tokens << Chronic::Token.new('afternoon') - tokens[2].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + tokens[2].tag(Chronic::TimeSpecial.new(:afternoon)) - assert !handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 1, [Chronic::TimeSpecial, Chronic::TimeSpecial]) end def test_handler_class_3 - handler = Chronic::Handler.new([:repeater, 'time?'], :handler) - tokens = [Chronic::Token.new('friday')] - tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + tokens[0].tag(Chronic::DayName.new(:friday)) - assert handler.match(tokens, definitions) + assert !Chronic::Handler.match(tokens, 0, [Chronic::DayName, Chronic::TimeSpecial]) tokens << Chronic::Token.new('afternoon') - tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon)) + tokens[1].tag(Chronic::TimeSpecial.new(:afternoon)) - assert !handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::DayName, Chronic::TimeSpecial]) end def test_handler_class_4 - handler = Chronic::Handler.new([:repeater_month_name, :scalar_day, 'time?'], :handler) - tokens = [Chronic::Token.new('may')] - tokens[0].tag(Chronic::RepeaterMonthName.new(:may)) + tokens[0].tag(Chronic::MonthName.new(:may)) - assert !handler.match(tokens, definitions) + assert !Chronic::Handler.match(tokens, 0, [Chronic::MonthName, Chronic::ScalarDay]) tokens << Chronic::Token.new('27') tokens[1].tag(Chronic::ScalarDay.new(27)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::MonthName, Chronic::ScalarDay]) end def test_handler_class_5 - handler = Chronic::Handler.new([:repeater, 'time?'], :handler) - tokens = [Chronic::Token.new('friday')] - tokens[0].tag(Chronic::RepeaterDayName.new(:friday)) + tokens[0].tag(Chronic::DayName.new(:friday)) - assert handler.match(tokens, definitions) + assert !Chronic::Handler.match(tokens, 0, [Chronic::DayName, Chronic::ScalarHour]) - tokens << Chronic::Token.new('5:00') - tokens[1].tag(Chronic::RepeaterTime.new('5:00')) + tokens << Chronic::Token.new('5') + tokens[1].tag(Chronic::ScalarHour.new('5')) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::DayName, Chronic::ScalarHour]) tokens << Chronic::Token.new('pm') - tokens[2].tag(Chronic::RepeaterDayPortion.new(:pm)) + tokens[2].tag(Chronic::DayPortion.new(:pm)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::DayName, Chronic::ScalarHour, Chronic::DayPortion]) end def test_handler_class_6 - handler = Chronic::Handler.new([:scalar, :repeater, :pointer], :handler) - tokens = [Chronic::Token.new('3'), Chronic::Token.new('years'), Chronic::Token.new('past')] tokens[0].tag(Chronic::Scalar.new(3)) - tokens[1].tag(Chronic::RepeaterYear.new(:year)) + tokens[1].tag(Chronic::UnitYear.new(:year)) tokens[2].tag(Chronic::Pointer.new(:past)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::Scalar, Chronic::UnitYear, Chronic::Pointer]) end def test_handler_class_7 - handler = Chronic::Handler.new([[:separator_on, :separator_at], :scalar], :handler) - tokens = [Chronic::Token.new('at'), Chronic::Token.new('14')] tokens[0].tag(Chronic::SeparatorAt.new('at')) tokens[1].tag(Chronic::Scalar.new(14)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::SeparatorAt, Chronic::Scalar]) tokens = [Chronic::Token.new('on'), Chronic::Token.new('15')] @@ -122,7 +108,6 @@ def test_handler_class_7 tokens[0].tag(Chronic::SeparatorOn.new('on')) tokens[1].tag(Chronic::Scalar.new(15)) - assert handler.match(tokens, definitions) + assert Chronic::Handler.match(tokens, 0, [Chronic::SeparatorOn, Chronic::Scalar]) end - end From ec22ba7f74d6b44e40cd3e27c370fff988e7f217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Thu, 3 Aug 2017 20:32:16 +0300 Subject: [PATCH 20/23] When :context == :none assume time isn't ambiguous --- lib/chronic/objects/time_object.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/chronic/objects/time_object.rb b/lib/chronic/objects/time_object.rb index 831b2022..453eb3c1 100644 --- a/lib/chronic/objects/time_object.rb +++ b/lib/chronic/objects/time_object.rb @@ -32,7 +32,7 @@ def normalize! @hour += 12 if @hour < 12 @ambiguous = false end - @hour += 12 if @ambiguous and @hour != 12 and @hour <= @options[:ambiguous_time_range] + @hour += 12 if @ambiguous and @options[:context] != :none and @hour != 12 and @hour <= @options[:ambiguous_time_range] elsif @time_special if @time_special == :now set_time @@ -93,4 +93,4 @@ def set_time include TimeHandlers end -end \ No newline at end of file +end From 345e9f4b68e97d5625d58163e380aff1ae2cb4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Thu, 3 Aug 2017 20:33:58 +0300 Subject: [PATCH 21/23] Fix test_daylight_savings tests --- test/test_daylight_savings.rb | 172 +++++++++++++++------------------- 1 file changed, 76 insertions(+), 96 deletions(-) diff --git a/test/test_daylight_savings.rb b/test/test_daylight_savings.rb index d6f303fb..ea6aa5df 100644 --- a/test/test_daylight_savings.rb +++ b/test/test_daylight_savings.rb @@ -8,111 +8,91 @@ def setup end def test_begin_past - # ambiguous - resolve to last night - t = Chronic::RepeaterTime.new('900') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 8, 21), t.next(:past).begin - - # ambiguous - resolve to this afternoon - t = Chronic::RepeaterTime.new('900') - t.start = Time.local(2008, 3, 9, 22, 0, 0, 0) - assert_equal Time.local(2008, 3, 9, 21), t.next(:past).begin - - # ambiguous - resolve to this morning - t = Chronic::RepeaterTime.new('400') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 9, 4), t.next(:past).begin - - # unambiguous - resolve to today - t = Chronic::RepeaterTime.new('0400') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 9, 4), t.next(:past).begin - - # unambiguous - resolve to yesterday - t = Chronic::RepeaterTime.new('1300') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 8, 13), t.next(:past).begin + # resolve to last night + t = Chronic.parse('9:00 PM', :guess => false, :context => :past, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 8, 21), t.begin + + # resolve to this afternoon + t = Chronic.parse('9:00 PM', :guess => false, :context => :none, :now => Time.local(2008, 3, 9, 22, 0, 0, 0)) + assert_equal Time.local(2008, 3, 9, 21), t.begin + + # resolve to this morning + t = Chronic.parse('4:00 AM', :guess => false, :context => :none, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 9, 4), t.begin + + # resolve to today + t = Chronic.parse('04:00', :guess => false, :context => :none, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 9, 4), t.begin + + # resolve to yesterday + t = Chronic.parse('13:00', :guess => false, :context => :past, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 8, 13), t.begin end def test_begin_future - # ambiguous - resolve to this morning - t = Chronic::RepeaterTime.new('900') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 9, 9), t.next(:future).begin - - # ambiguous - resolve to this afternoon - t = Chronic::RepeaterTime.new('900') - t.start = Time.local(2008, 3, 9, 13, 0, 0, 0) - assert_equal Time.local(2008, 3, 9, 21), t.next(:future).begin - - # ambiguous - resolve to tomorrow - t = Chronic::RepeaterTime.new('900') - t.start = Time.local(2008, 3, 9, 22, 0, 0, 0) - assert_equal Time.local(2008, 3, 10, 9), t.next(:future).begin - - # unambiguous - resolve to today - t = Chronic::RepeaterTime.new('0900') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 9, 9), t.next(:future).begin - - # unambiguous - resolve to tomorrow - t = Chronic::RepeaterTime.new('0400') - t.start = @begin_daylight_savings - assert_equal Time.local(2008, 3, 10, 4), t.next(:future).begin + # resolve to this morning + t = Chronic.parse('9:00 AM', :guess => false, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 9, 9), t.begin + + # resolve to this afternoon + t = Chronic.parse('9:00 PM', :guess => false, :now => Time.local(2008, 3, 9, 13, 0, 0, 0)) + assert_equal Time.local(2008, 3, 9, 21), t.begin + + # resolve to tomorrow + t = Chronic.parse('9:00', :guess => false, :now => Time.local(2008, 3, 9, 22, 0, 0, 0)) + assert_equal Time.local(2008, 3, 10, 9), t.begin + + # resolve to today + t = Chronic.parse('09:00', :guess => false, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 9, 9), t.begin + + # resolve to tomorrow + t = Chronic.parse('04:00', :guess => false, :now => @begin_daylight_savings) + assert_equal Time.local(2008, 3, 10, 4), t.begin end def test_end_past - # ambiguous - resolve to last night - t = Chronic::RepeaterTime.new('900') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 1, 21), t.next(:past).begin - - # ambiguous - resolve to this afternoon - t = Chronic::RepeaterTime.new('900') - t.start = Time.local(2008, 11, 2, 22, 0, 0, 0) - assert_equal Time.local(2008, 11, 2, 21), t.next(:past).begin - - # ambiguous - resolve to this morning - t = Chronic::RepeaterTime.new('400') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 2, 4), t.next(:past).begin - - # unambiguous - resolve to today - t = Chronic::RepeaterTime.new('0400') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 2, 4), t.next(:past).begin - - # unambiguous - resolve to yesterday - t = Chronic::RepeaterTime.new('1300') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 1, 13), t.next(:past).begin + # resolve to last night + t = Chronic.parse('9:00 PM', :guess => false, :context => :past, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 1, 21), t.begin + + # resolve to this afternoon + t = Chronic.parse('9:00 PM', :guess => false, :context => :none, :now => Time.local(2008, 11, 2, 22, 0, 0, 0)) + assert_equal Time.local(2008, 11, 2, 21), t.begin + + # resolve to this morning + t = Chronic.parse('4:00', :guess => false, :context => :none, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 2, 4), t.begin + + # resolve to today + t = Chronic.parse('04:00', :guess => false, :context => :none, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 2, 4), t.begin + + # resolve to yesterday + t = Chronic.parse('13:00', :guess => false, :context => :past, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 1, 13), t.begin end def test_end_future - # ambiguous - resolve to this morning - t = Chronic::RepeaterTime.new('900') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 2, 9), t.next(:future).begin - - # ambiguous - resolve to this afternoon - t = Chronic::RepeaterTime.new('900') - t.start = Time.local(2008, 11, 2, 13, 0, 0, 0) - assert_equal Time.local(2008, 11, 2, 21), t.next(:future).begin - - # ambiguous - resolve to tomorrow - t = Chronic::RepeaterTime.new('900') - t.start = Time.local(2008, 11, 2, 22, 0, 0, 0) - assert_equal Time.local(2008, 11, 3, 9), t.next(:future).begin - - # unambiguous - resolve to today - t = Chronic::RepeaterTime.new('0900') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 2, 9), t.next(:future).begin - - # unambiguous - resolve to tomorrow - t = Chronic::RepeaterTime.new('0400') - t.start = @end_daylight_savings - assert_equal Time.local(2008, 11, 3, 4), t.next(:future).begin + # resolve to this morning + t = Chronic.parse('at 9:00', :guess => false, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 2, 9), t.begin + + # resolve to this afternoon + t = Chronic.parse('9:00 PM', :guess => false, :now => Time.local(2008, 11, 2, 13, 0, 0, 0)) + assert_equal Time.local(2008, 11, 2, 21), t.begin + + # resolve to tomorrow + t = Chronic.parse('9:00', :guess => false, :now => Time.local(2008, 11, 2, 22, 0, 0, 0)) + assert_equal Time.local(2008, 11, 3, 9), t.begin + + # resolve to today + t = Chronic.parse('09:00', :guess => false, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 2, 9), t.begin + + # resolve to tomorrow + t = Chronic.parse('04:00', :guess => false, :now => @end_daylight_savings) + assert_equal Time.local(2008, 11, 3, 4), t.begin end end From 8e243c6a636f35d6beafe0bcdf012af32e0a385c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Thu, 3 Aug 2017 21:49:27 +0300 Subject: [PATCH 22/23] Support (mm/yy), :endian_precedence == :month_year --- lib/chronic/definition.rb | 12 ++++++++++-- lib/chronic/handlers/date.rb | 9 ++++++++- test/test_parsing.rb | 20 ++++++++++---------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index e2c86a25..1add75b9 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -73,7 +73,7 @@ def definitions [[MonthName, [SeparatorSpace, SeparatorDash], ScalarDay, [SeparatorSpace, Unit, :none]], :handle_mn_sd], [[MonthName, SeparatorSpace, SeparatorApostrophe, ScalarYear], :handle_mn_sy], [[MonthName, SeparatorSpace, ScalarYear], :handle_mn_sy], - [[ScalarYear, [SeparatorDash, SeparatorSlash], ScalarMonth], :handle_sy_sm], + [[ScalarFullYear, [SeparatorDash, SeparatorSlash], ScalarMonth], :handle_sy_sm], [[ScalarFullYear, SeparatorSpace, MonthName], :handle_sy_mn], [[OrdinalDay, SeparatorSpace, MonthName], :handle_od_mn], [[ScalarDay, [SeparatorSpace, SeparatorDash, :optional], MonthName], :handle_sd_mn], @@ -177,7 +177,13 @@ def self.little [[ScalarDay, SeparatorSlash, ScalarMonth, SeparatorSlash, ScalarYear, [SeparatorDot, Scalar, :none]], :handle_sd_sm_sy], [[ScalarDay, [SeparatorSlash, SeparatorDash, SeparatorDot], ScalarMonth, [SeparatorSlash, :none]], :handle_sd_sm] ] - end + end + + def self.month_year + [ + [[ScalarMonth, SeparatorSlash, ScalarYear], :handle_sm_sy] + ] + end def prefered_endian options[:endian_precedence] ||= [:middle, :little] @@ -189,6 +195,8 @@ def prefered_endian definition_list += self.class.little when :middle definition_list += self.class.middle + when :month_year + definition_list += self.class.month_year else raise ArgumentError, "Unknown endian option '#{endian}'" end diff --git a/lib/chronic/handlers/date.rb b/lib/chronic/handlers/date.rb index 236e5134..91135ca6 100644 --- a/lib/chronic/handlers/date.rb +++ b/lib/chronic/handlers/date.rb @@ -194,7 +194,14 @@ def handle_mn_sy @precision = :month end - + # Handle scalar-month/scalar-year + # formats: mm/yy + def handle_sm_sy + handle_sm + next_tag + handle_sy + @precision = :month + end # Handle scalar-month/scalar-day # formats: mm/dd diff --git a/test/test_parsing.rb b/test/test_parsing.rb index 52b1b184..69732063 100644 --- a/test/test_parsing.rb +++ b/test/test_parsing.rb @@ -442,16 +442,16 @@ def test_handle_sm_sd assert_equal Time.local(2006, 1, 1, 12), time end - # def test_handle_sm_sy - # time = parse_now("05/06") - # assert_equal Time.local(2006, 5, 16, 12), time - # - # time = parse_now("12/06") - # assert_equal Time.local(2006, 12, 16, 12), time - # - # time = parse_now("13/06") - # assert_nil time - # end + def test_handle_sm_sy + time = parse_now("05/06", :endian_precedence => :month_year) + assert_equal Time.local(2006, 5, 16, 12), time + + time = parse_now("12/06", :endian_precedence => :month_year) + assert_equal Time.local(2006, 12, 16, 12), time + + time = parse_now("13/06", :endian_precedence => :month_year) + assert_nil time + end def test_handle_sy_sm time = parse_now("2012-06") From ec658809213d659e99a36d7aec1b561a143450aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis?= Date: Wed, 27 Sep 2017 21:56:09 +0300 Subject: [PATCH 23/23] Add support for 'Wed, Sep 27, 2017' date format DayName, MonthName ScalarDay, ScalarYear --- lib/chronic/definition.rb | 4 ++-- test/test_definitions.rb | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 test/test_definitions.rb diff --git a/lib/chronic/definition.rb b/lib/chronic/definition.rb index 1add75b9..cd2cf049 100644 --- a/lib/chronic/definition.rb +++ b/lib/chronic/definition.rb @@ -44,8 +44,8 @@ def definitions class DateDefinitions < SpanDefinitions def definitions [ - [[DayName, SeparatorSpace, MonthName, SeparatorSpace, OrdinalDay, SeparatorSpace, ScalarYear], :handle_dn_mn_od_sy], - [[DayName, SeparatorSpace, MonthName, SeparatorSpace, ScalarDay, SeparatorSpace, ScalarYear], :handle_dn_mn_sd_sy], + [[DayName, [SeparatorComma, :optional], SeparatorSpace, MonthName, SeparatorSpace, OrdinalDay, [SeparatorComma, :optional], SeparatorSpace, ScalarYear], :handle_dn_mn_od_sy], + [[DayName, [SeparatorComma, :optional], SeparatorSpace, MonthName, SeparatorSpace, ScalarDay, [SeparatorComma, :optional], SeparatorSpace, ScalarYear], :handle_dn_mn_sd_sy], [[DayName, SeparatorSpace, MonthName, SeparatorSpace, OrdinalDay], :handle_dn_mn_od], [[DayName, SeparatorSpace, MonthName, SeparatorSpace, ScalarDay], :handle_dn_mn_sd], [[MonthName, SeparatorSpace, OrdinalDay, [SeparatorComma, SeparatorSpace], [SeparatorSpace, :optional], ScalarYear], :handle_mn_od_sy], diff --git a/test/test_definitions.rb b/test/test_definitions.rb new file mode 100644 index 00000000..732e454a --- /dev/null +++ b/test/test_definitions.rb @@ -0,0 +1,40 @@ +require 'helper' + +class TestDefinitions < TestCase + + def setup + end + + # DayName, MonthName ScalarDay, ScalarYear + def test_dn_mn_sd_sy + time = Chronic.parse('Wed Sep 27 2017', :guess => false) + assert_equal Time.local(2017, 9, 27, 0), time.begin + assert_equal Time.local(2017, 9, 28, 0), time.end + + time = Chronic.parse('Wed, Sep 27, 2017', :guess => false) + assert_equal Time.local(2017, 9, 27, 0), time.begin + assert_equal Time.local(2017, 9, 28, 0), time.end + end + + # DayName, MonthName OrdinalDay, ScalarYear + def test_dn_mn_od_sy + time = Chronic.parse('Sun Oct 22nd 2017', :guess => false) + assert_equal Time.local(2017, 10, 22, 0), time.begin + assert_equal Time.local(2017, 10, 23, 0), time.end + + time = Chronic.parse('Mon Oct 30th 2017', :guess => false) + assert_equal Time.local(2017, 10, 30, 0), time.begin + assert_equal Time.local(2017, 10, 31, 0), time.end + + time = Chronic.parse('Sun, Oct 22nd, 2017', :guess => false) + assert_equal Time.local(2017, 10, 22, 0), time.begin + assert_equal Time.local(2017, 10, 23, 0), time.end + + time = Chronic.parse('Mon, Oct 30th, 2017', :guess => false) + assert_equal Time.local(2017, 10, 30, 0), time.begin + assert_equal Time.local(2017, 10, 31, 0), time.end + + end + +end +