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 +237 -83
- data/VERSION +1 -1
- data/git-flow-version +1 -1
- data/lib/tickle/handler.rb +46 -36
- data/lib/tickle/repeater.rb +14 -19
- data/lib/tickle/tickle.rb +81 -37
- data/lib/tickle.rb +82 -3
- data/test/test_parsing.rb +128 -93
- data/tickle.gemspec +2 -2
- metadata +3 -3
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
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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(
|
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
|
323
|
+
Tickle comes with a full testing suite for simple, complex, options hash and invalid arguments.
|
171
324
|
|
172
|
-
|
173
|
-
|
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.
|
1
|
+
0.1.6
|
data/git-flow-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
GITFLOW_VERSION=0.1.
|
1
|
+
GITFLOW_VERSION=0.1.6
|
data/lib/tickle/handler.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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 =
|
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 =
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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 =
|
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 =
|
54
|
-
@next =
|
55
|
-
@next =
|
56
|
-
|
57
|
-
|
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
|
-
|
71
|
-
|
72
|
-
@next =
|
73
|
-
@next =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
data/lib/tickle/repeater.rb
CHANGED
@@ -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,
|
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,
|
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)?$/ =>
|
67
|
-
/^feb\.?(ruary)?$/ =>
|
68
|
-
/^mar\.?(ch)?$/ =>
|
69
|
-
/^apr\.?(il)?$/ =>
|
70
|
-
/^may$/ =>
|
71
|
-
/^jun\.?e?$/ =>
|
72
|
-
/^jul\.?y?$/ =>
|
73
|
-
/^aug\.?(ust)?$/ =>
|
74
|
-
/^sep\.?(t\.?|tember)?$/ =>
|
75
|
-
/^oct\.?(ober)?$/ =>
|
76
|
-
/^nov\.?(ember)?$/ =>
|
77
|
-
/^dec\.?(ember)?$/ =>
|
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
|
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
|
-
|
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.
|
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
|
-
|
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|
|
87
|
+
@tokens.each {|x| Tickle.dwrite("processed: #{x.inspect}")}
|
85
88
|
|
86
|
-
|
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|
|
105
|
-
every_start_regex = /^(every|each|
|
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
|
-
|
119
|
-
|
120
|
-
|
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(
|
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
|
210
|
-
# However, if get_next_month(15) is called and
|
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 <
|
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;
|
23
|
+
def self.debug=(val); @debug = val; end
|
24
24
|
|
25
|
-
def self.dwrite(msg)
|
26
|
-
puts msg if
|
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
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
81
|
-
p Time.now
|
89
|
+
start = Date.new(2020, 04, 01)
|
82
90
|
|
83
|
-
|
84
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
94
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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,
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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}
|
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.
|
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-
|
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
|
-
-
|
9
|
-
version: 0.1.
|
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-
|
17
|
+
date: 2010-05-09 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|