public
Fork of mojombo/chronic
Description: Chronic is a pure Ruby natural language date parser.
Homepage: http://chronic.rubyforge.org
Clone URL: git://github.com/technoweenie/chronic.git
seasons and weekday/weekend support (Edwin Chen)
mojombo (author)
Mon Feb 18 00:56:24 -0800 2008
commit  3d7d840f8e6dac043c0733d29f727a4d7835e9b5
tree    24c49e0ebc6cea7422ed532ca2d3fb096bc0deb1
parent  b9eb114e77d86eeac43e5b0b713d40b8d5a23f62
...
23
24
25
 
26
27
28
...
23
24
25
26
27
28
29
0
@@ -23,6 +23,7 @@ require 'chronic/repeaters/repeater_month_name'
0
 require 'chronic/repeaters/repeater_fortnight'
0
 require 'chronic/repeaters/repeater_week'
0
 require 'chronic/repeaters/repeater_weekend'
0
+require 'chronic/repeaters/repeater_weekday'
0
 require 'chronic/repeaters/repeater_day'
0
 require 'chronic/repeaters/repeater_day_name'
0
 require 'chronic/repeaters/repeater_day_portion'
...
2
3
4
 
5
6
7
...
11
12
13
 
 
 
 
 
 
 
 
 
 
 
 
14
15
16
...
74
75
76
 
77
78
79
...
2
3
4
5
6
7
8
...
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
87
88
89
90
91
92
93
0
@@ -2,6 +2,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
0
   def self.scan(tokens, options)
0
     # for each token
0
     tokens.each_index do |i|
0
+ if t = self.scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
0
       if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
0
       if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
0
       if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
0
@@ -11,6 +12,18 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
0
     tokens
0
   end
0
   
0
+ def self.scan_for_season_names(token)
0
+ scanner = {/^springs?$/ => :spring,
0
+ /^summers?$/ => :summer,
0
+ /^(autumn)|(fall)s?$/ => :autumn,
0
+ /^winters?$/ => :winter}
0
+ scanner.keys.each do |scanner_item|
0
+ return Chronic::RepeaterSeasonName.new(scanner[scanner_item]) if scanner_item =~ token.word
0
+ end
0
+
0
+ return nil
0
+ end
0
+
0
   def self.scan_for_month_names(token)
