nickel 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,8 @@
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
1
  module Nickel
6
2
  module NLPQueryConstants
7
3
  DATE_DD = %r{\b((?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?)\b}
8
4
  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}
5
+ DATE_DD_NB = %r{\b(?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?\b}
10
6
  DATE_DD_WITH_SUFFIX = %r{\b((?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th))\b}
11
7
  DATE_DD_WITHOUT_SUFFIX = %r{\b(0?[1-9]|[12][0-9]|3[01])\b}
12
8
  DATE_DD_WITH_SUFFIX_NB = %r{\b(?:0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)\b}
@@ -19,7 +15,7 @@ module Nickel
19
15
  MONTH_OF_YEAR_NB = %r{\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b}
20
16
  TIME_24HR = %r{[01]?[0-9]|2[0-3]:[0-5][0-9]}
21
17
  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})}
18
+ TIME = %r{(#{TIME_12HR}|#{TIME_24HR})}
23
19
  YEAR = %r{((?:20)?0[789](?:\s|\n)|(?:20)[1-9][0-9])}
24
20
  WEEK_OF_MONTH = %r{(1st|2nd|3rd|4th|5th)}
25
21
  end
@@ -1,27 +1,19 @@
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
1
  module Nickel
6
2
 
7
- class Occurrence
8
- include InstanceFromHash
3
+ # Some notes about this class, type can take the following values:
4
+ # :single, :daily, :weekly, :daymonthly, :datemonthly,
5
+ Occurrence = Struct.new(:type, :start_date, :end_date, :start_time, :end_time, :interval, :day_of_week, :week_of_month, :date_of_month) do
9
6
 
10
- # Some notes about this class, @type can take the following values:
11
- # :single, :daily, :weekly, :daymonthly, :datemonthly,
12
- attr_accessor :type, :start_date, :end_date, :start_time, :end_time, :interval, :day_of_week, :week_of_month, :date_of_month
13
-
14
7
  def initialize(h)
15
- @start_date = nil # prevents warning in testing; but why is the warning there in the first place? Because I should be using instance_variable_defined in finalize method instead of checking for nil vals
16
- super(h)
8
+ h.each { |k,v| send("#{k}=", v) }
17
9
  end
18
-
10
+
19
11
  def inspect
20
12
  str = %(\#<Occurrence type: #{type})
21
- str << %(, start_date: "#{start_date.date}") if start_date
22
- str << %(, end_date: "#{end_date.date}") if end_date
23
- str << %(, start_time: "#{start_time.time}") if start_time
24
- str << %(, end_time: "#{end_time.time}") if end_time
13
+ str << %(, start_date: "#{start_date}") if start_date
14
+ str << %(, end_date: "#{end_date}") if end_date
15
+ str << %(, start_time: "#{start_time}") if start_time
16
+ str << %(, end_time: "#{end_time}") if end_time
25
17
  str << %(, interval: #{interval}) if interval
26
18
  str << %(, day_of_week: #{day_of_week}) if day_of_week
27
19
  str << %(, week_of_month: #{week_of_month}) if week_of_month
@@ -29,62 +21,51 @@ module Nickel
29
21
  str << ">"
30
22
  str
31
23
  end
32
-
33
-
24
+
34
25
  def finalize(cur_date)
35
- #@end_date = nil if @end_date.nil?
36
- # one of the purposes of this method is to find a start date if it is not already specified
26
+ cur_date = start_date unless start_date.nil?
27
+ case type
28
+ when :daily then finalize_daily(cur_date)
29
+ when :weekly then finalize_weekly(cur_date)
30
+ when :datemonthly then finalize_datemonthly(cur_date)
31
+ when :daymonthly then finalize_daymonthly(cur_date)
32
+ end
33
+ end
34
+
35
+ private
37
36
 
38
- # case type
39
- # when :daily then finalize_daily
40
- # when :weekly then finalize_weekly
41
- # when :daymonthly then finalize_daymonthly
42
- # when :datemonthly then finalize_datemonthly
43
- # end
44
-
45
-
46
- if @type == :daily && @start_date.nil?
47
- @start_date = cur_date
48
- elsif @type == :weekly
49
- if @start_date.nil?
50
- @start_date = cur_date.this(@day_of_week)
51
- else
52
- @start_date = @start_date.this(@day_of_week) # this is needed in case someone said "every monday and wed starting DATE"; we want to find the first occurrence after DATE
53
- end
54
- if instance_variable_defined?("@end_date")
55
- @end_date = @end_date.prev(@day_of_week) # find the real end date, if someone says "every monday until dec 1"; find the actual last occurrence
56
- end
57
- elsif @type == :datemonthly
58
- if @start_date.nil?
59
- if cur_date.day <= @date_of_month
60
- @start_date = cur_date.add_days(@date_of_month - cur_date.day)
61
- else
62
- @start_date = cur_date.add_months(1).beginning_of_month.add_days(@date_of_month - 1)
63
- end
64
- else
65
- if @start_date.day <= @date_of_month
66
- @start_date = @start_date.add_days(@date_of_month - @start_date.day)
67
- else
68
- @start_date = @start_date.add_months(1).beginning_of_month.add_days(@date_of_month - 1)
69
- end
70
- end
71
- elsif @type == :daymonthly
72
- # in this case we also want to change @week_of_month val to -1 if it is currently 5. I used 5 to represent "last" in the previous version of the parser, but a more standard format is to use -1
73
- @week_of_month = -1 if @week_of_month == 5
74
- if @start_date.nil?
75
- @start_date = cur_date.get_date_from_day_and_week_of_month(@day_of_week, @week_of_month)
76
- else
77
- @start_date = @start_date.get_date_from_day_and_week_of_month(@day_of_week, @week_of_month)
78
- end
37
+ def finalize_daily(cur_date)
38
+ self.start_date = cur_date
39
+ end
40
+
41
+ def finalize_weekly(cur_date)
42
+ # this is needed in case someone said "every monday and wed
43
+ # starting DATE"; we want to find the first occurrence after DATE
44
+ self.start_date = cur_date.this(day_of_week)
45
+
46
+ if !end_date.nil?
47
+ # find the real end date, if someone says "every monday until
48
+ # dec 1"; find the actual last occurrence
49
+ self.end_date = end_date.prev(day_of_week)
79
50
  end
80
-
81
51
  end
82
52
 
83
- class << self
84
- def finalizer(occurrences, cur_date)
85
- occurrences.each {|occ| occ.finalize(cur_date)}
86
- occurrences
53
+ def finalize_datemonthly(cur_date)
54
+ if cur_date.day <= date_of_month
55
+ self.start_date = cur_date.add_days(date_of_month - cur_date.day)
56
+ else
57
+ self.start_date = cur_date.add_months(1).beginning_of_month.add_days(date_of_month - 1)
87
58
  end
88
59
  end
60
+
61
+ def finalize_daymonthly(cur_date)
62
+ # in this case we also want to change week_of_month val to -1 if
63
+ # it is currently 5. I used 5 to represent "last" in the
64
+ # previous version of the parser, but a more standard format is
65
+ # to use -1
66
+ self.week_of_month = -1 if week_of_month == 5
67
+
68
+ self.start_date = cur_date.get_date_from_day_and_week_of_month(day_of_week, week_of_month)
69
+ end
89
70
  end
90
71
  end
@@ -0,0 +1,3 @@
1
+ module Nickel
2
+ VERSION = '0.1.0'
3
+ end
data/lib/nickel/zdate.rb CHANGED
@@ -1,18 +1,18 @@
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]
1
+ require 'date'
4
2
 
5
3
  module Nickel
6
4
 
7
5
  # TODO: get methods should accept dayname or dayindex
8
6
  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"]
7
+ include Comparable
8
+
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
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
-
14
+ @days_in_leap_year_months = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
15
+
16
16
  MON = 0
17
17
  TUE = 1
18
18
  WED = 2
@@ -20,9 +20,9 @@ module Nickel
20
20
  FRI = 4
21
21
  SAT = 5
22
22
  SUN = 6
23
-
23
+
24
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
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
26
  end
27
27
 
28
28
  # Don't use attr_accessor for date, year, month, day; we want to validate on change.
@@ -31,11 +31,11 @@ module Nickel
31
31
  d.gsub!(/-/,'') # remove any hyphens, so a user can initialize with something like "2008-10-23"
32
32
  self.date = d
33
33
  end
34
-
34
+
35
35
  def date
36
36
  @date
37
37
  end
38
-
38
+
39
39
  def date=(yyyymmdd)
40
40
  @date = yyyymmdd
41
41
  validate
@@ -44,67 +44,59 @@ module Nickel
44
44
  def year_str
45
45
  @date[0..3]
46
46
  end
47
-
47
+
48
48
  def month_str
49
49
  @date[4..5]
50
50
  end
51
-
51
+
52
52
  def day_str
53
53
  @date[6..7]
54
54
  end
55
-
55
+
56
56
  def year
57
57
  year_str.to_i
58
58
  end
59
-
59
+
60
60
  def month
61
61
  month_str.to_i
62
62
  end
63
-
63
+
64
64
  def day
65
65
  day_str.to_i
66
66
  end
67
-
67
+
68
68
  def readable
69
69
  month_str + '/' + day_str + '/' + year_str
70
70
  end
71
-
71
+
72
72
  def fmt(txt)
73
73
  txt.gsub!(/%Y/, self.year_str)
74
74
  txt.gsub!(/%m/, self.month_str)
75
75
  txt.gsub!(/%d/, self.day_str)
76
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)))
77
+
78
+ def before(d2)
79
+ d2.respond_to?(:year) && (year < d2.year) ||
80
+ d2.respond_to?(:month) && (year == d2.year && (month < d2.month ||
81
+ d2.respond_to?(:day) && (month == d2.month && day < d2.day)))
92
82
  end
93
-
94
- def ==(d2)
95
- self.year == d2.year && self.month == d2.month && self.day == d2.day
83
+
84
+ def after(d2)
85
+ d2.respond_to?(:year) && (year > d2.year) ||
86
+ d2.respond_to?(:month) && (year == d2.year && (month > d2.month ||
87
+ d2.respond_to?(:day) && (month == d2.month && day > d2.day)))
96
88
  end
97
-
89
+
98
90
  def <=>(d2)
99
- if self < d2
91
+ if before(d2)
100
92
  -1
101
- elsif self > d2
93
+ elsif after(d2)
102
94
  1
103
95
  else
104
96
  0
105
97
  end
106
98
  end
107
-
99
+
108
100
  # returns true if self is today
109
101
  def is_today?
110
102
  self == ZDate.new
@@ -113,7 +105,7 @@ module Nickel
113
105
  # for example, "1st friday", uses self as the reference month
114
106
  def ordinal_dayindex(num, day_index)
115
107
  # 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)
108
+ first_occ_date = ZDate.new(ZDate.format_date(year_str, month_str)).this(day_index)
117
109
  # if num is 1 through 4, we can just add (num-1) weeks
118
110
  if num <= 4
119
111
  d = first_occ_date.add_weeks(num - 1)
@@ -130,7 +122,7 @@ module Nickel
130
122
  def this(day)
131
123
  x_weeks_from_day(0, day)
132
124
  end
133
-
125
+
134
126
  # for example, "next friday"
135
127
  def next(day)
136
128
  x_weeks_from_day(1, day)
@@ -140,7 +132,7 @@ module Nickel
140
132
  def prev(day)
141
133
  (self.dayindex == day) ? self.dup : x_weeks_from_day(-1,day)
142
134
  end
143
-
135
+
144
136
  # returns a new date object
145
137
  def x_weeks_from_day(weeks_away, day2index)
146
138
  day1index = self.dayindex
@@ -153,7 +145,7 @@ module Nickel
153
145
  end
154
146
  self.add_days(days_away) # returns a new date object
155
147
  end
156
-
148
+
157
149
  # add_ methods return new ZDate object, they DO NOT modify self
158
150
  def add_days(number)
159
151
  if number < 0 then return sub_days(number.abs) end
@@ -161,7 +153,7 @@ module Nickel
161
153
  # Let's see what month we are going to end in
162
154
  while number > 0
163
155
  if o.days_left_in_month >= number
164
- o.date = o.year_str + o.month_str + (o.day + number).to_s2
156
+ o.date = ZDate.format_date(o.year_str, o.month_str, o.day + number)
165
157
  number = 0
166
158
  else
167
159
  number = number - 1 - o.days_left_in_month #it costs 1 day to increment the month
@@ -170,11 +162,11 @@ module Nickel
170
162
  end
171
163
  o
172
164
  end
173
-
165
+
174
166
  def add_weeks(number)
175
167
  self.add_days(7*number)
176
168
  end
177
-
169
+
178
170
  def add_months(number)
179
171
  new_month = 1 + ((month - 1 + number) % 12)
180
172
  if number > months_left_in_year # are we going to change year?
@@ -184,23 +176,23 @@ module Nickel
184
176
  end
185
177
  new_year = year + years_to_increment
186
178
  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)
179
+ ZDate.new(ZDate.format_date(new_year, new_month, new_day))
188
180
  end
189
-
181
+
190
182
  def add_years(number)
191
183
  new_year = year + number
192
184
  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)
