tickle 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|