tickle 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -44,7 +44,7 @@ Tickle.parse returns a hash containing the following keys:
44
44
 
45
45
  Tickle returns nil if it cannot parse the string cannot be parsed.
46
46
 
47
- Tickle HEAVILY uses chronic for parsing both the event and the start date.
47
+ Tickle HEAVILY uses chronic for parsing but mostly the start and until phrases.
48
48
 
49
49
  === OPTIONS
50
50
 
@@ -57,8 +57,8 @@ NATURAL LANGUAGE:
57
57
 
58
58
  OPTIONS HASH
59
59
  Valid options are:
60
- * start - must be a valid date. (e.g. Tickle.parse('every other day', {:start => Date.new(2010,8,1) }))
61
- * until - must be a valid date. (e.g. Tickle.parse('every other day', {:until => Date.new(2010,10,1) }))
60
+ * start - must be a valid date or Chronic date expression. (e.g. Tickle.parse('every other day', {:start => Date.new(2010,8,1) }))
61
+ * until - must be a valid date or Chronic date expression. (e.g. Tickle.parse('every other day', {:until => Date.new(2010,10,1) }))
62
62
  * next_only - legacy switch to ONLY return the next occurrence as a date and not return a hash
63
63
 
64
64
  === SUPER IMPORTANT NOTE ABOUT NEXT OCCURRENCE & START DATE
@@ -74,84 +74,237 @@ If you don't like that, fork and have fun but don't say I didn't warn ya.
74
74
  require 'rubygems'
75
75
  require 'tickle'
76
76
 
77
- .Time.now
78
- 2010-05-07 14:01:19 -0400
79
-
80
77
  SIMPLE