185
+ ZDate.new(ZDate.format_date(new_year, self.month_str, new_day))
194
186
  end
195
187
 
196
188
  # DEPRECATED, change_ methods in ZTime modify self, this was confusing,
197
189
  # change_ methods return new ZDate object, they DO NOT modify self
198
190
  # def change_year_to(y)
199
- # o = ZDate.new(y.to_s + self.month_str + self.day_str)
191
+ # o = ZDate.new(ZDate.format_date(y, self.month_str, self.day_str))
200
192
  # o
201
193
  # end
202
194
  # def change_day_to(d)
203
- # o = ZDate.new(self.year_str + self.month_str + d.to_s2)
195
+ # o = ZDate.new(ZDate.format_date(self.year_str, self.month_str, d))
204
196
  # o
205
197
  # end
206
198
 
@@ -209,21 +201,21 @@ module Nickel
209
201
  def jump_to_month(month_number)
210
202
  # find difference in months
211
203
  if month_number >= self.month
212
- ZDate.new(year_str + (month_number).to_s2 + "01")
204
+ ZDate.new(ZDate.format_date(year_str, month_number))
213
205
  else
214
- ZDate.new((year + 1).to_s + (month_number).to_s2 + "01")
206
+ ZDate.new(ZDate.format_date(year + 1, month_number))
215
207
  end
