nickel 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|