0
     scanner = {/^jan\.?(uary)?$/ => :january,
0
                /^feb\.?(ruary)?$/ => :february,
0
@@ -74,6 +87,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
0
                /^fortnights?$/ => :fortnight,
0
                /^weeks?$/ => :week,
0
                /^weekends?$/ => :weekend,
0
+ /^(week|business)days?$/ => :weekday,
0
                /^days?$/ => :day,
0
                /^hours?$/ => :hour,
0
                /^minutes?$/ => :minute,
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
 
 
 
 
3
4
5
6
7
 
 
 
 
8
9
10
11
12
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
15
16
...
20
21
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
24
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 
62
63
64
65
66
67
68
69
70
 
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
...
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
0
@@ -1,16 +1,99 @@
0
+class Time
0
+ def to_minidate
0
+ MiniDate.new(self.month, self.day)
0
+ end
0
+end
0
+
0
+class Season
0
+ attr_reader :start, :end
0
+
0
+ def initialize(myStart, myEnd)
0
+ @start = myStart
0
+ @end = myEnd
0
+ end
0
+
0
+ def self.find_next_season(season, pointer)
0
+ lookup = {:spring => 0, :summer => 1, :autumn => 2, :winter => 3}
0
+ next_season_num = (lookup[season]+1*pointer) % 4
0
+ lookup.invert[next_season_num]
0
+ end
0
+
0
+ def self.season_after(season); find_next_season(season, +1); end
0
+ def self.season_before(season); find_next_season(season, -1); end
0
+end
0
+
0
+class MiniDate
0
+ attr_accessor :month, :day
0
+
0
+ def initialize(month, day)
0
+ @month = month
0
+ @day = day
0
+ end
0
+
0
+ def is_between?(md_start, md_end)
0
+ return true if (@month == md_start.month and @day >= md_start.day) ||
0
+ (@month == md_end.month and @day <= md_end.day)
0
+
0
+ i = md_start.month + 1
0
+ until i == md_end.month
0
+ return true if @month == i
0
+ i = (i+1) % 12
0
+ end
0
+
0
+ return false
0
+ end
0
+
0
+ def equals?(other)
0
+ @month == other.month and day == other.day
0
+ end
0
+end
0
+
0
 class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
0
+ YEAR_SEASONS = 4
0
   SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
0
+ SEASONS = { :spring => Season.new( MiniDate.new(3,20),MiniDate.new(6,20) ),
0
+ :summer => Season.new( MiniDate.new(6,21),MiniDate.new(9,22) ),
0
+ :autumn => Season.new( MiniDate.new(9,23),MiniDate.new(12,21) ),
0
+ :winter => Season.new( MiniDate.new(12,22),MiniDate.new(3,19) ) }
0
   
0
   def next(pointer)
0
     super
0
     
0
- raise 'Not implemented'
0
+ direction = pointer == :future ? 1 : -1
0
+ next_season = Season.find_next_season(find_current_season(@now.to_minidate), direction)
0
+
0
+ find_next_season_span(direction, next_season)
0
   end
0
   
0
   def this(pointer = :future)
0
     super
0
     
0
- raise 'Not implemented'
0
+ direction = pointer == :future ? 1 : -1
0
+
0
+ today = Time.construct(@now.year, @now.month, @now.day)
0
+ this_ssn = find_current_season(@now.to_minidate)
0
+ case pointer
0
+ when :past
0
+ this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
0
+ this_ssn_end = today
0
+ when :future
0
+ this_ssn_start = today + Chronic::RepeaterDay::DAY_SECONDS
0
+ this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
0
+ when :none
0
+ this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
0
+ this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
0
+ end
0
+
0
+ Chronic::Span.new(this_ssn_start, this_ssn_end)
0
+ end
0
+
0
+ def offset(span, amount, pointer)
0
+ Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
0
+ end
0
+
0
+ def offset_by(time, amount, pointer)
0
+ direction = pointer == :future ? 1 : -1
0
+ time + amount * direction * SEASON_SECONDS
0
   end
0
   
0
   def width
0
@@ -20,4 +103,43 @@ class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
0
   def to_s
0
     super << '-season'
0
   end
0
+
0
+ private
0
+
0
+ def find_next_season_span(direction, next_season)
0
+ if !@next_season_start or !@next_season_end
0
+ @next_season_start = Time.construct(@now.year, @now.month, @now.day)
0
+ @next_season_end = Time.construct(@now.year, @now.month, @now.day)
0
+ end
0
+
0
+ @next_season_start += direction * num_seconds_til_start(next_season, direction)
0
+ @next_season_end += direction * num_seconds_til_end(next_season, direction)
0
+
0
+ Chronic::Span.new(@next_season_start, @next_season_end)
0
+ end
0
+
0
+ def find_current_season(md)
0
+ [:spring, :summer, :autumn, :winter].each do |season|
0
+ return season if md.is_between?(SEASONS[season].start, SEASONS[season].end)
0
+ end
0
+ end
0
+
0
+ def num_seconds_til(goal, direction)
0
+ start = Time.construct(@now.year, @now.month, @now.day)
0
+ seconds = 0
0
+
0
+ until (start + direction * seconds).to_minidate.equals?(goal)
0
+ seconds += Chronic::RepeaterDay::DAY_SECONDS
0
+ end
0
+
0
+ seconds
0
+ end
0
+
0
+ def num_seconds_til_start(season_symbol, direction)
0
+ num_seconds_til(SEASONS[season_symbol].start, direction)
0
+ end
0
+
0
+ def num_seconds_til_end(season_symbol, direction)
0
+ num_seconds_til(SEASONS[season_symbol].end, direction)
0
+ end
0
 end
0
\ No newline at end of file
...
 
 
1
2
3
4
5
 
 
6
7
8
9
 
 
10
11
12
13
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
16
17
18
 
 
19
20
21
22
 
 
 
23
 
24
25
...
1
2
3
 
 
 
 
4
5
6
7
 
 
8
9
10
11
12
 
 
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 
 
36
37
38
39
 
 
40
41
42
43
44
45
46
0
@@ -1,24 +1,45 @@
0
+require 'chronic/repeaters/repeater_season.rb'
0
+
0
 class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
0
- @summer = ['jul 21', 'sep 22']
0
- @autumn = ['sep 23', 'dec 21']
0
- @winter = ['dec 22', 'mar 19']
0
- @spring = ['mar 20', 'jul 20']
0
+ SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
0
+ DAY_SECONDS = 86_400 # (24 * 60 * 60)
0
   
0
   def next(pointer)
0
- super
0
- raise 'Not implemented'
0
+ direction = pointer == :future ? 1 : -1
0
+ find_next_season_span(direction, @type)
0
   end
0
   
0
   def this(pointer = :future)
0
- super
0
- raise 'Not implemented'
0
+ # super
0
+
0
+ direction = pointer == :future ? 1 : -1
0
+
0
+ today = Time.construct(@now.year, @now.month, @now.day)
0
+ goal_ssn_start = today + direction * num_seconds_til_start(@type, direction)
0
+ goal_ssn_end = today + direction * num_seconds_til_end(@type, direction)
0
+ curr_ssn = find_current_season(@now.to_minidate)
0
+ case pointer
0
+ when :past
0
+ this_ssn_start = goal_ssn_start
0
+ this_ssn_end = (curr_ssn == @type) ? today : goal_ssn_end
0
+ when :future
0
+ this_ssn_start = (curr_ssn == @type) ? today + Chronic::RepeaterDay::DAY_SECONDS : goal_ssn_start
0
+ this_ssn_end = goal_ssn_end
0
+ when :none
0
+ this_ssn_start = goal_ssn_start
0
+ this_ssn_end = goal_ssn_end
0
+ end
0
+
0
+ Chronic::Span.new(this_ssn_start, this_ssn_end)
0
   end
0
   
0
- def width
0
- (91 * 24 * 60 * 60)
0
+ def offset(span, amount, pointer)
0
+ Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
0
   end
0
   
0
- def to_s
0
- super << '-season-' << @type.to_s
0
+ def offset_by(time, amount, pointer)
0
+ direction = pointer == :future ? 1 : -1
0
+ time + amount * direction * Chronic::RepeaterYear::YEAR_SECONDS
0
   end
0
+
0
 end
0
\ No newline at end of file
...
1
 
2
3
4
...
1
2
3
4
5
0
@@ -1,4 +1,5 @@
0
 class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
0
+ YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
0
   
0
   def next(pointer)
0
     super
...
638
639
640
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
642
643
...
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
0
@@ -638,6 +638,28 @@ class TestParsing < Test::Unit::TestCase
0
     end
0
   end
0
   
0
+ def test_seasons
0
+ t = parse_now("this spring", :guess => false)
0
+ assert_equal Time.local(2007, 3, 20), t.begin
0
+ assert_equal Time.local(2007, 6, 20), t.end
0
+
0
+ t = parse_now("this winter", :guess => false)
0
+ assert_equal Time.local(2006, 12, 22, 23), t.begin
0
+ assert_equal Time.local(2007, 3, 19), t.end
0
+
0
+ t = parse_now("last spring", :guess => false)
0
+ assert_equal Time.local(2006, 3, 20, 23), t.begin
0
+ assert_equal Time.local(2006, 6, 20), t.end
0
+
0
+ t = parse_now("last winter", :guess => false)
0
+ assert_equal Time.local(2005, 12, 22, 23), t.begin
0
+ assert_equal Time.local(2006, 3, 19, 23), t.end
0
+
0
+ t = parse_now("next spring", :guess => false)
0
+ assert_equal Time.local(2007, 3, 20), t.begin
0
+ assert_equal Time.local(2007, 6, 20), t.end
0
+ end
0
+
0
   # regression
0
   
0
   # def test_partial

Comments

    No one has commented yet.