216
208
  end
217
-
209
+
218
210
  # beginning and end of month both return new ZDate objects
219
211
  def beginning_of_month
220
- ZDate.new(year_str + month_str + "01")
212
+ ZDate.new(ZDate.format_date(year_str, month_str))
221
213
  end
222
-
214
+
223
215
  def end_of_month
224
- ZDate.new(year_str + month_str + self.days_in_month.to_s)
216
+ ZDate.new(ZDate.format_date(year_str, month_str, self.days_in_month))
225
217
  end
226
-
218
+
227
219
  def beginning_of_next_month
228
220
  o = self.dup
229
221
  o.increment_month!
@@ -235,7 +227,7 @@ module Nickel
235
227
  o = self.dup
236
228
  while number > 0
237
229
  if (o.day - 1) >= number
238
- o.date = o.year_str + o.month_str + (o.day - number).to_s2
230
+ o.date = ZDate.format_date(o.year_str, o.month_str, o.day - number)
239
231
  number = 0
240
232
  else
241
233
  number = number - o.day
@@ -244,19 +236,19 @@ module Nickel
244
236
  end
245
237
  o
246
238
  end
247
-
239
+
248
240
  def sub_weeks(number)
249
241
  self.sub_days(7 * number)
250
242
  end
251
-
243
+
252
244
  def sub_months(number)
