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