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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/License.txt +3 -1
- data/README.md +110 -0
- data/Rakefile +10 -18
- data/bin/run_specs.sh +14 -0
- data/lib/nickel.rb +4 -23
- data/lib/nickel/construct.rb +25 -28
- data/lib/nickel/construct_finder.rb +251 -244
- data/lib/nickel/construct_interpreter.rb +68 -69
- data/lib/nickel/nlp.rb +67 -31
- data/lib/nickel/{query.rb → nlp_query.rb} +160 -348
- data/lib/nickel/{query_constants.rb → nlp_query_constants.rb} +2 -6
- data/lib/nickel/occurrence.rb +48 -67
- data/lib/nickel/version.rb +3 -0
- data/lib/nickel/zdate.rb +244 -162
- data/lib/nickel/ztime.rb +152 -72
- data/nickel.gemspec +31 -38
- data/spec/lib/nickel/nlp_spec.rb +14 -0
- data/spec/lib/nickel/occurrence_spec.rb +40 -0
- data/spec/lib/nickel/zdate_spec.rb +94 -0
- data/spec/lib/nickel/ztime_spec.rb +331 -0
- data/spec/lib/nickel_spec.rb +1859 -0
- data/spec/spec_helper.rb +22 -0
- metadata +124 -35
- data/History.txt +0 -11
- data/README.rdoc +0 -69
- data/lib/nickel/instance_from_hash.rb +0 -13
- data/lib/nickel/ruby_ext/calling_method.rb +0 -10
- data/lib/nickel/ruby_ext/to_s2.rb +0 -12
- data/spec/nickel_spec.rb +0 -165
- data/test/compare.rb +0 -109
- data/test/nlp_test.rb +0 -813
- data/test/nlp_tests_helper.rb +0 -55
- data/test/zdate_test.rb +0 -44
- data/test/ztime_test.rb +0 -429
@@ -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
|
data/lib/nickel/occurrence.rb
CHANGED
@@ -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
|
8
|
-
|
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
|
-
|
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
|
22
|
-
str << %(, end_date: "#{end_date
|
23
|
-
str << %(, start_time: "#{start_time
|
24
|
-
str << %(, end_time: "#{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
|
-
|
36
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
data/lib/nickel/zdate.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
|
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
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
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
|
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
|
79
|
-
|
80
|
-
|
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
|
95
|
-
|
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
|
91
|
+
if before(d2)
|
100
92
|
-1
|
101
|
-
elsif
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
204
|
+
ZDate.new(ZDate.format_date(year_str, month_number))
|
213
205
|
else
|
214
|
-
ZDate.new((year + 1
|
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
|
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
|
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
|
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)
|
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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
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
|
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
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
388
|
-
|
389
|
-
|
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
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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
|
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
|
542
|
+
self.date = ZDate.format_date(year_str, month + 1)
|
461
543
|
else
|
462
|
-
self.date = (year + 1)
|
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
|
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
|
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)
|