253
245
  o = self.dup
254
- number.times do
246
+ number.times do
255
247
  o.decrement_month!
256
248
  end
257
249
  o
258
250
  end
259
-
251
+
260
252
  # Gets the absolute difference in days between self and date_to_compare, order is not important.
261
253
  def diff_in_days(date_to_compare)
262
254
  # d1 will be the earlier date, d2 the later
@@ -271,29 +263,29 @@ module Nickel
271
263
  total = 0
272
264
  while d1.year != d2.year
273
265
  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")
266
+ d1 = ZDate.new(ZDate.format_date(d1.year + 1))
275
267
  end
276
268
  total += d2.day_of_year - d1.day_of_year
277
269
  total
278
270
  end
279
-
271
+
280
272
  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
273
+ if closest_day_index >= self.dayindex
274
+ closest_day_index - self.dayindex # could be 0
275
+ else # day_num < self.dayindex
276
+ 7 - (self.dayindex - closest_day_index)
277
+ end
286
278
  end
287
-
279
+
288
280
  # We need days_in_months and diff_in_months to be available at the class level as well.
289
281
  class << self
290
282
  def new_first_day_in_month(month, year)
291
- ZDate.new(year.to_s + month.to_s2 + "01")
283
+ ZDate.new(ZDate.format_date(year, month))
292
284
  end