81
- Tickle.parse('day') #=> {:next=>2010-05-08 14:01:19 -0400, :recurrence_expression=>"day", :starting=>2010-05-07 14:01:19 -0400}
82
- Tickle.parse('day') #=> {:next=>2010-05-08 14:01:19 -0400, :recurrence_expression=>"day", :starting=>2010-05-07 14:01:19 -0400}
83
- Tickle.parse('week') #=> {:next=>2010-05-14 14:01:19 -0400, :recurrence_expression=>"week", :starting=>2010-05-07 14:01:19 -0400}
84
- Tickle.parse('Month') #=> {:next=>2010-06-07 14:01:19 -0400, :recurrence_expression=>"month", :starting=>2010-05-07 14:01:19 -0400}
85
- Tickle.parse('year') #=> {:next=>2011-05-07 14:01:19 -0400, :recurrence_expression=>"year", :starting=>2010-05-07 14:01:19 -0400}
86
- Tickle.parse('daily') #=> {:next=>2010-05-08 14:01:19 -0400, :recurrence_expression=>"daily", :starting=>2010-05-07 14:01:19 -0400}
87
- Tickle.parse('weekly') #=> {:next=>2010-05-14 14:01:19 -0400, :recurrence_expression=>"weekly", :starting=>2010-05-07 14:01:19 -0400}
88
- Tickle.parse('monthly') #=> {:next=>2010-06-07 14:01:19 -0400, :recurrence_expression=>"monthly", :starting=>2010-05-07 14:01:19 -0400}
89
- Tickle.parse('yearly') #=> {:next=>2011-05-07 14:01:19 -0400, :recurrence_expression=>"yearly", :starting=>2010-05-07 14:01:19 -0400}
90
- Tickle.parse('3 days') #=> {:next=>2010-05-10 14:01:19 -0400, :recurrence_expression=>"3 days", :starting=>2010-05-07 14:01:19 -0400}
91
- Tickle.parse('3 weeks') #=> {:next=>2010-05-28 14:01:19 -0400, :recurrence_expression=>"3 weeks", :starting=>2010-05-07 14:01:19 -0400}
92
- Tickle.parse('3 months') #=> {:next=>2010-08-08 14:01:19 -0400, :recurrence_expression=>"3 months", :starting=>2010-05-07 14:01:19 -0400}
93
- Tickle.parse('3 years') #=> {:next=>2013-05-06 14:01:19 -0400, :recurrence_expression=>"3 years", :starting=>2010-05-07 14:01:19 -0400}
94
- Tickle.parse('other day') #=> {:next=>2010-05-09 14:01:19 -0400, :recurrence_expression=>"other day", :starting=>2010-05-07 14:01:19 -0400}
95
- Tickle.parse('other week') #=> {:next=>2010-05-21 14:01:19 -0400, :recurrence_expression=>"other week", :starting=>2010-05-07 14:01:19 -0400}
96
- Tickle.parse('other month') #=> {:next=>2010-07-07 14:01:19 -0400, :recurrence_expression=>"other month", :starting=>2010-05-07 14:01:19 -0400}
97
- Tickle.parse('other year') #=> {:next=>2012-05-07 14:01:19 -0400, :recurrence_expression=>"other year", :starting=>2010-05-07 14:01:19 -0400}
98
- Tickle.parse('Monday') #=> {:next=>2010-05-10 12:00:00 -0400, :recurrence_expression=>"monday", :starting=>2010-05-07 14:01:19 -0400}
99
- Tickle.parse('Wednesday') #=> {:next=>2010-05-12 12:00:00 -0400, :recurrence_expression=>"wednesday", :starting=>2010-05-07 14:01:19 -0400}
100
- Tickle.parse('Friday') #=> {:next=>2010-05-14 12:00:00 -0400, :recurrence_expression=>"friday", :starting=>2010-05-07 14:01:19 -0400}
101
- Tickle.parse('May') #=> {:next=>2011-05-01 12:00:00 -0400, :recurrence_expression=>"may", :starting=>2010-05-07 14:01:19 -0400}
102
- Tickle.parse('june') #=> {:next=>2010-06-01 12:00:00 -0400, :recurrence_expression=>"june", :starting=>2010-05-07 14:01:19 -0400}
103
- Tickle.parse('beginning of the week') #=> {:next=>2010-05-09 12:00:00 -0400, :recurrence_expression=>"beginning of the week", :starting=>2010-05-07 14:01:19 -0400}
104
- Tickle.parse('middle of the week') #=> {:next=>2010-05-12 12:00:00 -0400, :recurrence_expression=>"middle of the week", :starting=>2010-05-07 14:01:19 -0400}
105
- Tickle.parse('end of the week') #=> {:next=>2010-05-08 12:00:00 -0400, :recurrence_expression=>"end of the week", :starting=>2010-05-07 14:01:19 -0400}
106
- Tickle.parse('beginning of the month') #=> {:next=>2010-06-01 00:00:00 -0400, :recurrence_expression=>"beginning of the month", :starting=>2010-05-07 14:01:19 -0400}
107
- Tickle.parse('middle of the month') #=> {:next=>2010-05-15 00:00:00 -0400, :recurrence_expression=>"middle of the month", :starting=>2010-05-07 14:01:19 -0400}
108
- Tickle.parse('end of the month') #=> {:next=>2010-05-31 00:00:00 -0400, :recurrence_expression=>"end of the month", :starting=>2010-05-07 14:01:19 -0400}
109
- Tickle.parse('beginning of the year') #=> {:next=>2011-01-01 00:00:00 -0500, :recurrence_expression=>"beginning of the year", :starting=>2010-05-07 14:01:19 -0400}
110
- Tickle.parse('middle of the year') #=> {:next=>2010-06-15 00:00:00 -0400, :recurrence_expression=>"middle of the year", :starting=>2010-05-07 14:01:19 -0400}
111
- Tickle.parse('end of the year') #=> {:next=>2010-12-31 00:00:00 -0500, :recurrence_expression=>"end of the year", :starting=>2010-05-07 14:01:19 -0400}
112
- Tickle.parse('the 3rd of May') #=> {:next=>2011-05-03 12:00:00 -0400, :recurrence_expression=>"the 3rd of may", :starting=>2010-05-07 14:01:19 -0400}
113
- Tickle.parse('the 3rd of February') #=> {:next=>2011-02-03 12:00:00 -0500, :recurrence_expression=>"the 3rd of february", :starting=>2010-05-07 14:01:19 -0400}
114
- Tickle.parse('the 3rd of February 2012') #=> {:next=>2012-02-03 12:00:00 -0500, :recurrence_expression=>"the 3rd of february 2012", :starting=>2010-05-07 14:01:19 -0400}
115
- Tickle.parse('the 3rd of Feb 2012') #=> {:next=>2012-02-03 12:00:00 -0500, :recurrence_expression=>"the 3rd of feb 2012", :starting=>2010-05-07 14:01:19 -0400}
116
- Tickle.parse('the 4th of the month') #=> {:next=>2010-06-04 12:00:00 -0400, :recurrence_expression=>"the 4th of the month", :starting=>2010-05-07 14:01:19 -0400}
117
- Tickle.parse('the 10th of the month') #=> {:next=>2010-05-10 12:00:00 -0400, :recurrence_expression=>"the 10th of the month", :starting=>2010-05-07 14:01:19 -0400}
118
- Tickle.parse('the tenth of the month') #=> {:next=>2010-05-10 12:00:00 -0400, :recurrence_expression=>"the tenth of the month", :starting=>2010-05-07 14:01:19 -0400}
119
- Tickle.parse('the first of the month') #=> {:next=>2010-06-01 12:00:00 -0400, :recurrence_expression=>"the first of the month", :starting=>2010-05-07 14:01:19 -0400}
120
- Tickle.parse('the thirtieth') #=> {:next=>2010-05-30 12:00:00 -0400, :recurrence_expression=>"the thirtieth", :starting=>2010-05-07 14:01:19 -0400}
121
- Tickle.parse('the fifth') #=> {:next=>2010-06-05 12:00:00 -0400, :recurrence_expression=>"the fifth", :starting=>2010-05-07 14:01:19 -0400}
122
- Tickle.parse('the 3rd Sunday of May') #=> {:next=>2010-05-16 12:00:00 -0400, :recurrence_expression=>"the 3rd sunday of may", :starting=>2010-05-07 14:01:19 -0400}
123
- Tickle.parse('the 3rd Sunday of the month') #=> {:next=>2010-06-20 12:00:00 -0400, :recurrence_expression=>"the 3rd sunday of the month", :starting=>2010-05-07 14:01:19 -0400}
124
- Tickle.parse('the 23rd of June') #=> {:next=>2010-06-23 12:00:00 -0400, :recurrence_expression=>"the 23rd of june", :starting=>2010-05-07 14:01:19 -0400}
125
- Tickle.parse('the twenty third of June') #=> {:next=>2010-06-23 12:00:00 -0400, :recurrence_expression=>"the twenty third of june", :starting=>2010-05-07 14:01:19 -0400}
126
- Tickle.parse('the thirty first of August') #=> {:next=>2010-08-31 12:00:00 -0400, :recurrence_expression=>"the thirty first of august", :starting=>2010-05-07 14:01:19 -0400}
127
- Tickle.parse('the twenty first') #=> {:next=>2010-05-21 12:00:00 -0400, :recurrence_expression=>"the twenty first", :starting=>2010-05-07 14:01:19 -0400}
128
- Tickle.parse('the twenty first of the month') #=> {:next=>2010-05-21 12:00:00 -0400, :recurrence_expression=>"the twenty first of the month", :starting=>2010-05-07 14:01:19 -0400}
129
-
78
+ Tickle.parse('day')
79
+ #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
80
+
81
+ Tickle.parse('day')
82
+ #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
83
+
84
+ Tickle.parse('week')
85
+ #=> {:next=>2010-05-16 20:57:36 -0400, :expression=>"week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
86
+
87
+ Tickle.parse('month')
88
+ #=> {:next=>2010-06-09 20:57:36 -0400, :expression=>"month", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
89
+
90
+ Tickle.parse('year')
91
+ #=> {:next=>2011-05-09 20:57:36 -0400, :expression=>"year", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
92
+
93
+ Tickle.parse('daily')
94
+ #=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"daily", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
95
+
96
+ Tickle.parse('weekly')
97
+ #=> {:next=>2010-05-16 20:57:36 -0400, :expression=>"weekly", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
98
+
99
+ Tickle.parse('monthly')
100
+ #=> {:next=>2010-06-09 20:57:36 -0400, :expression=>"monthly", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
101
+
102
+ Tickle.parse('yearly')
103
+ #=> {:next=>2011-05-09 20:57:36 -0400, :expression=>"yearly", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
104
+
105
+ Tickle.parse('3 days')
106
+ #=> {:next=>2010-05-12 20:57:36 -0400, :expression=>"3 days", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
107
+
108
+ Tickle.parse('3 weeks')
109
+ #=> {:next=>2010-05-30 20:57:36 -0400, :expression=>"3 weeks", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
110
+
111
+ Tickle.parse('3 months')
112
+ #=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
113
+
114
+ Tickle.parse('3 years')
115
+ #=> {:next=>2013-05-09 20:57:36 -0400, :expression=>"3 years", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
116
+
117
+ Tickle.parse('other day')
118
+ #=> {:next=>2010-05-11 20:57:36 -0400, :expression=>"other day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
119
+
120
+ Tickle.parse('other week')
121
+ #=> {:next=>2010-05-23 20:57:36 -0400, :expression=>"other week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
122
+
123
+ Tickle.parse('other month')
124
+ #=> {:next=>2010-07-09 20:57:36 -0400, :expression=>"other month", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
125
+
126
+ Tickle.parse('other year')
127
+ #=> {:next=>2012-05-09 20:57:36 -0400, :expression=>"other year", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
128
+
129
+ Tickle.parse('Monday')
130
+ #=> {:next=>2010-05-10 12:00:00 -0400, :expression=>"monday", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
131
+
132
+ Tickle.parse('Wednesday')
133
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"wednesday", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
134
+
135
+ Tickle.parse('Friday')
136
+ #=> {:next=>2010-05-14 12:00:00 -0400, :expression=>"friday", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
137
+
138
+ Tickle.parse('February', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
139
+ #=> {:next=>2021-02-01 12:00:00 -0500, :expression=>"february", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
140
+
141
+ Tickle.parse('May', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
142
+ #=> {:next=>2020-05-01 12:00:00 -0400, :expression=>"may", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
143
+
144
+ Tickle.parse('june', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
145
+ #=> {:next=>2020-06-01 12:00:00 -0400, :expression=>"june", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
146
+
147
+ Tickle.parse('beginning of the week')
148
+ #=> {:next=>2010-05-16 12:00:00 -0400, :expression=>"beginning of the week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
149
+
150
+ Tickle.parse('middle of the week')
151
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"middle of the week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
152
+
153
+ Tickle.parse('end of the week')
154
+ #=> {:next=>2010-05-15 12:00:00 -0400, :expression=>"end of the week", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
155
+
156
+ Tickle.parse('beginning of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
157
+ #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"beginning of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
158
+
159
+ Tickle.parse('middle of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
160
+ #=> {:next=>2020-04-15 00:00:00 -0400, :expression=>"middle of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
161
+
162
+ Tickle.parse('end of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
163
+ #=> {:next=>2020-04-30 00:00:00 -0400, :expression=>"end of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
164
+
165
+ Tickle.parse('beginning of the year', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
166
+ #=> {:next=>2021-01-01 00:00:00 -0500, :expression=>"beginning of the year", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
167
+
168
+ Tickle.parse('middle of the year', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
169
+ #=> {:next=>2020-06-15 00:00:00 -0400, :expression=>"middle of the year", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
170
+
171
+ Tickle.parse('end of the year', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
172
+ #=> {:next=>2020-12-31 00:00:00 -0500, :expression=>"end of the year", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
173
+
174
+ Tickle.parse('the 3rd of May', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
175
+ #=> {:next=>2020-05-03 00:00:00 -0400, :expression=>"the 3rd of may", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
176
+
177
+ Tickle.parse('the 3rd of February', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
178
+ #=> {:next=>2021-02-03 00:00:00 -0500, :expression=>"the 3rd of february", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
179
+
180
+ Tickle.parse('the 3rd of February 2022', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
181
+ #=> {:next=>2022-02-03 00:00:00 -0500, :expression=>"the 3rd of february 2022", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
182
+
183
+ Tickle.parse('the 3rd of Feb 2022', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
184
+ #=> {:next=>2022-02-03 00:00:00 -0500, :expression=>"the 3rd of feb 2022", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
185
+
186
+ Tickle.parse('the 4th of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
187
+ #=> {:next=>2020-04-04 00:00:00 -0400, :expression=>"the 4th of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
188
+
189
+ Tickle.parse('the 10th of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
190
+ #=> {:next=>2020-04-10 00:00:00 -0400, :expression=>"the 10th of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
191
+
192
+ Tickle.parse('the tenth of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
193
+ #=> {:next=>2020-04-10 00:00:00 -0400, :expression=>"the tenth of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
194
+
195
+ Tickle.parse('first', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
196
+ #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"first", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
197
+
198
+ Tickle.parse('the first of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
199
+ #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"the first of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
200
+
201
+ Tickle.parse('the thirtieth', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
202
+ #=> {:next=>2020-04-30 00:00:00 -0400, :expression=>"the thirtieth", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
203
+
204
+ Tickle.parse('the fifth', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
205
+ #=> {:next=>2020-04-05 00:00:00 -0400, :expression=>"the fifth", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
206
+
207
+ Tickle.parse('the 1st Wednesday of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
208
+ #=> {:next=>2020-05-01 00:00:00 -0400, :expression=>"the 1st wednesday of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
209
+
210
+ Tickle.parse('the 3rd Sunday of May', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
211
+ #=> {:next=>2020-05-17 12:00:00 -0400, :expression=>"the 3rd sunday of may", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
212
+
213
+ Tickle.parse('the 3rd Sunday of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
214
+ #=> {:next=>2020-04-19 12:00:00 -0400, :expression=>"the 3rd sunday of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
215
+
216
+ Tickle.parse('the 23rd of June', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
217
+ #=> {:next=>2020-06-23 00:00:00 -0400, :expression=>"the 23rd of june", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
218
+
219
+ Tickle.parse('the twenty third of June', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
220
+ #=> {:next=>2020-06-23 00:00:00 -0400, :expression=>"the twenty third of june", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
221
+
222
+ Tickle.parse('the thirty first of July', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
223
+ #=> {:next=>2020-07-31 00:00:00 -0400, :expression=>"the thirty first of july", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
224
+
225
+ Tickle.parse('the twenty first', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
226
+ #=> {:next=>2020-04-21 00:00:00 -0400, :expression=>"the twenty first", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
227
+
228
+ Tickle.parse('the twenty first of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
229
+ #=> {:next=>2020-04-21 00:00:00 -0400, :expression=>"the twenty first of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
230
+
130
231
  COMPLEX
131
- Tickle.parse('starting Monday repeat every month') #=> {:next=>2010-05-10 12:00:00 -0400, :recurrence_expression=>"month", :starting=>2010-05-10 12:00:00 -0400}
132
- Tickle.parse('starting May 13th repeat every week') #=> {:next=>2010-05-13 12:00:00 -0400, :recurrence_expression=>"week", :starting=>2010-05-13 12:00:00 -0400}
133
- Tickle.parse('starting May 13th repeat every other day') #=> {:next=>2010-05-13 12:00:00 -0400, :recurrence_expression=>"other day", :starting=>2010-05-13 12:00:00 -0400}
134
- Tickle.parse('every week starts this wednesday') #=> {:next=>2010-05-12 12:00:00 -0400, :recurrence_expression=>"week", :starting=>2010-05-12 12:00:00 -0400}
135
- Tickle.parse('every other day starts the 1st May') #=> {:next=>2011-05-01 12:00:00 -0400, :recurrence_expression=>"other day", :starting=>2011-05-01 12:00:00 -0400}
136
- Tickle.parse('every other day starting May 6') #=> {:next=>2010-05-09 14:01:19 -0400, :recurrence_expression=>"other day", :starting=>2010-05-07 14:01:19 -0400}
137
- Tickle.parse('every week starting this wednesday') #=> {:next=>2010-05-12 12:00:00 -0400, :recurrence_expression=>"week", :starting=>2010-05-12 12:00:00 -0400}
138
- Tickle.parse('every other day starting the 1st May') #=> {:next=>2011-05-01 12:00:00 -0400, :recurrence_expression=>"other day", :starting=>2011-05-01 12:00:00 -0400}
139
- Tickle.parse('every other day starting May 1st 2011') #=> {:next=>2011-05-01 12:00:00 -0400, :recurrence_expression=>"other day", :starting=>2011-05-01 12:00:00 -0400}
140
- Tickle.parse('every other week starting this Sunday') #=> {:next=>2010-05-09 12:00:00 -0400, :recurrence_expression=>"other week", :starting=>2010-05-09 12:00:00 -0400}
141
- Tickle.parse('every week starting this wednesday until June 5th') #=> {:next=>2010-05-14 14:01:19 -0400, :recurrence_expression=>"week", :starting=>2010-05-07 14:01:19 -0400, :until=>2010-06-05 12:00:00 -0400}
142
- Tickle.parse('every week starting this wednesday ends June 5th') #=> {:next=>2010-05-14 14:01:19 -0400, :recurrence_expression=>"week", :starting=>2010-05-07 14:01:19 -0400, :until=>2010-06-05 12:00:00 -0400}
143
- Tickle.parse('every week starting this wednesday ending June 5th') #=> {:next=>2010-05-14 14:01:19 -0400, :recurrence_expression=>"week", :starting=>2010-05-07 14:01:19 -0400, :until=>2010-06-05 12:00:00 -0400}
144
-
145
- WITH OPTIONS HASH
146
- .Tickle.parse('May 1st 2011, {:next_only=>true}') #=> 2011-05-01 12:00:00 -0400
147
- Tickle.parse('every 3 days, {:start=>Mon, 17 May 2010}') #=> {:next=>2010-05-17 00:00:00 -0400, :recurrence_expression=>"every 3 days", :starting=>2010-05-17 00:00:00 -0400}
148
- Tickle.parse('every 3 weeks, {:start=>Mon, 17 May 2010}') #=> {:next=>2010-05-17 00:00:00 -0400, :recurrence_expression=>"every 3 weeks", :starting=>2010-05-17 00:00:00 -0400}
149
- Tickle.parse('every 3 months, {:start=>Mon, 17 May 2010}') #=> {:next=>2010-05-17 00:00:00 -0400, :recurrence_expression=>"every 3 months", :starting=>2010-05-17 00:00:00 -0400}
150
- Tickle.parse('every 3 years, {:start=>Mon, 17 May 2010}') #=> {:next=>2010-05-17 00:00:00 -0400, :recurrence_expression=>"every 3 years", :starting=>2010-05-17 00:00:00 -0400}
151
- Tickle.parse('every 3 days, {:start=>Mon, 17 May 2010, :until=>Thu, 07 Oct 2010}') #=> {:next=>2010-05-17 00:00:00 -0400, :recurrence_expression=>"every 3 days", :starting=>2010-05-17 00:00:00 -0400, :until=>2010-10-07 00:00:00 -0400}
152
- Tickle.parse('every 3 weeks, {:start=>Mon, 17 May 2010, :until=>Thu, 07 Oct 2010}') #=> {:next=>2010-05-17 00:00:00 -0400, :recurrence_expression=>"every 3 weeks", :starting=>2010-05-17 00:00:00 -0400, :until=>2010-10-07 00:00:00 -0400}
153
- Tickle.parse('3 months, {:until=>Thu, 07 Oct 2010}') #=> {:next=>2010-08-08 14:01:19 -0400, :recurrence_expression=>"3 months", :starting=>2010-05-07 14:01:19 -0400, :until=>2010-10-07 00:00:00 -0400}
154
- .
232
+ Tickle.parse('starting today and ending one week from now')
233
+ #=> {:next=>2010-05-10 22:30:00 -0400, :expression=>"day", :starting=>2010-05-09 22:30:00 -0400, :until=>2010-05-16 20:57:35 -0400}
234
+
235
+ Tickle.parse('starting tomorrow and ending one week from now')
236
+ #=> {:next=>2010-05-10 12:00:00 -0400, :expression=>"day", :starting=>2010-05-10 12:00:00 -0400, :until=>2010-05-16 20:57:35 -0400}
237
+
238
+ Tickle.parse('starting Monday repeat every month')
239
+ #=> {:next=>2010-05-10 12:00:00 -0400, :expression=>"month", :starting=>2010-05-10 12:00:00 -0400, :until=>nil}
240
+
241
+ Tickle.parse('starting May 13th repeat every week')
242
+ #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"week", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}
243
+
244
+ Tickle.parse('starting May 13th repeat every other day')
245
+ #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}
246
+
247
+ Tickle.parse('every other day starts May 13th')
248
+ #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}
249
+
250
+ Tickle.parse('every other day starts May 13')
251
+ #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}
252
+
253
+ Tickle.parse('every other day starting May 13th')
254
+ #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}
255
+
256
+ Tickle.parse('every other day starting May 13')
257
+ #=> {:next=>2010-05-13 12:00:00 -0400, :expression=>"other day", :starting=>2010-05-13 12:00:00 -0400, :until=>nil}
258
+
259
+ Tickle.parse('every week starts this wednesday')
260
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>nil}
261
+
262
+ Tickle.parse('every week starting this wednesday')
263
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>nil}
264
+
265
+ Tickle.parse('every other day starting May 1st 2021')
266
+ #=> {:next=>2021-05-01 12:00:00 -0400, :expression=>"other day", :starting=>2021-05-01 12:00:00 -0400, :until=>nil}
267
+
268
+ Tickle.parse('every other day starting May 1 2021')
269
+ #=> {:next=>2021-05-01 12:00:00 -0400, :expression=>"other day", :starting=>2021-05-01 12:00:00 -0400, :until=>nil}
270
+
271
+ Tickle.parse('every other week starting this Sunday')
272
+ #=> {:next=>2010-05-16 12:00:00 -0400, :expression=>"other week", :starting=>2010-05-16 12:00:00 -0400, :until=>nil}
273
+
274
+ Tickle.parse('every week starting this wednesday until May 13th')
275
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}
276
+
277
+ Tickle.parse('every week starting this wednesday ends May 13th')
278
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}
279
+
280
+ Tickle.parse('every week starting this wednesday ending May 13th')
281
+ #=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}
282
+
283
+
284
+ OPTIONS HASH
285
+ Tickle.parse('May 1st 2020', {:next_only=>true})
286
+ #=> 2020-05-01 00:00:00 -0400
287
+
288
+ Tickle.parse('3 days', {:start=>2010-05-09 20:57:36 -0400})
289
+ #=> {:next=>2010-05-12 20:57:36 -0400, :expression=>"3 days", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
290
+
291
+ Tickle.parse('3 weeks', {:start=>2010-05-09 20:57:36 -0400})
292
+ #=> {:next=>2010-05-30 20:57:36 -0400, :expression=>"3 weeks", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
293
+
294
+ Tickle.parse('3 months', {:start=>2010-05-09 20:57:36 -0400})
295
+ #=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
296
+
297
+ Tickle.parse('3 years', {:start=>2010-05-09 20:57:36 -0400})
298
+ #=> {:next=>2013-05-09 20:57:36 -0400, :expression=>"3 years", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
299
+
300
+ Tickle.parse('3 days', {:start=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400})
301
+ #=> {:next=>2010-05-12 20:57:36 -0400, :expression=>"3 days", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}
302
+
303
+ Tickle.parse('3 weeks', {:start=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400})
304
+ #=> {:next=>2010-05-30 20:57:36 -0400, :expression=>"3 weeks", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}
305
+
306
+ Tickle.parse('3 months', {:until=>2010-10-09 00:00:00 -0400})
307
+ #=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}
155
308
 
156
309
  == USING IN APP
157
310
 
@@ -159,7 +312,7 @@ To use in your app, we recommend adding two attributes to your database model:
159
312
  * next_occurrence
160
313
  * tickle_expression
161
314
 
162
- Then call Tickle.parse(query) when you need to and save the results accordingly. In your
315
+ Then call Tickle.parse(date expression) when you need to and save the results accordingly. In your
163
316
  code, each day, simply check to see if today is >= next_occurrence and, if so, run your block.
164
317
 
165
318
  After it completes, call Tickle.parse(tickle_expression) again to update the next occurrence of the event.
@@ -167,10 +320,11 @@ After it completes, call Tickle.parse(tickle_expression) again to update the nex
167
320
 
168
321
  == TESTING
169
322
 
170
- Tickle comes with a full testing suite that tests and shows sample output for simple, complex, options hash and invalid arguments.
323
+ Tickle comes with a full testing suite for simple, complex, options hash and invalid arguments.
171
324
 
172
- Please note, the tests were designed to output the parsed expression and not return true. This allows (and sorry, forces you) to have to skim through the
173
- results and queries to ensure they are working correctly. You can also add your own for fun.
325
+ You also have some command line options:
326
+ --v verbose output like the examples above
327
+ --d debug output showing the guts of a date expression
174
328
 
175
329
  == LIMITATIONS
176
330
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.6
data/git-flow-version CHANGED
@@ -1 +1 @@
1
- GITFLOW_VERSION=0.1.5
1
+ GITFLOW_VERSION=0.1.6
@@ -1,11 +1,13 @@
1
1
  module Tickle #:nodoc:
2
2
  class << self #:nodoc:
3
3
 
4
- # The heavy lifting. Goes through each token groupings to determine what natural language should either by
5
- # parsed by Chronic or returned. This methodology makes extension fairly simple, as new token types can be
4
+ # The heavy lifting. Goes through each token groupings to determine what natural language should either by
5
+ # parsed by Chronic or returned. This methodology makes extension fairly simple, as new token types can be
6
6
  # easily added in repeater and then processed by the guess method
7
7
  #
8
8
  def guess()
9
+ return nil if @tokens.empty?
10
+
9
11
  guess_unit_types
10
12
  guess_weekday unless @next
11
13
  guess_month_names unless @next
@@ -15,46 +17,52 @@ module Tickle #:nodoc:
15
17
  guess_special unless @next
16
18
 
17
19
  # check to see if next is less than now and, if so, set it to next year
18
- @next = Time.local(@next.year + 1, @next.month, @next.day, @next.hour, @next.min, @next.sec) if @next && @next.to_date < @start.to_date
20
+ @next = Time.local(@next.year + 1, @next.month, @next.day, @next.hour, @next.min, @next.sec) if @next && @next.to_date < @start.to_date
19
21
 
20
22
  # return the next occurrence
21
23
  return @next.to_time if @next
22
24
  end
23
25
 
24
26
  def guess_unit_types
25
- interval = 1 if token_types.same?([:day])
26
- interval = 7 if token_types.same?([:week])
27
- interval = Tickle.days_in_month if token_types.same?([:month])
28
- interval = 365 if token_types.same?([:year])
29
- compute_next(interval)
27
+ @next = @start.bump(:day) if token_types.same?([:day])
28
+ @next = @start.bump(:week) if token_types.same?([:week])
29
+ @next = @start.bump(:month) if token_types.same?([:month])
30
+ @next = @start.bump(:year) if token_types.same?([:year])
30
31
  end
31
32
 
32
33
  def guess_weekday
33
- @next = chronic_parse("#{token_of_type(:weekday).start.to_s}") if token_types.same?([:weekday])
34
+ @next = chronic_parse_with_start("#{token_of_type(:weekday).start.to_s}") if token_types.same?([:weekday])
34
35
  end
35
36
 
36
37
  def guess_month_names
37
- @next = chronic_parse("#{token_of_type(:month_name).start.to_s} 1") if token_types.same?([:month_name])
38
+ @next = chronic_parse_with_start("#{Date::MONTHNAMES[token_of_type(:month_name).start]} 1") if token_types.same?([:month_name])
38
39
  end
39
40
 
40
41
  def guess_number_and_unit
41
- interval = token_of_type(:number).interval if token_types.same?([:number, :day])
42
- interval = (token_of_type(:number).interval * 7) if token_types.same?([:number, :week])
43
- interval = (token_of_type(:number).interval * Tickle.days_in_month) if token_types.same?([:number, :month])
44
- interval = (token_of_type(:number).interval * 365) if token_types.same?([:number, :year])
45
- compute_next(interval)
42
+ @next = @start.bump(:day, token_of_type(:number).interval) if token_types.same?([:number, :day])
43
+ @next = @start.bump(:week, token_of_type(:number).interval) if token_types.same?([:number, :week])
44
+ @next = @start.bump(:month, token_of_type(:number).interval) if token_types.same?([:number, :month])
45
+ @next = @start.bump(:year, token_of_type(:number).interval) if token_types.same?([:number, :year])
46
46
  end
47
47
 
48
48
  def guess_ordinal
49
- @next = chronic_parse("#{token_of_type(:ordinal).word} day in #{Date::MONTHNAMES[get_next_month(token_of_type(:ordinal).start)]}") if token_types.same?([:ordinal])
49
+ @next = handle_same_day_chronic_issue(@start.year, @start.month, token_of_type(:ordinal).start) if token_types.same?([:ordinal])
50
50
  end
51
51
 
52
52
  def guess_ordinal_and_unit
53
- @next = chronic_parse("#{token_of_type(:ordinal).word} day in #{token_of_type(:month_name).start.to_s} ") if token_types.same?([:ordinal, :month_name])
54
- @next = chronic_parse("#{token_of_type(:ordinal).word} day in #{Date::MONTHNAMES[get_next_month(token_of_type(:ordinal).start)]}") if token_types.same?([:ordinal, :month])
55
- @next = chronic_parse("#{token_of_type(:ordinal).word} #{token_of_type(:weekday).start.to_s} in #{token_of_type(:month_name).start.to_s}") if token_types.same?([:ordinal, :weekday, :month_name])
56
- @next = chronic_parse("#{token_of_type(:ordinal).word} #{token_of_type(:weekday).start.to_s} in #{Date::MONTHNAMES[get_next_month(token_of_type(:ordinal).start)]}") if token_types.same?([:ordinal, :weekday, :month])
57
- @next = chronic_parse("#{token_of_type(:month_name).word} #{token_of_type(:ordinal).start} #{token_of_type(:specific_year).word}") if token_types.same?([:ordinal, :month_name, :specific_year])
53
+ @next = handle_same_day_chronic_issue(@start.year, token_of_type(:month_name).start, token_of_type(:ordinal).start) if token_types.same?([:ordinal, :month_name])
54
+ @next = handle_same_day_chronic_issue(@start.year, @start.month, token_of_type(:ordinal).start) if token_types.same?([:ordinal, :month])
55
+ @next = handle_same_day_chronic_issue(token_of_type(:specific_year).word, token_of_type(:month_name).start, token_of_type(:ordinal).start) if token_types.same?([:ordinal, :month_name, :specific_year])
56
+
57
+ if token_types.same?([:ordinal, :weekday, :month_name])
58
+ @next = chronic_parse_with_start("#{token_of_type(:ordinal).word} #{token_of_type(:weekday).start.to_s} in #{Date::MONTHNAMES[token_of_type(:month_name).start]}")
59
+ @next = handle_same_day_chronic_issue(@start.year, token_of_type(:month_name).start, token_of_type(:ordinal).start) if @next.to_date == @start.to_date
60
+ end
61
+
62
+ if token_types.same?([:ordinal, :weekday, :month])
63
+ @next = chronic_parse_with_start("#{token_of_type(:ordinal).word} #{token_of_type(:weekday).start.to_s} in #{Date::MONTHNAMES[get_next_month(token_of_type(:ordinal).start)]}")
64
+ @next = handle_same_day_chronic_issue(@start.year, @start.month, token_of_type(:ordinal).start) if @next.to_date == @start.to_date
65
+ end
58
66
  end
59
67
 
60
68
  def guess_special
@@ -67,27 +75,26 @@ module Tickle #:nodoc:
67
75
  private
68
76
 
69
77
  def guess_special_other
70
- interval = 2 if token_types.same?([:special, :day]) && token_of_type(:special).start == :other
71
- interval = 14 if token_types.same?([:special, :week]) && token_of_type(:special).start == :other
72
- @next = chronic_parse('2 months from now') if token_types.same?([:special, :month]) && token_of_type(:special).start == :other
73
- @next = chronic_parse('2 years from now') if token_types.same?([:special, :year]) && token_of_type(:special).start == :other
74
- compute_next(interval)
78
+ @next = @start.bump(:day, 2) if token_types.same?([:special, :day]) && token_of_type(:special).start == :other
79
+ @next = @start.bump(:week, 2) if token_types.same?([:special, :week]) && token_of_type(:special).start == :other
80
+ @next = chronic_parse_with_start('2 months from now') if token_types.same?([:special, :month]) && token_of_type(:special).start == :other
81
+ @next = chronic_parse_with_start('2 years from now') if token_types.same?([:special, :year]) && token_of_type(:special).start == :other
75
82
  end
76
83
 
77
84
  def guess_special_beginning
78
- if token_types.same?([:special, :week]) && token_of_type(:special).start == :beginning then @next = chronic_parse('Sunday'); end
85
+ if token_types.same?([:special, :week]) && token_of_type(:special).start == :beginning then @next = chronic_parse_with_start('Sunday'); end
79
86
  if token_types.same?([:special, :month]) && token_of_type(:special).start == :beginning then @next = Date.civil(@start.year, @start.month + 1, 1); end
80
87
  if token_types.same?([:special, :year]) && token_of_type(:special).start == :beginning then @next = Date.civil(@start.year+1, 1, 1); end
81
88
  end
82
89
 
83
90
  def guess_special_end
84
- if token_types.same?([:special, :week]) && token_of_type(:special).start == :end then @next = chronic_parse('Saturday'); end
91
+ if token_types.same?([:special, :week]) && token_of_type(:special).start == :end then @next = chronic_parse_with_start('Saturday'); end
85
92
  if token_types.same?([:special, :month]) && token_of_type(:special).start == :end then @next = Date.civil(@start.year, @start.month, -1); end
86
93
  if token_types.same?([:special, :year]) && token_of_type(:special).start == :end then @next = Date.new(@start.year, 12, 31); end
87
94
  end
88
95
 
89
96
  def guess_special_middle
90
- if token_types.same?([:special, :week]) && token_of_type(:special).start == :middle then @next = chronic_parse('Wednesday'); end
97
+ if token_types.same?([:special, :week]) && token_of_type(:special).start == :middle then @next = chronic_parse_with_start('Wednesday'); end
91
98
  if token_types.same?([:special, :month]) && token_of_type(:special).start == :middle then
92
99
  @next = (@start.day > 15 ? Date.civil(@start.year, @start.month + 1, 15) : Date.civil(@start.year, @start.month, 15))
93
100
  end
@@ -102,16 +109,19 @@ module Tickle #:nodoc:
102
109
 
103
110
  private
104
111
 
105
- def compute_next(interval)
106
- # defines the next occurrence of this tickle if not set in a guess routine
107
- @next ||= @start + (interval * 60 * 60 * 24) if interval
108
- end
109
-
110
- def chronic_parse(exp)
111
- puts "date expression: #{exp}" if Tickle.debug
112
+ # runs Chronic.parse with now being set to the specified start date for Tickle parsing
113
+ def chronic_parse_with_start(exp)
114
+ Tickle.dwrite("date expression: #{exp}, start: #{@start}")
112
115
  Chronic.parse(exp, :now => @start)
113
116
  end
114
117
 
118
+ # needed to handle the unique situation where a number or ordinal plus optional month or month name is passed that is EQUAL to the start date since Chronic returns that day.
119
+ def handle_same_day_chronic_issue(year, month, day)
120
+ Tickle.dwrite("year (#{year}), month (#{month}), day (#{day})")
121
+ arg_date = (Date.new(year.to_i, month.to_i, day.to_i) == @start.to_date) ? Time.local(year, month+1, day) : Time.local(year, month, day)
122
+ return arg_date
123
+ end
124
+
115
125
 
116
126
  end
117
127
  end
@@ -47,7 +47,7 @@ class Tickle::Repeater < Chronic::Tag #:nodoc:
47
47
  scanner.keys.each do |scanner_item|
48
48
  if scanner_item =~ token.original
49
49
  token.word = scanner[scanner_item]
50
- token.update(:ordinal, numericize_ordinals(scanner[scanner_item]), Tickle.days_in_month(Tickle.get_next_month(numericize_ordinals(scanner[scanner_item]))))
50
+ token.update(:ordinal, scanner[scanner_item].ordinal_as_number, Tickle.days_in_month(Tickle.get_next_month(scanner[scanner_item].ordinal_as_number)))
51
51
  end
52
52
  end
53
53
  token
@@ -57,24 +57,24 @@ class Tickle::Repeater < Chronic::Tag #:nodoc:
57
57
  regex = /\b(\d*)(st|nd|rd|th)\b/
58
58
  if token.original =~ regex
59
59
  token.word = token.original
60
- token.update(:ordinal, numericize_ordinals(token.word), Tickle.days_in_month(Tickle.get_next_month(token.word)))
60
+ token.update(:ordinal, token.word.ordinal_as_number, Tickle.days_in_month(Tickle.get_next_month(token.word)))
61
61
  end
62
62
  token
63
63
  end
64
64
 
65
65
  def self.scan_for_month_names(token)
66
- scanner = {/^jan\.?(uary)?$/ => :january,
67
- /^feb\.?(ruary)?$/ => :february,
68
- /^mar\.?(ch)?$/ => :march,
69
- /^apr\.?(il)?$/ => :april,
70
- /^may$/ => :may,
71
- /^jun\.?e?$/ => :june,
72
- /^jul\.?y?$/ => :july,
73
- /^aug\.?(ust)?$/ => :august,
74
- /^sep\.?(t\.?|tember)?$/ => :september,
75
- /^oct\.?(ober)?$/ => :october,
76
- /^nov\.?(ember)?$/ => :november,
77
- /^dec\.?(ember)?$/ => :december}
66
+ scanner = {/^jan\.?(uary)?$/ => 1,
67
+ /^feb\.?(ruary)?$/ => 2,
68
+ /^mar\.?(ch)?$/ => 3,
69
+ /^apr\.?(il)?$/ => 4,
70
+ /^may$/ => 5,
71
+ /^jun\.?e?$/ => 6,
72
+ /^jul\.?y?$/ => 7,
73
+ /^aug\.?(ust)?$/ => 8,
74
+ /^sep\.?(t\.?|tember)?$/ => 9,
75
+ /^oct\.?(ober)?$/ => 10,
76
+ /^nov\.?(ember)?$/ => 11,
77
+ /^dec\.?(ember)?$/ => 12}
78
78
  scanner.keys.each do |scanner_item|
79
79
  token.update(:month_name, scanner[scanner_item], 30) if scanner_item =~ token.word
80
80
  end
@@ -132,10 +132,5 @@ class Tickle::Repeater < Chronic::Tag #:nodoc:
132
132
  token
133
133
  end
134
134
 
135
- # Convert ordinal words to numeric ordinals (third => 3rd)
136
- def self.numericize_ordinals(text) #:nodoc:
137
- text = text.gsub(/\b(\d*)(st|nd|rd|th)\b/, '\1')
138
- end
139
-
140
135
 
141
136
  end
data/lib/tickle/tickle.rb CHANGED
@@ -25,17 +25,17 @@ module Tickle #:nodoc:
25
25
  #
26
26
  # * +start+ - start date for future occurrences. Must be in valid date format.
27
27
  # * +until+ - last date to run occurrences until. Must be in valid date format.
28
- #
28
+ #
29
29
  # Use by calling Tickle.parse and passing natural language with or without options.
30
- #
30
+ #
31
31
  # def get_next_occurrence
32
32
  # results = Tickle.parse('every Wednesday starting June 1st until Dec 15th')
33
33
  # return results[:next] if results
34
34
  # end
35
35
  #
36
36
  def parse(text, specified_options = {})
37
- # get options and set defaults if necessary
38
- default_options = {:start => Time.now, :next_only => false, :until => nil}
37
+ # get options and set defaults if necessary. Ability to set now is mostly for debugging
38
+ default_options = {:start => Time.now, :next_only => false, :until => nil, :now => Time.now}
39
39
  options = default_options.merge specified_options
40
40
 
41
41
  # ensure an expression was provided
@@ -52,13 +52,16 @@ module Tickle #:nodoc:
52
52
 
53
53
  # check to see if this event starts some other time and reset now
54
54
  event = scan_expression(text, options)
55
+
56
+ Tickle.dwrite("start: #{@start}, until: #{@until}, now: #{options[:now].to_date}")
55
57
 
56
- raise(InvalidDateExpression, "the start date (#{@start.to_date}) for a recurring event cannot occur in the past ") if @start.to_date < Date.today
58
+ # => ** this is mostly for testing. Bump by 1 day if today (or in the past for testing)
59
+ raise(InvalidDateExpression, "the start date (#{@start.to_date}) cannot occur in the past for a future event") if @start && @start.to_date < Date.today
57
60
  raise(InvalidDateExpression, "the start date (#{@start.to_date}) cannot occur after the end date") if @until && @start.to_date > @until.to_date
58
61
 
59
62
  # no need to guess at expression if the start_date is in the future
60
63
  best_guess = nil
61
- if @start.to_time > Time.now
64
+ if @start.to_date > options[:now].to_date
62
65
  best_guess = @start
63
66
  else
64
67
  # put the text into a normal format to ease scanning using Chronic
@@ -70,7 +73,7 @@ module Tickle #:nodoc:
70
73
  # process each original word for implied word
71
74
  post_tokenize
72
75
 
73
- # @tokens.each {|x| puts x.inspect} if Tickle.debug
76
+ @tokens.each {|x| Tickle.dwrite("raw: #{x.inspect}")}
74
77
 
75
78
  # scan the tokens with each token scanner
76
79
  @tokens = Repeater.scan(@tokens)
@@ -81,9 +84,10 @@ module Tickle #:nodoc:
81
84
  # combine number and ordinals into single number
82
85
  combine_multiple_numbers
83
86
 
84
- @tokens.each {|x| puts x.inspect} if Tickle.debug
87
+ @tokens.each {|x| Tickle.dwrite("processed: #{x.inspect}")}
85
88
 
86
- best_guess = guess
89
+ # if we can't guess it maybe chronic can
90
+ best_guess = (guess || chronic_parse(event))
87
91
  end
88
92
 
89
93
  raise(InvalidDateExpression, "the next occurrence takes place after the end date specified") if @until && best_guess.to_date > @until.to_date
@@ -101,30 +105,57 @@ module Tickle #:nodoc:
101
105
  def scan_expression(text, options)
102
106
  starting = ending = nil
103
107
 
104
- start_every_regex = /^(start(?:s|ing)?)\s(.*)(\s(?:every|each|on|repeat)(?:s|ing)?)(.*)/i
105
- every_start_regex = /^(every|each|on|repeat(?:the)?)\s(.*)(\s(?:start)(?:s|ing)?)(.*)/i
108
+ start_every_regex = /^(start(?:s|ing)?)\s(.*)(\s(?:every|each|\bon\b|repeat)(?:s|ing)?)(.*)/i
109
+ every_start_regex = /^(every|each|\bon\b|repeat(?:the)?)\s(.*)(\s(?:start)(?:s|ing)?)(.*)/i
110
+ start_ending_regex = /^(start(?:s|ing)?)\s(.*)(\s(?:\bend|until)(?:s|ing)?)(.*)/i
106
111
  if text =~ start_every_regex
107
- starting = text.match(start_every_regex)[2]
108
- text = text.match(start_every_regex)[4]
112
+ starting = text.match(start_every_regex)[2].strip
113
+ text = text.match(start_every_regex)[4].strip
109
114
  event, ending = process_for_ending(text)
110
115
  elsif text =~ every_start_regex
111
- event = text.match(every_start_regex)[2]
112
- text = text.match(every_start_regex)[4]
116
+ event = text.match(every_start_regex)[2].strip
117
+ text = text.match(every_start_regex)[4].strip
113
118
  starting, ending = process_for_ending(text)
119
+ elsif text =~ start_ending_regex
120
+ starting = text.match(start_ending_regex)[2].strip
121
+ ending = text.match(start_ending_regex)[4].strip
122
+ event = 'day'
114
123
  else
115
124
  event, ending = process_for_ending(text)
116
125
  end
117
126
 
118
- @start = (starting && Tickle.parse(pre_filter(starting), {:next_only => true}) || options[:start]).to_time
119
- @until = (ending && Tickle.parse(pre_filter(ending), {:next_only => true}) || options[:until])
120
- @until = @until.to_time if @until
127
+ # they gave a phrase so if we can't interpret then we need to raise an error
128
+ if starting
129
+ Tickle.dwrite("starting: #{starting}")
130
+ @start = chronic_parse(pre_filter(starting))
131
+ if @start
132
+ @start.to_time
133
+ else
134
+ raise(InvalidDateExpression,"the starting date expression \"#{starting}\" could not be interpretted")
135
+ end
136
+ else
137
+ @start = options[:start].to_time rescue nil
138
+ end
139
+
140
+ if ending
141
+ @until = chronic_parse(pre_filter(ending))
142
+ if @until
143
+ @until.to_time
144
+ else
145
+ raise(InvalidDateExpression,"the ending date expression \"#{ending}\" could not be interpretted")
146
+ end
147
+ else
148
+ @until = options[:until].to_time rescue nil
149
+ end
150
+
121
151
  @next = nil
152
+
122
153
  return event
123
154
  end
124
155
 
125
156
  # process the remaining expression to see if an until, end, ending is specified
126
157
  def process_for_ending(text)
127
- regex = /^(.*)(\s(?:end|until)(?:s|ing)?)(.*)/i
158
+ regex = /^(.*)(\s(?:\bend|until)(?:s|ing)?)(.*)/i
128
159
  if text =~ regex
129
160
  return text.match(regex)[1], text.match(regex)[3]
130
161
  else
@@ -166,21 +197,21 @@ module Tickle #:nodoc:
166
197
  normalized_text = Numerizer.numerize(normalized_text)
167
198
  normalized_text.gsub!(/['"\.]/, '')
168
199
  normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
169
- normalized_text.gsub!(/\btoday\b/, 'this day')
170
- normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
171
- normalized_text.gsub!(/\byesterday\b/, 'last day')
172
- normalized_text.gsub!(/\bnoon\b/, '12:00')
173
- normalized_text.gsub!(/\bmidnight\b/, '24:00')
174
- normalized_text.gsub!(/\bbefore now\b/, 'past')
175
- normalized_text.gsub!(/\bnow\b/, 'this second')
176
- normalized_text.gsub!(/\b(ago|before)\b/, 'past')
177
- normalized_text.gsub!(/\bthis past\b/, 'last')
178
- normalized_text.gsub!(/\bthis last\b/, 'last')
179
- normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
180
- normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
181
- normalized_text.gsub!(/\btonight\b/, 'this night')
182
- normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
183
- normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
200
+ # normalized_text.gsub!(/\btoday\b/, 'this day')
201
+ # normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
202
+ # normalized_text.gsub!(/\byesterday\b/, 'last day')
203
+ # normalized_text.gsub!(/\bnoon\b/, '12:00')
204
+ # normalized_text.gsub!(/\bmidnight\b/, '24:00')
205
+ # normalized_text.gsub!(/\bbefore now\b/, 'past')
206
+ # normalized_text.gsub!(/\bnow\b/, 'this second')
207
+ # normalized_text.gsub!(/\b(ago|before)\b/, 'past')
208
+ # normalized_text.gsub!(/\bthis past\b/, 'last')
209
+ # normalized_text.gsub!(/\bthis last\b/, 'last')
210
+ # normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
211
+ # normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
212
+ # normalized_text.gsub!(/\btonight\b/, 'this night')
213
+ # normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
214
+ # normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
184
215
  normalized_text
185
216
  end
186
217
 
@@ -206,10 +237,10 @@ module Tickle #:nodoc:
206
237
  protected
207
238
 
208
239
  # Returns the next available month based on the current day of the month.
209
- # For example, if get_next_month(15) is called and today is the 10th, then it will return the 15th of this month.
210
- # However, if get_next_month(15) is called and today is the 18th, it will return the 15th of next month.
240
+ # For example, if get_next_month(15) is called and the start date is the 10th, then it will return the 15th of this month.
241
+ # However, if get_next_month(15) is called and the start date is the 18th, it will return the 15th of next month.
211
242
  def get_next_month(number)
212
- month = number.to_i < Date.today.day ? (Date.today.month == 12 ? 1 : Date.today.month + 1) : Date.today.month
243
+ month = number.to_i < @start.day ? (@start.month == 12 ? 1 : @start.month + 1) : @start.month
213
244
  end
214
245
 
215
246
  # Return the number of days in a specified month.
@@ -218,6 +249,19 @@ module Tickle #:nodoc:
218
249
  month ||= Date.today.month
219
250
  days_in_mon = Date.civil(Date.today.year, month, -1).day
220
251
  end
252
+
253
+ private
254
+
255
+ # slightly modified chronic parser to ensure that the date found is in the future
256
+ # first we check to see if an explicit date was passed and, if so, dont do anything.
257
+ # if, however, a date expression was passed we evaluate and shift forward if needed
258
+ def chronic_parse(exp)
259
+ result = Chronic.parse(exp.ordinal_as_number)
260
+ result = Time.local(result.year + 1, result.month, result.day, result.hour, result.min, result.sec) if result && result.to_time < Time.now
261
+ Tickle.dwrite("Chronic.parse('#{exp}') # => #{result}")
262
+ result
263
+ end
264
+
221
265
  end
222
266
 
223
267
  class Token #:nodoc:
data/lib/tickle.rb CHANGED
@@ -20,10 +20,10 @@ require 'tickle/repeater'
20
20
  module Tickle #:nodoc:
21
21
  VERSION = "0.1.5"
22
22
 
23
- def self.debug; false; end
23
+ def self.debug=(val); @debug = val; end
24
24
 
25
- def self.dwrite(msg)
26
- puts msg if Tickle.debug
25
+ def self.dwrite(msg, line_feed=nil)
26
+ (line_feed ? p(">> #{msg}") : puts(">> #{msg}")) if @debug
27
27
  end
28
28
 
29
29
  def self.is_date(str)
@@ -43,6 +43,56 @@ class Date #:nodoc:
43
43
  d += 1 while Date.valid_civil?(y,m,d)
44
44
  d - 1
45
45
  end
46
+
47
+ def bump(attr, amount=nil)
48
+ amount ||= 1
49
+ case attr
50
+ when :day then
51
+ Date.civil(self.year, self.month, self.day + amount)
52
+ when :wday then
53
+ amount = Date::ABBR_DAYNAMES.index(amount) if amount.is_a?(String)
54
+ raise Exception, "specified day of week invalid. Use #{Date::ABBR_DAYNAMES}" unless amount
55
+ diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
56
+ Date.civil(self.year, self.month, self.day + diff)
57
+ when :week then
58
+ Date.civil(self.year, self.month, self.day + (7*amount))
59
+ when :month then
60
+ Date.civil(self.year, self.month+amount, self.day)
61
+ when :year then
62
+ Date.civil(self.year + amount, self.month, self.day)
63
+ else
64
+ raise Exception, "type \"#{attr}\" not supported."
65
+ end
66
+ end
67
+ end
68
+
69
+ class Time #:nodoc:
70
+ def bump(attr, amount=nil)
71
+ amount ||= 1
72
+ case attr
73
+ when :sec then
74
+ Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec + amount)
75
+ when :min then
76
+ Time.local(self.year, self.month, self.day, self.hour, self.min + amount, self.sec)
77
+ when :hour then
78
+ Time.local(self.year, self.month, self.day, self.hour + amount, self.min, self.sec)
79
+ when :day then
80
+ Time.local(self.year, self.month, self.day + amount, self.hour, self.min, self.sec)
81
+ when :wday then
82
+ amount = Time::RFC2822_DAY_NAME.index(amount) if amount.is_a?(String)
83
+ raise Exception, "specified day of week invalid. Use #{Time::RFC2822_DAY_NAME}" unless amount
84
+ diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
85
+ Time.local(self.year, self.month, self.day + diff, self.hour, self.min, self.sec)
86
+ when :week then
87
+ Time.local(self.year, self.month, self.day + (amount * 7), self.hour, self.min, self.sec)
88
+ when :month then
89
+ Time.local(self.year, self.month + amount, self.day, self.hour, self.min, self.sec)
90
+ when :year then
91
+ Time.local(self.year + amount, self.month, self.day, self.hour, self.min, self.sec)
92
+ else
93
+ raise Exception, "type \"#{attr}\" not supported."
94
+ end
95
+ end
46
96
  end
47
97
 
48
98
  class String #:nodoc:
@@ -52,6 +102,35 @@ class String #:nodoc:
52
102
  regex = /\b(\d*)(st|nd|rd|th)\b/
53
103
  !(self =~ regex).nil? || scanner.include?(self.downcase)
54
104
  end
105
+
106
+ def ordinal_as_number
107
+ return self unless self.is_ordinal?
108
+ scanner = {/first/ => '1st',
109
+ /second/ => '2nd',
110
+ /third/ => '3rd',
111
+ /fourth/ => '4th',
112
+ /fifth/ => '5th',
113
+ /sixth/ => '6th',
114
+ /seventh/ => '7th',
115
+ /eighth/ => '8th',
116
+ /ninth/ => '9th',
117
+ /tenth/ => '10th',
118
+ /eleventh/ => '11th',
119
+ /twelfth/ => '12th',
120
+ /thirteenth/ => '13th',
121
+ /fourteenth/ => '14th',
122
+ /fifteenth/ => '15th',
123
+ /sixteenth/ => '16th',
124
+ /seventeenth/ => '17th',
125
+ /eighteenth/ => '18th',
126
+ /nineteenth/ => '19th',
127
+ /twentieth/ => '20th',
128
+ /thirtieth/ => '30th',
129
+ }
130
+ result = self
131
+ scanner.keys.each {|scanner_item| result = scanner[scanner_item] if scanner_item =~ self}
132
+ return result.gsub(/\b(\d*)(st|nd|rd|th)\b/, '\1')
133
+ end
55
134
  end
56
135
 
57
136
  class Array #:nodoc:
data/test/test_parsing.rb CHANGED
@@ -5,113 +5,129 @@ require 'test/unit'
5
5
  class TestParsing < Test::Unit::TestCase
6
6
 
7
7
  def setup
8
- end
8
+ Tickle.debug = (ARGV.detect {|a| a == '--d'})
9
+ @verbose = (ARGV.detect {|a| a == '--v'})
9
10
 
10
- def test_parse_best_guess_simple
11
11
  puts "Time.now"
12
12
  p Time.now
13
13
 
14
- parse_now('each day')
15
-
16
- parse_now('every day')
17
- parse_now('every week')
18
- parse_now('every Month')
19
- parse_now('every year')
20
-
21
- parse_now('daily')
22
- parse_now('weekly')
23
- parse_now('monthly')
24
- parse_now('yearly')
25
-
26
- parse_now('every 3 days')
27
- parse_now('every 3 weeks')
28
- parse_now('every 3 months')
29
- parse_now('every 3 years')
30
-
31
- parse_now('every other day')
32
- parse_now('every other week')
33
- parse_now('every other month')
34
- parse_now('every other year')
35
-
36
- parse_now('every Monday')
37
- parse_now('every Wednesday')
38
- parse_now('every Friday')
39
-
40
- parse_now('every May')
41
- parse_now('every june')
42
-
43
- parse_now('beginning of the week')
44
- parse_now('middle of the week')
45
- parse_now('end of the week')
46
-
47
- parse_now('beginning of the month')
48
- parse_now('middle of the month')
49
- parse_now('end of the month')
50
-
51
- parse_now('beginning of the year')
52
- parse_now('middle of the year')
53
- parse_now('end of the year')
54
-
55
- parse_now('the 3rd of May')
56
- parse_now('the 3rd of February')
57
- parse_now('the 3rd of February 2012')
58
- parse_now('the 3rd of Feb, 2012')
59
-
60
- parse_now('the 4th of the month')
61
- parse_now('the 10th of the month')
62
- parse_now('the tenth of the month')
63
-
64
- parse_now('the first of the month')
65
- parse_now('the thirtieth')
66
- parse_now('the fifth')
67
-
68
- parse_now('the 3rd Sunday of May')
69
- parse_now('the 3rd Sunday of the month')
70
-
71
- parse_now('the 23rd of June')
72
- parse_now('the twenty third of June')
73
- parse_now('the thirty first of August')
14
+ @date = Date.today
15
+ end
74
16
 
75
- parse_now('the twenty first')
76
- parse_now('the twenty first of the month')
17
+ def test_parse_best_guess_simple
18
+ start = Date.new(2020, 04, 01)
19
+
20
+ assert_date_match(@date.bump(:day, 1), 'each day')
21
+ assert_date_match(@date.bump(:day, 1), 'every day')
22
+ assert_date_match(@date.bump(:week, 1), 'every week')
23
+ assert_date_match(@date.bump(:month, 1), 'every month')
24
+ assert_date_match(@date.bump(:year, 1), 'every year')
25
+
26
+ assert_date_match(@date.bump(:day, 1), 'daily')
27
+ assert_date_match(@date.bump(:week, 1) , 'weekly')
28
+ assert_date_match(@date.bump(:month, 1) , 'monthly')
29
+ assert_date_match(@date.bump(:year, 1) , 'yearly')
30
+
31
+ assert_date_match(@date.bump(:day, 3), 'every 3 days')
32
+ assert_date_match(@date.bump(:week, 3), 'every 3 weeks')
33
+ assert_date_match(@date.bump(:month, 3), 'every 3 months')
34
+ assert_date_match(@date.bump(:year, 3), 'every 3 years')
35
+
36
+ assert_date_match(@date.bump(:day, 2), 'every other day')
37
+ assert_date_match(@date.bump(:week, 2), 'every other week')
38
+ assert_date_match(@date.bump(:month, 2), 'every other month')
39
+ assert_date_match(@date.bump(:year, 2), 'every other year')
40
+
41
+ assert_date_match(@date.bump(:wday, 'Mon'), 'every Monday')
42
+ assert_date_match(@date.bump(:wday, 'Wed'), 'every Wednesday')
43
+ assert_date_match(@date.bump(:wday, 'Fri'), 'every Friday')
44
+
45
+ assert_date_match(Date.new(2021, 2, 1), 'every February', {:start => start, :now => start})
46
+ assert_date_match(Date.new(2020, 5, 1), 'every May', {:start => start, :now => start})
47
+ assert_date_match(Date.new(2020, 6, 1), 'every june', {:start => start, :now => start})
48
+
49
+ assert_date_match(@date.bump(:wday, 'Sun'), 'beginning of the week')
50
+ assert_date_match(@date.bump(:wday, 'Wed'), 'middle of the week')
51
+ assert_date_match(@date.bump(:wday, 'Sat'), 'end of the week')
52
+
53
+ assert_date_match(Date.new(2020, 05, 01), 'beginning of the month', {:start => start, :now => start})
54
+ assert_date_match(Date.new(2020, 04, 15), 'middle of the month', {:start => start, :now => start})
55
+ assert_date_match(Date.new(2020, 04, 30), 'end of the month', {:start => start, :now => start})
56
+
57
+ assert_date_match(Date.new(2021, 01, 01), 'beginning of the year', {:start => start, :now => start})
58
+ assert_date_match(Date.new(2020, 06, 15), 'middle of the year', {:start => start, :now => start})
59
+ assert_date_match(Date.new(2020, 12, 31), 'end of the year', {:start => start, :now => start})
60
+
61
+ assert_date_match(Date.new(2020, 05, 03), 'the 3rd of May', {:start => start, :now => start})
62
+ assert_date_match(Date.new(2021, 02, 03), 'the 3rd of February', {:start => start, :now => start})
63
+ assert_date_match(Date.new(2022, 02, 03), 'the 3rd of February 2022', {:start => start, :now => start})
64
+ assert_date_match(Date.new(2022, 02, 03), 'the 3rd of Feb, 2022', {:start => start, :now => start})
65
+
66
+ assert_date_match(Date.new(2020, 04, 04), 'the 4th of the month', {:start => start, :now => start})
67
+ assert_date_match(Date.new(2020, 04, 10), 'the 10th of the month', {:start => start, :now => start})
68
+ assert_date_match(Date.new(2020, 04, 10), 'the tenth of the month', {:start => start, :now => start})
69
+
70
+ assert_date_match(Date.new(2020, 05, 01), 'first', {:start => start, :now => start})
71
+
72
+ assert_date_match(Date.new(2020, 05, 01), 'the first of the month', {:start => start, :now => start})
73
+ assert_date_match(Date.new(2020, 04, 30), 'the thirtieth', {:start => start, :now => start})
74
+ assert_date_match(Date.new(2020, 04, 05), 'the fifth', {:start => start, :now => start})
75
+
76
+ assert_date_match(Date.new(2020, 05, 01), 'the 1st Wednesday of the month', {:start => start, :now => start})
77
+ assert_date_match(Date.new(2020, 05, 17), 'the 3rd Sunday of May', {:start => start, :now => start})
78
+ assert_date_match(Date.new(2020, 04, 19), 'the 3rd Sunday of the month', {:start => start, :now => start})
79
+
80
+ assert_date_match(Date.new(2020, 06, 23), 'the 23rd of June', {:start => start, :now => start})
81
+ assert_date_match(Date.new(2020, 06, 23), 'the twenty third of June', {:start => start, :now => start})
82
+ assert_date_match(Date.new(2020, 07, 31), 'the thirty first of July', {:start => start, :now => start})
83
+
84
+ assert_date_match(Date.new(2020, 04, 21), 'the twenty first', {:start => start, :now => start})
85
+ assert_date_match(Date.new(2020, 04, 21), 'the twenty first of the month', {:start => start, :now => start})
77
86
  end
78
87
 
79
88
  def test_parse_best_guess_complex
80
- puts "Time.now"
81
- p Time.now
89
+ start = Date.new(2020, 04, 01)
82
90
 
83
- parse_now('starting Monday repeat every month')
84
- parse_now('starting May 13th repeat every week')
85
- parse_now('starting May 13th repeat every other day')
91
+ assert_tickle_match(@date.bump(:day, 1), @date, @date.bump(:week, 1), 'day', 'starting today and ending one week from now') if Time.now.hour < 21 # => demonstrates leaving out the actual time period and implying it as daily
92
+ assert_tickle_match(@date.bump(:day, 1), @date.bump(:day, 1), @date.bump(:week, 1), 'day','starting tomorrow and ending one week from now') # => demonstrates leaving out the actual time period and implying it as daily.
86
93
 
87
- parse_now('every week starts this wednesday')
88
- parse_now('every other day starts the 1st May')
89
- parse_now('every other day starting May 6')
90
- parse_now('every week starting this wednesday')
91
- parse_now('every other day starting the 1st May')
94
+ assert_tickle_match(@date.bump(:wday, 'Mon'), @date.bump(:wday, 'Mon'), nil, 'month', 'starting Monday repeat every month')
95
+
96
+ year = @date >= Date.new(@date.year, 5, 13) ? @date.bump(:year,1) : @date.year
97
+ assert_tickle_match(Date.new(year, 05, 13), Date.new(year, 05, 13), nil, 'week', 'starting May 13th repeat every week')
98
+ assert_tickle_match(Date.new(year, 05, 13), Date.new(year, 05, 13), nil, 'other day', 'starting May 13th repeat every other day')
99
+ assert_tickle_match(Date.new(year, 05, 13), Date.new(year, 05, 13), nil, 'other day', 'every other day starts May 13th')
100
+ assert_tickle_match(Date.new(year, 05, 13), Date.new(year, 05, 13), nil, 'other day', 'every other day starts May 13')
101
+ assert_tickle_match(Date.new(year, 05, 13), Date.new(year, 05, 13), nil, 'other day', 'every other day starting May 13th')
102
+ assert_tickle_match(Date.new(year, 05, 13), Date.new(year, 05, 13), nil, 'other day', 'every other day starting May 13')
92
103
 
93
- parse_now("every other day starting May 1st #{Date.today.year + 1}")
94
- parse_now('every other week starting this Sunday')
104
+ assert_tickle_match(@date.bump(:wday, 'Wed'), @date.bump(:wday, 'Wed'), nil, 'week', 'every week starts this wednesday')
105
+ assert_tickle_match(@date.bump(:wday, 'Wed'), @date.bump(:wday, 'Wed'), nil, 'week', 'every week starting this wednesday')
95
106
 
96
- parse_now('every week starting this wednesday until June 5th')
97
- parse_now('every week starting this wednesday ends June 5th')
98
- parse_now('every week starting this wednesday ending June 5th')
107
+ assert_tickle_match(Date.new(2021, 05, 01), Date.new(2021, 05, 01), nil, 'other day', "every other day starting May 1st #{start.bump(:year, 1).year}")
108
+ assert_tickle_match(Date.new(2021, 05, 01), Date.new(2021, 05, 01), nil, 'other day', "every other day starting May 1 #{start.bump(:year, 1).year}")
109
+ assert_tickle_match(@date.bump(:wday, 'Sun'), @date.bump(:wday, 'Sun'), nil, 'other week', 'every other week starting this Sunday')
110
+
111
+ assert_tickle_match(@date.bump(:wday, 'Wed'), @date.bump(:wday, 'Wed'), Date.new(year, 05, 13), 'week', 'every week starting this wednesday until May 13th')
112
+ assert_tickle_match(@date.bump(:wday, 'Wed'), @date.bump(:wday, 'Wed'), Date.new(year, 05, 13), 'week', 'every week starting this wednesday ends May 13th')
113
+ assert_tickle_match(@date.bump(:wday, 'Wed'), @date.bump(:wday, 'Wed'), Date.new(year, 05, 13), 'week', 'every week starting this wednesday ending May 13th')
99
114
 
100
115
  end
101
116
 
102
117
  def test_tickle_args
103
- parse_now('May 1st, 2011', {:next_only => true})
104
-
105
- start_date = Date.civil(Date.today.year, Date.today.month, Date.today.day + 10)
106
- parse_now('every 3 days', {:start => start_date})
107
- parse_now('every 3 weeks', {:start => start_date})
108
- parse_now('every 3 months', {:start => start_date})
109
- parse_now('every 3 years', {:start => start_date})
110
-
111
- end_date = Date.civil(Date.today.year, Date.today.month+5, Date.today.day)
112
- parse_now('every 3 days', {:start => start_date, :until => end_date})
113
- parse_now('every 3 weeks', {:start => start_date, :until => end_date})
114
- parse_now('every 3 months', {:until => end_date})
118
+ actual_next_only = parse_now('May 1st, 2020', {:next_only => true}).to_date
119
+ assert(Date.new(2020, 05, 01).to_date == actual_next_only, "\"May 1st, 2011\" :next parses to #{actual_next_only} but should be equal to #{Date.new(2020, 05, 01).to_date}")
120
+
121
+ start_date = Time.now
122
+ assert_tickle_match(start_date.bump(:day, 3), @date, nil, '3 days', 'every 3 days', {:start => start_date})
123
+ assert_tickle_match(start_date.bump(:week, 3), @date, nil, '3 weeks', 'every 3 weeks', {:start => start_date})
124
+ assert_tickle_match(start_date.bump(:month, 3), @date, nil, '3 months', 'every 3 months', {:start => start_date})
125
+ assert_tickle_match(start_date.bump(:year, 3), @date, nil, '3 years', 'every 3 years', {:start => start_date})
126
+
127
+ end_date = Date.civil(Date.today.year, Date.today.month+5, Date.today.day).to_time
128
+ assert_tickle_match(start_date.bump(:day, 3), @date, start_date.bump(:month, 5), '3 days', 'every 3 days', {:start => start_date, :until => end_date})
129
+ assert_tickle_match(start_date.bump(:week, 3), @date, start_date.bump(:month, 5), '3 weeks', 'every 3 weeks', {:start => start_date, :until => end_date})
130
+ assert_tickle_match(start_date.bump(:month, 3), @date, start_date.bump(:month, 5), '3 months', 'every 3 months', {:until => end_date})
115
131
  end
116
132
 
117
133
  def test_argument_validation
@@ -147,8 +163,27 @@ class TestParsing < Test::Unit::TestCase
147
163
  private
148
164
  def parse_now(string, options={})
149
165
  out = Tickle.parse(string, {}.merge(options))
150
- puts (options.empty? ? ("Tickle.parse('#{string}') #=> #{out}") : ("Tickle.parse('#{string}, #{options}') #=> #{out}"))
151
- p '--' if Tickle.debug
166
+ puts (options.empty? ? ("Tickle.parse('#{string}')\n\r #=> #{out}\n\r") : ("Tickle.parse('#{string}', #{options})\n\r #=> #{out}\n\r")) if @verbose
152
167
  out
153
168
  end
169
+
170
+ def assert_date_match(expected_next, date_expression, options={})
171
+ actual_next = parse_now(date_expression, options)[:next].to_date
172
+ assert (expected_next.to_date == actual_next.to_date), "\"#{date_expression}\" parses to #{actual_next} but should be equal to #{expected_next}"
173
+ end
174
+
175
+ def assert_tickle_match(expected_next, expected_start, expected_until, expected_expression, date_expression, options={})
176
+ result = parse_now(date_expression, options)
177
+ actual_next = result[:next].to_date
178
+ actual_start = result[:starting].to_date
179
+ actual_until = result[:until].to_date rescue nil
180
+ expected_until = expected_until.to_date rescue nil
181
+ actual_expression = result[:expression]
182
+
183
+ assert (expected_next.to_date == actual_next.to_date), "\"#{date_expression}\" :next parses to #{actual_next} but should be equal to #{expected_next}"
184
+ assert (expected_start.to_date == actual_start.to_date), "\"#{date_expression}\" :starting parses to #{actual_start} but should be equal to #{expected_start}"
185
+ assert (expected_until == actual_until), "\"#{date_expression}\" :until parses to #{actual_until} but should be equal to #{expected_until}"
186
+ assert (expected_expression == actual_expression), "\"#{date_expression}\" :expression parses to \"#{actual_expression}\" but should be equal to \"#{expected_expression}\""
187
+ end
188
+
154
189
  end
data/tickle.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{tickle}
8
- s.version = "0.1.5"
8
+ s.version = "0.1.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Joshua Lippiner"]
12
- s.date = %q{2010-05-07}
12
+ s.date = %q{2010-05-09}
13
13
  s.description = %q{Tickle is a date/time helper gem to help parse natural language into a recurring pattern. Tickle is designed to be a compliment of Chronic and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc.}
14
14
  s.email = %q{jlippiner@noctivity.com}
15
15
  s.extra_rdoc_files = [
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 5
9
- version: 0.1.5
8
+ - 6
9
+ version: 0.1.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Joshua Lippiner
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-07 00:00:00 -04:00
17
+ date: 2010-05-09 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency