nickel 0.0.3 → 0.0.4
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/License.txt +2 -2
- data/README.rdoc +24 -12
- data/Rakefile +22 -0
- data/lib/nickel/construct.rb +121 -0
- data/lib/nickel/construct_finder.rb +1145 -0
- data/lib/nickel/construct_interpreter.rb +345 -0
- data/lib/nickel/instance_from_hash.rb +13 -0
- data/lib/nickel/nlp.rb +73 -0
- data/lib/nickel/occurrence.rb +90 -0
- data/lib/nickel/query.rb +1143 -0
- data/lib/nickel/query_constants.rb +26 -0
- data/lib/nickel/ruby_ext/calling_method.rb +10 -0
- data/lib/nickel/ruby_ext/to_s2.rb +12 -0
- data/lib/nickel/zdate.rb +503 -0
- data/lib/nickel/ztime.rb +319 -0
- data/lib/nickel.rb +30 -34
- data/nickel.gemspec +30 -10
- data/{test → spec}/nickel_spec.rb +4 -4
- data/test/compare.rb +109 -0
- data/test/nlp_test.rb +813 -0
- data/test/nlp_tests_helper.rb +55 -0
- data/test/zdate_test.rb +43 -0
- data/test/ztime_test.rb +428 -0
- metadata +34 -22
@@ -0,0 +1,26 @@
|
|
1
|
+
# Ruby Nickel Library
|
2
|
+
# Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
|
3
|
+
# MIT License [http://www.opensource.org/licenses/mit-license.php]
|
4
|
+
|
5
|
+
module Nickel
|
6
|
+
module NLPQueryConstants
|
7
|
+
DATE_DD = %r{\b((?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?)\b}
|
8
|
+
DATE_DD_NB_ON_SUFFIX = %r{\b(0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?\b}
|
9
|
+
DATE_DD_NB = %r{\b(?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?\b}
|
10
|
+
DATE_DD_WITH_SUFFIX = %r{\b((?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th))\b}
|
11
|
+
DATE_DD_WITHOUT_SUFFIX = %r{\b(0?[1-9]|[12][0-9]|3[01])\b}
|
12
|
+
DATE_DD_WITH_SUFFIX_NB = %r{\b(?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)\b}
|
13
|
+
DATE_DD_WITHOUT_SUFFIX_NB = %r{\b(?:0?[1-9]|[12][0-9]|3[01])\b}
|
14
|
+
DATE_DD_WITH_SUFFIX_NB_ON_SUFFIX = %r{\b(0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)\b}
|
15
|
+
DATE_MM_SLASH_DD = %r{\b(?:0?[1-9]|[1][0-2])\/(?:0?[1-9]|[12][0-9]|3[01])}
|
16
|
+
DAY_OF_WEEK = %r{\b(mon|tue|wed|thu|fri|sat|sun)\b}
|
17
|
+
DAY_OF_WEEK_NB = %r{\b(?:mon|tue|wed|thu|fri|sat|sun)\b} #no backreference
|
18
|
+
MONTH_OF_YEAR = %r{\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b}
|
19
|
+
MONTH_OF_YEAR_NB = %r{\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b}
|
20
|
+
TIME_24HR = %r{[01]?[0-9]|2[0-3]:[0-5][0-9]}
|
21
|
+
TIME_12HR = %r{(?:0?[1-9]|1[0-2])(?::[0-5][0-9])?(?:am|pm)?} # note 12 passes, as does 12:00
|
22
|
+
TIME = %r{(#{TIME_12HR}|#{TIME_24HR})}
|
23
|
+
YEAR = %r{((?:20)?0[789](?:\s|\n)|(?:20)[1-9][0-9])}
|
24
|
+
WEEK_OF_MONTH = %r{(1st|2nd|3rd|4th|5th)}
|
25
|
+
end
|
26
|
+
end
|
data/lib/nickel/zdate.rb
ADDED
@@ -0,0 +1,503 @@
|
|
1
|
+
# Ruby Nickel Library
|
2
|
+
# Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
|
3
|
+
# MIT License [http://www.opensource.org/licenses/mit-license.php]
|
4
|
+
|
5
|
+
module Nickel
|
6
|
+
|
7
|
+
# TODO: get methods should accept dayname or dayindex
|
8
|
+
class ZDate
|
9
|
+
@days_of_week = ["mon","tue","wed","thu","fri","sat","sun"]
|
10
|
+
@full_days_of_week = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
11
|
+
@months_of_year = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]
|
12
|
+
@full_months_of_year = ["january","february","march","april","may","june","july","august","september","october","november","december"]
|
13
|
+
@days_in_common_year_months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
14
|
+
@days_in_leap_year_months = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
15
|
+
|
16
|
+
MON = 0
|
17
|
+
TUE = 1
|
18
|
+
WED = 2
|
19
|
+
THU = 3
|
20
|
+
FRI = 4
|
21
|
+
SAT = 5
|
22
|
+
SUN = 6
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_reader :days_of_week, :months_of_year, :days_in_common_year_months, :days_in_leap_year_months, :full_days_of_week, :full_months_of_year
|
26
|
+
end
|
27
|
+
|
28
|
+
# Don't use attr_accessor for date, year, month, day; we want to validate on change.
|
29
|
+
def initialize(yyyymmdd = nil)
|
30
|
+
d = yyyymmdd ? yyyymmdd.dup : ::Time.new.strftime("%Y%m%d")
|
31
|
+
d.gsub!(/-/,'') # remove any hyphens, so a user can initialize with something like "2008-10-23"
|
32
|
+
self.date = d
|
33
|
+
end
|
34
|
+
|
35
|
+
def date
|
36
|
+
@date
|
37
|
+
end
|
38
|
+
|
39
|
+
def date=(yyyymmdd)
|
40
|
+
@date = yyyymmdd
|
41
|
+
validate
|
42
|
+
end
|
43
|
+
|
44
|
+
def year_str
|
45
|
+
@date[0..3]
|
46
|
+
end
|
47
|
+
|
48
|
+
def month_str
|
49
|
+
@date[4..5]
|
50
|
+
end
|
51
|
+
|
52
|
+
def day_str
|
53
|
+
@date[6..7]
|
54
|
+
end
|
55
|
+
|
56
|
+
def year
|
57
|
+
year_str.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
def month
|
61
|
+
month_str.to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
def day
|
65
|
+
day_str.to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
def readable
|
69
|
+
month_str + '/' + day_str + '/' + year_str
|
70
|
+
end
|
71
|
+
|
72
|
+
def fmt(txt)
|
73
|
+
txt.gsub!(/%Y/, self.year_str)
|
74
|
+
txt.gsub!(/%m/, self.month_str)
|
75
|
+
txt.gsub!(/%d/, self.day_str)
|
76
|
+
end
|
77
|
+
|
78
|
+
def <(d2)
|
79
|
+
(self.year < d2.year) || (self.year == d2.year && (self.month < d2.month || (self.month == d2.month && self.day < d2.day)))
|
80
|
+
end
|
81
|
+
|
82
|
+
def <=(d2)
|
83
|
+
(self.year < d2.year) || (self.year == d2.year && (self.month < d2.month || (self.month == d2.month && self.day <= d2.day)))
|
84
|
+
end
|
85
|
+
|
86
|
+
def >(d2)
|
87
|
+
(self.year > d2.year) || (self.year == d2.year && (self.month > d2.month || (self.month == d2.month && self.day > d2.day)))
|
88
|
+
end
|
89
|
+
|
90
|
+
def >=(d2)
|
91
|
+
(self.year > d2.year) || (self.year == d2.year && (self.month > d2.month || (self.month == d2.month && self.day >= d2.day)))
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(d2)
|
95
|
+
self.year == d2.year && self.month == d2.month && self.day == d2.day
|
96
|
+
end
|
97
|
+
|
98
|
+
def <=>(d2)
|
99
|
+
if self < d2
|
100
|
+
-1
|
101
|
+
elsif self > d2
|
102
|
+
1
|
103
|
+
else
|
104
|
+
0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# returns true if self is today
|
109
|
+
def is_today?
|
110
|
+
self == ZDate.new
|
111
|
+
end
|
112
|
+
|
113
|
+
# for example, "1st friday", uses self as the reference month
|
114
|
+
def ordinal_dayindex(num, day_index)
|
115
|
+
# create a date object at the first occurrence of day_index
|
116
|
+
first_occ_date = ZDate.new(year_str + month_str + "01").this(day_index)
|
117
|
+
# if num is 1 through 4, we can just add (num-1) weeks
|
118
|
+
if num <= 4
|
119
|
+
d = first_occ_date.add_weeks(num - 1)
|
120
|
+
else
|
121
|
+
# we want the last occurrence of this month
|
122
|
+
# add 4 weeks to first occurrence, see if we are in the same month, subtract 1 week if we are not
|
123
|
+
d = first_occ_date.add_weeks(4)
|
124
|
+
if d.month != self.month then d = d.sub_weeks(1) end
|
125
|
+
end
|
126
|
+
d
|
127
|
+
end
|
128
|
+
|
129
|
+
# for example, "this friday"
|
130
|
+
def this(day)
|
131
|
+
x_weeks_from_day(0, day)
|
132
|
+
end
|
133
|
+
|
134
|
+
# for example, "next friday"
|
135
|
+
def next(day)
|
136
|
+
x_weeks_from_day(1, day)
|
137
|
+
end
|
138
|
+
|
139
|
+
# for example, "previous friday"
|
140
|
+
def prev(day)
|
141
|
+
(self.dayindex == day) ? self.dup : x_weeks_from_day(-1,day)
|
142
|
+
end
|
143
|
+
|
144
|
+
# returns a new date object
|
145
|
+
def x_weeks_from_day(weeks_away, day2index)
|
146
|
+
day1index = self.dayindex
|
147
|
+
if day1index > day2index
|
148
|
+
days_away = 7 * (weeks_away + 1) - (day1index - day2index)
|
149
|
+
elsif day1index < day2index
|
150
|
+
days_away = (weeks_away * 7) + (day2index - day1index)
|
151
|
+
elsif day1index == day2index
|
152
|
+
days_away = 7 * weeks_away
|
153
|
+
end
|
154
|
+
self.add_days(days_away) # returns a new date object
|
155
|
+
end
|
156
|
+
|
157
|
+
# add_ methods return new ZDate object, they DO NOT modify self
|
158
|
+
def add_days(number)
|
159
|
+
if number < 0 then return sub_days(number.abs) end
|
160
|
+
o = self.dup # new ZDate object
|
161
|
+
# Let's see what month we are going to end in
|
162
|
+
while number > 0
|
163
|
+
if o.days_left_in_month >= number
|
164
|
+
o.date = o.year_str + o.month_str + (o.day + number).to_s2
|
165
|
+
number = 0
|
166
|
+
else
|
167
|
+
number = number - 1 - o.days_left_in_month #it costs 1 day to increment the month
|
168
|
+
o.increment_month!
|
169
|
+
end
|
170
|
+
end
|
171
|
+
o
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_weeks(number)
|
175
|
+
self.add_days(7*number)
|
176
|
+
end
|
177
|
+
|
178
|
+
def add_months(number)
|
179
|
+
new_month = 1 + ((month - 1 + number) % 12)
|
180
|
+
if number > months_left_in_year # are we going to change year?
|
181
|
+
years_to_increment = 1 + ((number - months_left_in_year) / 12) # second term adds years if user entered a large number of months (e.g. date.add_months(50))
|
182
|
+
else
|
183
|
+
years_to_increment = 0
|
184
|
+
end
|
185
|
+
new_year = year + years_to_increment
|
186
|
+
new_day = get_day_or_max_day_in_month(self.day, new_month, new_year)
|
187
|
+
ZDate.new(new_year.to_s + new_month.to_s2 + new_day.to_s2)
|
188
|
+
end
|
189
|
+
|
190
|
+
def add_years(number)
|
191
|
+
new_year = year + number
|
192
|
+
new_day = get_day_or_max_day_in_month(self.day, self.month, new_year)
|
193
|
+
ZDate.new(new_year.to_s + self.month_str + new_day.to_s2)
|
194
|
+
end
|
195
|
+
|
196
|
+
# DEPRECATED, change_ methods in ZTime modify self, this was confusing,
|
197
|
+
# change_ methods return new ZDate object, they DO NOT modify self
|
198
|
+
# def change_year_to(y)
|
199
|
+
# o = ZDate.new(y.to_s + self.month_str + self.day_str)
|
200
|
+
# o
|
201
|
+
# end
|
202
|
+
# def change_day_to(d)
|
203
|
+
# o = ZDate.new(self.year_str + self.month_str + d.to_s2)
|
204
|
+
# o
|
205
|
+
# end
|
206
|
+
|
207
|
+
# returns new ZDate object, note this is the MONTH NUMBER, not MONTH INDEX from ZDate.months_of_year
|
208
|
+
# returns the first day of the month
|
209
|
+
def jump_to_month(month_number)
|
210
|
+
# find difference in months
|
211
|
+
if month_number >= self.month
|
212
|
+
ZDate.new(year_str + (month_number).to_s2 + "01")
|
213
|
+
else
|
214
|
+
ZDate.new((year + 1).to_s + (month_number).to_s2 + "01")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# beginning and end of month both return new ZDate objects
|
219
|
+
def beginning_of_month
|
220
|
+
ZDate.new(year_str + month_str + "01")
|
221
|
+
end
|
222
|
+
|
223
|
+
def end_of_month
|
224
|
+
ZDate.new(year_str + month_str + self.days_in_month.to_s)
|
225
|
+
end
|
226
|
+
|
227
|
+
def beginning_of_next_month
|
228
|
+
o = self.dup
|
229
|
+
o.increment_month!
|
230
|
+
o
|
231
|
+
end
|
232
|
+
|
233
|
+
# sub_ methods return new ZDate object, they do not modify self.
|
234
|
+
def sub_days(number)
|
235
|
+
o = self.dup
|
236
|
+
while number > 0
|
237
|
+
if (o.day - 1) >= number
|
238
|
+
o.date = o.year_str + o.month_str + (o.day - number).to_s2
|
239
|
+
number = 0
|
240
|
+
else
|
241
|
+
number = number - o.day
|
242
|
+
o.decrement_month!
|
243
|
+
end
|
244
|
+
end
|
245
|
+
o
|
246
|
+
end
|
247
|
+
|
248
|
+
def sub_weeks(number)
|
249
|
+
self.sub_days(7 * number)
|
250
|
+
end
|
251
|
+
|
252
|
+
def sub_months(number)
|
253
|
+
o = self.dup
|
254
|
+
number.times do
|
255
|
+
o.decrement_month!
|
256
|
+
end
|
257
|
+
o
|
258
|
+
end
|
259
|
+
|
260
|
+
# Gets the absolute difference in days between self and date_to_compare, order is not important.
|
261
|
+
def diff_in_days(date_to_compare)
|
262
|
+
# d1 will be the earlier date, d2 the later
|
263
|
+
if date_to_compare > self
|
264
|
+
d1, d2 = self.dup, date_to_compare.dup
|
265
|
+
elsif self > date_to_compare
|
266
|
+
d1, d2 = date_to_compare.dup, self.dup
|
267
|
+
else
|
268
|
+
return 0 # same date
|
269
|
+
end
|
270
|
+
|
271
|
+
total = 0
|
272
|
+
while d1.year != d2.year
|
273
|
+
total += d1.days_left_in_year + 1 # need one extra day to push us to jan 1
|
274
|
+
d1 = ZDate.new((d1.year + 1).to_s + "0101")
|
275
|
+
end
|
276
|
+
total += d2.day_of_year - d1.day_of_year
|
277
|
+
total
|
278
|
+
end
|
279
|
+
|
280
|
+
def diff_in_days_to_this(closest_day_index)
|
281
|
+
if closest_day_index >= self.dayindex
|
282
|
+
closest_day_index - self.dayindex # could be 0
|
283
|
+
else # day_num < self.dayindex
|
284
|
+
7 - (self.dayindex - closest_day_index)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# We need days_in_months and diff_in_months to be available at the class level as well.
|
289
|
+
class << self
|
290
|
+
def new_first_day_in_month(month, year)
|
291
|
+
ZDate.new(year.to_s + month.to_s2 + "01")
|
292
|
+
end
|
293
|
+
|
294
|
+
def new_last_day_in_month(month, year)
|
295
|
+
day = days_in_month(month, year)
|
296
|
+
ZDate.new(year.to_s + month.to_s2 + day.to_s2)
|
297
|
+
end
|
298
|
+
|
299
|
+
def days_in_month(month, year)
|
300
|
+
if year % 400 == 0 || year % 4 == 0 && year % 100 != 0
|
301
|
+
ZDate.days_in_leap_year_months[month - 1]
|
302
|
+
else
|
303
|
+
ZDate.days_in_common_year_months[month - 1]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Gets the difference FROM month1, year1 TO month2, year2
|
308
|
+
# don't use it the other way around, it won't work
|
309
|
+
def diff_in_months(month1, year1, month2, year2)
|
310
|
+
# first get the difference in months
|
311
|
+
if month2 >= month1
|
312
|
+
diff_in_months = month2 - month1
|
313
|
+
else
|
314
|
+
diff_in_months = 12 - (month1 - month2)
|
315
|
+
year2 -= 1 # this makes the next line nice
|
316
|
+
end
|
317
|
+
diff_in_months += (year2 - year1) * 12
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# difference in months FROM self TO date2, for instance, if self is oct 1 and date2 is nov 14, will return 1
|
322
|
+
# if self is nov 14 and date2 is oct 1, will return -1
|
323
|
+
def diff_in_months(date2)
|
324
|
+
if date2 > self
|
325
|
+
ZDate.diff_in_months(self.month, self.year, date2.month, date2.year)
|
326
|
+
else
|
327
|
+
ZDate.diff_in_months(date2.month, date2.year, self.month, self.year) * -1
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def days_in_month
|
332
|
+
if leap_year?
|
333
|
+
ZDate.days_in_leap_year_months[self.month - 1]
|
334
|
+
else
|
335
|
+
ZDate.days_in_common_year_months[self.month - 1]
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def days_left_in_month
|
340
|
+
self.days_in_month - self.day
|
341
|
+
end
|
342
|
+
|
343
|
+
def months_left_in_year
|
344
|
+
12 - self.month
|
345
|
+
end
|
346
|
+
|
347
|
+
def dayname
|
348
|
+
# well this is going to be a hack, I need an algo for finding the day
|
349
|
+
# Ruby's Time.local is the fastest way to create a Ruby Time object
|
350
|
+
t = ::Time.local(self.year, ZDate.months_of_year[self.month - 1], self.day)
|
351
|
+
t.strftime("%a").downcase
|
352
|
+
end
|
353
|
+
|
354
|
+
def dayindex
|
355
|
+
ZDate.days_of_week.index(self.dayname)
|
356
|
+
end
|
357
|
+
|
358
|
+
def full_dayname
|
359
|
+
ZDate.full_days_of_week[self.dayindex]
|
360
|
+
end
|
361
|
+
|
362
|
+
def full_monthname
|
363
|
+
Z.full_months_of_year[self.month - 1]
|
364
|
+
end
|
365
|
+
|
366
|
+
def leap_year?
|
367
|
+
self.year % 400 == 0 || self.year % 4 == 0 && self.year % 100 != 0
|
368
|
+
end
|
369
|
+
|
370
|
+
def day_of_year
|
371
|
+
doy = self.day
|
372
|
+
# iterate through days in months arrays, summing up the days
|
373
|
+
if leap_year?
|
374
|
+
doy = (1...self.month).to_a.inject(doy) {|sum, n| sum += ZDate.days_in_leap_year_months[n-1]}
|
375
|
+
else
|
376
|
+
doy = (1...self.month).to_a.inject(doy) {|sum, n| sum += ZDate.days_in_common_year_months[n-1]}
|
377
|
+
end
|
378
|
+
doy
|
379
|
+
end
|
380
|
+
|
381
|
+
def days_left_in_year
|
382
|
+
leap_year? ? 366 - self.day_of_year : 365 - self.day_of_year
|
383
|
+
end
|
384
|
+
|
385
|
+
def get_date_from_day_and_week_of_month(day_num, week_num)
|
386
|
+
# This method is extremely sloppy, clean it up
|
387
|
+
# Get the index of the first day of this month
|
388
|
+
first_day_of_month = self.beginning_of_month
|
389
|
+
first_day_index = first_day_of_month.dayindex
|
390
|
+
|
391
|
+
diff_in_days_to_first_occ = first_day_of_month.diff_in_days_to_this(day_num)
|
392
|
+
|
393
|
+
# now find the number of days to the correct occurrence; REMEMBER TO CHECK FOR LAST MONTH
|
394
|
+
if week_num == -1
|
395
|
+
total_diff_in_days = diff_in_days_to_first_occ + 21 # 7 * 3 weeks; are already at the first ocurrence, so this is total diff in days to 4th occurrence; may not be the last!!
|
396
|
+
else
|
397
|
+
total_diff_in_days = diff_in_days_to_first_occ + 7 * (week_num - 1)
|
398
|
+
end
|
399
|
+
|
400
|
+
# there is a chance that the last occurrence is not the 4th week of the month; if that is the case, add an extra 7 days
|
401
|
+
if (week_num == -1) && (self.month == self.beginning_of_month.add_days(total_diff_in_days + 7).month)
|
402
|
+
total_diff_in_days += 7
|
403
|
+
end
|
404
|
+
|
405
|
+
# Now we have the number of days FROM THE START OF THE CURRENT MONTH; if we are not past that date, then we have found the first occurrence
|
406
|
+
if (total_diff_in_days + 1) >= self.day
|
407
|
+
# Create occ_date_str and make sure it has two digits
|
408
|
+
occ_date_str = (total_diff_in_days + 1).to_s.gsub(/^(\d)$/,'0\1')
|
409
|
+
return self.beginning_of_month.add_days(total_diff_in_days)
|
410
|
+
else # We have already past the date; calculate the occurrence next month!
|
411
|
+
# Get the index of the first day next month
|
412
|
+
first_day_index = self.add_months(1).beginning_of_month.dayindex
|
413
|
+
|
414
|
+
# Find the number of days away to the day of interest (NOT the week)
|
415
|
+
if day_num > first_day_index
|
416
|
+
diff_in_days_to_first_occ = day_num - first_day_index
|
417
|
+
elsif day_num < first_day_index
|
418
|
+
diff_in_days_to_first_occ = 7 - (first_day_index - day_num)
|
419
|
+
else # first_day_index == day_num
|
420
|
+
diff_in_days_to_first_occ = 0
|
421
|
+
end
|
422
|
+
|
423
|
+
# now find the number of days to the correct occurrence; REMEMBER TO CHECK FOR LAST MONTH
|
424
|
+
if week_num == -1
|
425
|
+
total_diff_in_days = diff_in_days_to_first_occ + 21 # 7 * 3 weeks
|
426
|
+
else
|
427
|
+
total_diff_in_days = diff_in_days_to_first_occ + 7 * (week_num - 1)
|
428
|
+
end
|
429
|
+
|
430
|
+
# there is a chance that the last occurrence is not the 4th week of the month; if that is the case, add an extra 7 days
|
431
|
+
if (week_num == -1) && (self.add_months(1).month == self.add_months(1).beginning_of_month.add_days(total_diff_in_days + 7).month)
|
432
|
+
total_diff_in_days += 7
|
433
|
+
end
|
434
|
+
|
435
|
+
# Now we have the number of days FROM THE START OF THE NEXT MONTH;
|
436
|
+
# Create occ_date_str and make sure it has two digits
|
437
|
+
return self.add_months(1).beginning_of_month.add_days(total_diff_in_days)
|
438
|
+
end # END if (total_diff_in_days + 1) ...
|
439
|
+
end
|
440
|
+
|
441
|
+
# returns a new ZDate object, NOTE! this returns nil if that date does not exist (sept 31st)
|
442
|
+
def get_next_date_from_date_of_month(date_of_month)
|
443
|
+
o = self.dup
|
444
|
+
if day == date_of_month
|
445
|
+
o
|
446
|
+
else
|
447
|
+
if day > date_of_month
|
448
|
+
o.increment_month!
|
449
|
+
end
|
450
|
+
ZDate.new(o.year_str + o.month_str + date_of_month.to_s2) rescue nil
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
protected
|
455
|
+
# Modifies self.
|
456
|
+
# bumps self to first day of next month
|
457
|
+
def increment_month!
|
458
|
+
if month != 12
|
459
|
+
# just bump up a number
|
460
|
+
self.date = year_str + (month + 1).to_s2 + "01"
|
461
|
+
else
|
462
|
+
self.date = (year + 1).to_s + "0101"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
def decrement_month!
|
467
|
+
if month != 1
|
468
|
+
# just bump down a number and set days to the last day in the month
|
469
|
+
self.date = year_str + (month - 1).to_s2 + ZDate.days_in_month(month - 1, year).to_s2
|
470
|
+
else
|
471
|
+
self.date = (year - 1).to_s + "1231" # dec has 31 days
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
private
|
476
|
+
def validate
|
477
|
+
raise "ZDate says: invalid date" if !valid
|
478
|
+
end
|
479
|
+
|
480
|
+
def valid
|
481
|
+
# It is important that valid_day is last because we have to do the days_in_month calculation!!!
|
482
|
+
@date.length == 8 && @date !~ /\D/ && valid_year && valid_month && valid_day
|
483
|
+
end
|
484
|
+
|
485
|
+
def valid_year
|
486
|
+
year >= 1900
|
487
|
+
end
|
488
|
+
|
489
|
+
def valid_month
|
490
|
+
month >= 1 and month <= 12
|
491
|
+
end
|
492
|
+
|
493
|
+
def valid_day
|
494
|
+
day >= 1 and day <= days_in_month
|
495
|
+
end
|
496
|
+
|
497
|
+
def get_day_or_max_day_in_month(day, month, year)
|
498
|
+
# if day exists in month/year then use it, if it is not then use the last day of the month
|
499
|
+
dm = ZDate.days_in_month(month, year)
|
500
|
+
dm >= day ? day : dm
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|