293
-
285
+
294
286
  def new_last_day_in_month(month, year)
295
287
  day = days_in_month(month, year)
296
- ZDate.new(year.to_s + month.to_s2 + day.to_s2)
288
+ ZDate.new(ZDate.format_date(year, month, day))
297
289
  end
298
290
 
299
291
  def days_in_month(month, year)
@@ -305,19 +297,105 @@ module Nickel
305
297
  end
306
298
 
307
299
  # 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
-
300
+ # don't use it the other way around, it won't work
301
+ def diff_in_months(month1, year1, month2, year2)
302
+ # first get the difference in months
303
+ if month2 >= month1
304
+ diff_in_months = month2 - month1
305
+ else
306
+ diff_in_months = 12 - (month1 - month2)
307
+ year2 -= 1 # this makes the next line nice
308
+ end
309
+ diff_in_months += (year2 - year1) * 12
310
+ end
311
+
312
+ def format_year(y)
313
+ # if there were only two digits, prepend 20 (e.g. "08" should be "2008")
314
+ y.to_s.rjust(4, '20')
315
+ end
316
+
317
+ def format_month(m)
318
+ m.to_s.rjust(2, '0')
319
+ end
320
+
321
+ def format_day(d)
322
+ d.to_s.rjust(2, '0')
323
+ end
324
+
325
+ # formats the year, month, day into the format expected by the ZDate constructor
326
+ def format_date(year, month=1, day=1)
327
+ format_year(year) + format_month(month) + format_day(day)
328
+ end
329
+
330
+ # Interpret Date is equally as important, our goals:
331
+ # First off, convention of the NLP is to not allow month names to the construct finder (unless it is implying date span), so we will not be interpreting
332
+ # anything such as january 2nd, 2008. Instead all dates will be represented in this form month/day/year. However it may not
333
+ # be as nice as that. We need to match things like '5', if someone just typed in "the 5th." Because of this, there will be
334
+ # overlap between interpret_date and interpret_time in matching; interpret_date should ALWAYS be found after interpret_time in
335
+ # the construct finder. If the construct finder happens upon a digit on it's own, e.g. "5", it will not run interpret_time
336
+ # because there is no "at" preceeding it. Therefore it will fall through to the finder with interpret_date and we will assume
337
+ # the user meant the 5th. If interpret_date is before interpret_time, then .... wait... does the order actually matter? Even if
338
+ # this is before interpret_time, it shouldn't get hit because the time should be picked up at the "at" construct. This may be a bunch
339
+ # of useless rambling.
340
+ #
341
+ # 2/08 <------ This is not A date
342
+ # 2/2008 <------ Neither is this, but I can see people using these as wrappers, must support this in next version
343
+ # 11/08 <------ same
344
+ # 11/2008 <------ same
345
+ # 2/1/08, 2/12/08, 2/1/2008, 2/12/2008
346
+ # 11/1/08, 11/12/08, 11/1/2008, 11/12/2008
347
+ # 2/1 feb first
348
+ # 2/12 feb twelfth
349
+ # 11/1 nov first
350
+ # 11/12 nov twelfth
351
+ # 11 the 11th
352
+ # 2 the 2nd
353
+ #
354
+ #
355
+ # Match all of the following:
356
+ # a.) 1 10
357
+ # b.) 1/1 1/12 10/1 10/12
358
+ # c.) 1/1/08 1/12/08 1/1/2008 1/12/2008 10/1/08 10/12/08 10/12/2008 10/12/2008
359
+ # d.) 1st 10th
360
+ def interpret(str, current_date)
361
+ day_str, month_str, year_str = nil, nil, nil
362
+ ambiguous = {:month => false, :year => false} # assume false, we use this flag if we aren't certain about the year
363
+
364
+ #appropriate matches
365
+ a_d = /^(\d{1,2})(rd|st|nd|th)?$/ # handles cases a and d
366
+ b = /^(\d{1,2})\/(\d{1,2})$/ # handles case b
367
+ c = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/ # handles case c
368
+
369
+ if mdata = str.match(a_d)
370
+ ambiguous[:month] = true
371
+ day_str = mdata[1]
372
+ elsif mdata = str.match(b)
373
+ ambiguous[:year] = true
374
+ month_str = mdata[1]
375
+ day_str = mdata[2]
376
+ elsif mdata = str.match(c)
377
+ month_str = mdata[1]
378
+ day_str = mdata[2]
379
+ year_str = mdata[3]
380
+ else
381
+ return nil
382
+ end
383
+
384
+ inst_str = ZDate.format_date(year_str || current_date.year_str, month_str || current_date.month_str, day_str || current_date.day_str)
385
+ # in this case we do not care if date fails validation, if it does, it just means we haven't found a valid date, return nil
386
+ date = ZDate.new(inst_str) rescue nil
387
+ if date
388
+ if ambiguous[:year]
389
+ # say the date is 11/1 and someone enters 2/1, they probably mean next year, I pick 4 months as a threshold but that is totally arbitrary
390
+ current_date.diff_in_months(date) < -4 and date = date.add_years(1)
391
+ elsif ambiguous[:month]
392
+ current_date.day > date.day and date = date.add_months(1)
393
+ end
394
+ end
395
+ date
396
+ end
397
+ end
398
+
321
399
  # difference in months FROM self TO date2, for instance, if self is oct 1 and date2 is nov 14, will return 1
