nickel 0.0.6 → 0.1.0

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.
@@ -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)