322
400
  # if self is nov 14 and date2 is oct 1, will return -1
323
401
  def diff_in_months(date2)
@@ -327,7 +405,7 @@ module Nickel
327
405
  ZDate.diff_in_months(date2.month, date2.year, self.month, self.year) * -1
328
406
  end
329
407
  end
330
-
408
+
331
409
  def days_in_month
332
410
  if leap_year?
333
411
  ZDate.days_in_leap_year_months[self.month - 1]
@@ -335,30 +413,30 @@ module Nickel
335
413
  ZDate.days_in_common_year_months[self.month - 1]
336
414
  end
337
415
  end
338
-
416
+
339
417
  def days_left_in_month
340
418
  self.days_in_month - self.day
341
419
  end
342
-
420
+
343
421
  def months_left_in_year
344
422
  12 - self.month
345
423
  end
346
-
424
+
347
425
  def dayname
348
426
  # well this is going to be a hack, I need an algo for finding the day
349
427
  # Ruby's Time.local is the fastest way to create a Ruby Time object
350
428
  t = ::Time.local(self.year, ZDate.months_of_year[self.month - 1], self.day)
351
429
  t.strftime("%a").downcase
352
430
  end
353
-
431
+
354
432
  def dayindex
355
433
  ZDate.days_of_week.index(self.dayname)
356
434
  end
357
-
435
+
358
436
  def full_dayname
359
437
  ZDate.full_days_of_week[self.dayindex]
360
438
  end
361
-
439
+
362
440
  def full_monthname
363
441
  Z.full_months_of_year[self.month - 1]
364
442
  end
@@ -366,7 +444,7 @@ module Nickel
366
444
  def leap_year?
367
445
  self.year % 400 == 0 || self.year % 4 == 0 && self.year % 100 != 0
368
446
  end
369
-
447
+
370
448
  def day_of_year
371
449
  doy = self.day
372
450
  # iterate through days in months arrays, summing up the days
@@ -377,67 +455,63 @@ module Nickel
377
455
  end
378
456
  doy
379
457
  end
380
-
458
+
381
459
  def days_left_in_year
382
460
  leap_year? ? 366 - self.day_of_year : 365 - self.day_of_year
383
461
  end
384
-
462
+
385
463
  def get_date_from_day_and_week_of_month(day_num, week_num)
386
464
  # 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
465
+ # Get the index of the first day of this month
466
+ first_day_of_month = self.beginning_of_month
467
+ first_day_index = first_day_of_month.dayindex
390
468
 
391
469
  diff_in_days_to_first_occ = first_day_of_month.diff_in_days_to_this(day_num)
392
470
 
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
-
471
+ # now find the number of days to the correct occurrence; REMEMBER TO CHECK FOR LAST MONTH
472
+ if week_num == -1
473
+ 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!!
474
+ else
475
+ total_diff_in_days = diff_in_days_to_first_occ + 7 * (week_num - 1)
476
+ end
477
+
478
+ # 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
479
+ if (week_num == -1) && (self.month == self.beginning_of_month.add_days(total_diff_in_days + 7).month)
480
+ total_diff_in_days += 7
481
+ end
482
+
483
+ # 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
484
+ if (total_diff_in_days + 1) >= self.day
485
+ return self.beginning_of_month.add_days(total_diff_in_days)
486
+ else # We have already past the date; calculate the occurrence next month!
487
+ # Get the index of the first day next month
488
+ first_day_index = self.add_months(1).beginning_of_month.dayindex
489
+
490
+ # Find the number of days away to the day of interest (NOT the week)
491
+ if day_num > first_day_index
492
+ diff_in_days_to_first_occ = day_num - first_day_index
493
+ elsif day_num < first_day_index
494
+ diff_in_days_to_first_occ = 7 - (first_day_index - day_num)
495
+ else # first_day_index == day_num
496
+ diff_in_days_to_first_occ = 0
497
+ end
498
+
499
+ # now find the number of days to the correct occurrence; REMEMBER TO CHECK FOR LAST MONTH
500
+ if week_num == -1
501
+ total_diff_in_days = diff_in_days_to_first_occ + 21 # 7 * 3 weeks
502
+ else
503
+ total_diff_in_days = diff_in_days_to_first_occ + 7 * (week_num - 1)
504
+ end
505
+
506
+ # 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
507
+ if (week_num == -1) && (self.add_months(1).month == self.add_months(1).beginning_of_month.add_days(total_diff_in_days + 7).month)
508
+ total_diff_in_days += 7
509
+ end
510
+
511
+ return self.add_months(1).beginning_of_month.add_days(total_diff_in_days)
512
+ end # END if (total_diff_in_days + 1) ...
513
+ end
514
+
441
515
  # returns a new ZDate object, NOTE! this returns nil if that date does not exist (sept 31st)
442
516
  def get_next_date_from_date_of_month(date_of_month)
443
517
  o = self.dup
@@ -447,53 +521,61 @@ module Nickel
447
521
  if day > date_of_month
448
522
  o.increment_month!
449
523
  end
450
- ZDate.new(o.year_str + o.month_str + date_of_month.to_s2) rescue nil
524
+ ZDate.new(ZDate.format_date(o.year_str, o.month_str, date_of_month)) rescue nil
451
525
  end
452
526
  end
453
527
 
528
+ def to_date
529
+ Date.new(year, month, day)
530
+ end
531
+
532
+ def to_s
533
+ date
534
+ end
535
+
454
536
  protected
455
537
  # Modifies self.
456
538
  # bumps self to first day of next month
457
539
  def increment_month!
458
540
  if month != 12
459
541
  # just bump up a number
460
- self.date = year_str + (month + 1).to_s2 + "01"
542
+ self.date = ZDate.format_date(year_str, month + 1)
461
543
  else
462
- self.date = (year + 1).to_s + "0101"
544
+ self.date = ZDate.format_date(year + 1)
463
545
  end
464
546
  end
465
547
 
466
548
  def decrement_month!
467
549
  if month != 1
468
550
  # 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
551
+ self.date = ZDate.format_date(year_str, month - 1, ZDate.days_in_month(month - 1, year))
470
552
  else
471
- self.date = (year - 1).to_s + "1231" # dec has 31 days
553
+ self.date = ZDate.format_date(year - 1, 12, 31) # dec has 31 days
472
554
  end
473
555
  end
474
-
556
+
475
557
  private
476
558
  def validate
477
559
  raise "ZDate says: invalid date" if !valid
478
560
  end
479
-
561
+
480
562
  def valid
481
563
  # It is important that valid_day is last because we have to do the days_in_month calculation!!!
482
564
  @date.length == 8 && @date !~ /\D/ && valid_year && valid_month && valid_day
483
565
  end
484
-
566
+
485
567
  def valid_year
486
568
  year >= 1900
487
569
  end
488
-
570
+
489
571
  def valid_month
490
572
  month >= 1 and month <= 12
491
573
  end
492
-
574
+
493
575
  def valid_day
494
576
  day >= 1 and day <= days_in_month
495
577
  end
496
-
578
+
497
579
  def get_day_or_max_day_in_month(day, month, year)
498
580
  # if day exists in month/year then use it, if it is not then use the last day of the month
499
581
  dm = ZDate.days_in_month(month, year)