timeframe 0.0.11 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -5
- data/LICENSE +20 -20
- data/README.rdoc +2 -0
- data/Rakefile +9 -14
- data/lib/timeframe.rb +163 -173
- data/lib/timeframe/core_ext/array.rb +9 -0
- data/lib/timeframe/version.rb +1 -1
- data/spec/spec_helper.rb +4 -7
- data/spec/timeframe_spec.rb +116 -101
- data/timeframe.gemspec +5 -3
- metadata +114 -55
- data/spec/spec.opts +0 -1
data/.document
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
README.rdoc
|
2
|
-
lib/**/*.rb
|
3
|
-
bin/*
|
4
|
-
features/**/*.feature
|
5
|
-
LICENSE
|
1
|
+
README.rdoc
|
2
|
+
lib/**/*.rb
|
3
|
+
bin/*
|
4
|
+
features/**/*.feature
|
5
|
+
LICENSE
|
data/LICENSE
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
-
Copyright (c) 2009 Andy Rossmeissl
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
-
a copy of this software and associated documentation files (the
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
the following conditions:
|
10
|
-
|
11
|
-
The above copyright notice and this permission notice shall be
|
12
|
-
included in all copies or substantial portions of the Software.
|
13
|
-
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
|
+
Copyright (c) 2009 Andy Rossmeissl
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
CHANGED
@@ -10,6 +10,8 @@ http://rdoc.info/projects/rossmeissl/timeframe
|
|
10
10
|
|
11
11
|
The good parts of Timeframe all came from the gentlemen at Fingertips[http://fngtps.com].
|
12
12
|
|
13
|
+
Thanks to @artemk for https://github.com/rossmeissl/timeframe/pull/5
|
14
|
+
|
13
15
|
== Copyright
|
14
16
|
|
15
17
|
Copyright (c) 2010 Andy Rossmeissl.
|
data/Rakefile
CHANGED
@@ -6,26 +6,21 @@ end
|
|
6
6
|
require 'bundler'
|
7
7
|
Bundler::GemHelper.install_tasks
|
8
8
|
|
9
|
-
require '
|
10
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
11
|
-
spec.libs << 'lib' << 'spec'
|
12
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
13
|
-
end
|
9
|
+
require 'rake/testtask'
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
Rake::TestTask.new do |t|
|
12
|
+
t.libs.push 'lib'
|
13
|
+
t.test_files = FileList['spec/**/*spec.rb']
|
14
|
+
t.verbose = true
|
19
15
|
end
|
20
16
|
|
21
|
-
task :default => :spec
|
22
17
|
|
23
|
-
|
24
|
-
Rake::RDocTask.new do |rdoc|
|
25
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
18
|
+
task :default => :test
|
26
19
|
|
20
|
+
require 'rdoc/task'
|
21
|
+
RDoc::Task.new do |rdoc|
|
27
22
|
rdoc.rdoc_dir = 'rdoc'
|
28
|
-
rdoc.title = "timeframe
|
23
|
+
rdoc.title = "timeframe"
|
29
24
|
rdoc.rdoc_files.include('README*')
|
30
25
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
26
|
end
|
data/lib/timeframe.rb
CHANGED
@@ -1,16 +1,7 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'multi_json'
|
2
3
|
require 'active_support/version'
|
3
|
-
|
4
|
-
active_support/core_ext/hash
|
5
|
-
active_support/core_ext/array/extract_options
|
6
|
-
active_support/core_ext/string/conversions
|
7
|
-
active_support/core_ext/date/conversions
|
8
|
-
active_support/core_ext/integer/time
|
9
|
-
active_support/core_ext/numeric/time
|
10
|
-
active_support/json
|
11
|
-
}.each do |active_support_3_requirement|
|
12
|
-
require active_support_3_requirement
|
13
|
-
end if ActiveSupport::VERSION::MAJOR == 3
|
4
|
+
require 'active_support/core_ext' if ActiveSupport::VERSION::MAJOR >= 3
|
14
5
|
|
15
6
|
# Encapsulates a timeframe between two dates. The dates provided to the class are always until the last date. That means
|
16
7
|
# that the last date is excluded.
|
@@ -20,7 +11,90 @@ end if ActiveSupport::VERSION::MAJOR == 3
|
|
20
11
|
# # and holds 31 days
|
21
12
|
# Timeframe.new(Date(2007,10,1), Date(2007,11,1)).days #=> 31
|
22
13
|
class Timeframe
|
23
|
-
|
14
|
+
class << self
|
15
|
+
# Shortcut method to return the Timeframe representing the current year (as defined by Time.now)
|
16
|
+
def this_year
|
17
|
+
new :year => Time.now.year
|
18
|
+
end
|
19
|
+
|
20
|
+
# Construct a new Timeframe, but constrain it by another
|
21
|
+
def constrained_new(start_date, end_date, constraint)
|
22
|
+
start_date, end_date = make_dates start_date, end_date
|
23
|
+
raise ArgumentError, 'Constraint must be a Timeframe' unless constraint.is_a? Timeframe
|
24
|
+
raise ArgumentError, "Start date #{start_date} should be earlier than end date #{end_date}" if start_date > end_date
|
25
|
+
if end_date <= constraint.start_date or start_date >= constraint.end_date
|
26
|
+
new constraint.start_date, constraint.start_date
|
27
|
+
elsif start_date.year == end_date.yesterday.year
|
28
|
+
new(start_date, end_date) & constraint
|
29
|
+
elsif start_date.year < constraint.start_date.year and constraint.start_date.year < end_date.yesterday.year
|
30
|
+
constraint
|
31
|
+
else
|
32
|
+
new [constraint.start_date, start_date].max, [constraint.end_date, end_date].min
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a timeframe +/- number of years around today
|
37
|
+
def mid(number)
|
38
|
+
start_date = Time.now.today - number.years
|
39
|
+
end_date = Time.now.today + number.years
|
40
|
+
new start_date, end_date
|
41
|
+
end
|
42
|
+
|
43
|
+
# Construct a new Timeframe by parsing an ISO 8601 time interval string
|
44
|
+
# http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
|
45
|
+
def from_iso8601(str)
|
46
|
+
raise ArgumentError, 'Intervals should be specified according to ISO 8601, method 1, eliding times' unless str =~ /^\d\d\d\d-\d\d-\d\d\/\d\d\d\d-\d\d-\d\d$/
|
47
|
+
new *str.split('/')
|
48
|
+
end
|
49
|
+
|
50
|
+
# Construct a new Timeframe from a hash with keys startDate and endDate
|
51
|
+
def from_hash(hsh)
|
52
|
+
hsh = hsh.symbolize_keys
|
53
|
+
new hsh[:startDate], hsh[:endDate]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Construct a new Timeframe from a year.
|
57
|
+
def from_year(year)
|
58
|
+
new :year => year.to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
# Automagically parse a Timeframe from either a String or a Hash
|
62
|
+
def parse(input)
|
63
|
+
case input
|
64
|
+
when ::Integer
|
65
|
+
from_year input
|
66
|
+
when ::Hash
|
67
|
+
from_hash input
|
68
|
+
when ::String
|
69
|
+
str = input.strip
|
70
|
+
if str.start_with?('{')
|
71
|
+
from_hash ::MultiJson.decode(str)
|
72
|
+
elsif input =~ /\A\d\d\d\d\z/
|
73
|
+
from_year input
|
74
|
+
else
|
75
|
+
from_iso8601 str
|
76
|
+
end
|
77
|
+
else
|
78
|
+
raise ::ArgumentError, "Must be String or Hash"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias :interval :parse
|
82
|
+
alias :from_json :parse
|
83
|
+
|
84
|
+
# Deprecated
|
85
|
+
def multiyear(*args) # :nodoc:
|
86
|
+
new *args
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def make_dates(start_date, end_date)
|
92
|
+
[start_date.to_date, end_date.to_date]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
attr_reader :start_date
|
97
|
+
attr_reader :end_date
|
24
98
|
|
25
99
|
# Creates a new instance of Timeframe. You can either pass a start and end Date or a Hash with named arguments,
|
26
100
|
# with the following options:
|
@@ -30,9 +104,6 @@ class Timeframe
|
|
30
104
|
# <tt>:year</tt>: Start date becomes the first day of this year, and the end date becomes the first day of the
|
31
105
|
# next year.
|
32
106
|
#
|
33
|
-
# By default, Timeframe.new will die if the resulting Timeframe would cross year boundaries. This can be overridden
|
34
|
-
# by setting the <tt>:skip_year_boundary_crossing_check</tt> option.
|
35
|
-
#
|
36
107
|
# Examples:
|
37
108
|
#
|
38
109
|
# Timeframe.new Date.new(2007, 2, 1), Date.new(2007, 4, 1) # February and March
|
@@ -45,263 +116,182 @@ class Timeframe
|
|
45
116
|
if month = options[:month]
|
46
117
|
month = Date.parse(month).month if month.is_a? String
|
47
118
|
year = options[:year] || Date.today.year
|
48
|
-
|
49
|
-
|
119
|
+
start_date = Date.new(year, month, 1)
|
120
|
+
end_date = start_date.next_month
|
50
121
|
elsif year = options[:year]
|
51
|
-
|
52
|
-
|
122
|
+
start_date = Date.new(year, 1, 1)
|
123
|
+
end_date = Date.new(year+1, 1, 1)
|
53
124
|
end
|
54
125
|
|
55
|
-
|
56
|
-
|
126
|
+
start_date = args.shift.to_date if start_date.nil? and args.any?
|
127
|
+
end_date = args.shift.to_date if end_date.nil? and args.any?
|
57
128
|
|
58
|
-
raise ArgumentError, "Please supply a start and end date, `#{args.map(&:inspect).to_sentence}' is not enough" if
|
59
|
-
raise ArgumentError, "Start date #{
|
60
|
-
raise ArgumentError, 'Timeframes that cross year boundaries are dangerous' unless options[:skip_year_boundary_crossing_check] or from.year == to.yesterday.year or from == to
|
129
|
+
raise ArgumentError, "Please supply a start and end date, `#{args.map(&:inspect).to_sentence}' is not enough" if start_date.nil? or end_date.nil?
|
130
|
+
raise ArgumentError, "Start date #{start_date} should be earlier than end date #{end_date}" if start_date > end_date
|
61
131
|
|
62
|
-
@
|
132
|
+
@start_date, @end_date = start_date, end_date
|
63
133
|
end
|
64
|
-
|
134
|
+
|
65
135
|
def inspect # :nodoc:
|
66
|
-
"<Timeframe(#{object_id}) #{days} days starting #{
|
136
|
+
"<Timeframe(#{object_id}) #{days} days starting #{start_date} ending #{end_date}>"
|
67
137
|
end
|
68
|
-
|
138
|
+
|
69
139
|
# The number of days in the timeframe
|
70
140
|
#
|
71
141
|
# Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days #=> 30
|
72
142
|
# Timeframe.new(:month => 1).days #=> 31
|
73
143
|
# Timeframe.new(:year => 2004).days #=> 366
|
74
144
|
def days
|
75
|
-
(
|
145
|
+
(end_date - start_date).to_i
|
76
146
|
end
|
77
|
-
|
147
|
+
|
78
148
|
# Returns true when a Date or other Timeframe is included in this Timeframe
|
79
149
|
def include?(obj)
|
80
|
-
# puts "checking to see if #{date} is between #{
|
150
|
+
# puts "checking to see if #{date} is between #{start_date} and #{end_date}" if Emitter::DEBUG
|
81
151
|
case obj
|
82
152
|
when Date
|
83
|
-
(
|
153
|
+
(start_date...end_date).include?(obj)
|
84
154
|
when Time
|
85
|
-
# (
|
155
|
+
# (start_date...end_date).include?(obj.to_date)
|
86
156
|
raise "this wasn't previously supported, but it could be"
|
87
157
|
when Timeframe
|
88
|
-
|
158
|
+
start_date <= obj.start_date and end_date >= obj.end_date
|
89
159
|
end
|
90
160
|
end
|
91
|
-
|
161
|
+
|
92
162
|
# Returns true when the parameter Timeframe is properly included in the Timeframe
|
93
163
|
def proper_include?(other_timeframe)
|
94
164
|
raise ArgumentError, 'Proper inclusion only makes sense when testing other Timeframes' unless other_timeframe.is_a? Timeframe
|
95
|
-
(
|
165
|
+
(start_date < other_timeframe.start_date) and (end_date > other_timeframe.end_date)
|
96
166
|
end
|
97
|
-
|
167
|
+
|
98
168
|
# Returns true when this timeframe is equal to the other timeframe
|
99
169
|
def ==(other)
|
100
170
|
# puts "checking to see if #{self} is equal to #{other}" if Emitter::DEBUG
|
101
171
|
return false unless other.is_a?(Timeframe)
|
102
|
-
|
172
|
+
start_date == other.start_date and end_date == other.end_date
|
103
173
|
end
|
104
174
|
alias :eql? :==
|
105
|
-
|
175
|
+
|
106
176
|
# Calculates a hash value for the Timeframe, used for equality checking and Hash lookups.
|
107
|
-
#--
|
108
|
-
# This needs to be an integer or else it won't use #eql?
|
109
177
|
def hash
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
# Returns an array of month-long subtimeframes
|
114
|
-
#--
|
115
|
-
# TODO: rename to month_subtimeframes
|
116
|
-
def months
|
117
|
-
raise ArgumentError, "Please only provide whole-month timeframes to Timeframe#months" unless from.day == 1 and to.day == 1
|
118
|
-
raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#months' unless from.year == to.yesterday.year
|
119
|
-
year = from.year # therefore this only works in the from year
|
120
|
-
(from.month..to.yesterday.month).map { |m| Timeframe.new :month => m, :year => year }
|
178
|
+
start_date.hash + end_date.hash
|
121
179
|
end
|
122
|
-
|
180
|
+
|
123
181
|
# Returns the relevant year as a Timeframe
|
124
182
|
def year
|
125
|
-
raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#year' unless
|
126
|
-
Timeframe.new :year =>
|
127
|
-
end
|
128
|
-
|
129
|
-
# Divides a Timeframe into component parts, each no more than a month long.
|
130
|
-
#--
|
131
|
-
# multiyear safe
|
132
|
-
def month_subtimeframes
|
133
|
-
(from.year..to.yesterday.year).map do |year|
|
134
|
-
(1..12).map do |month|
|
135
|
-
Timeframe.new(:year => year, :month => month) & self
|
136
|
-
end
|
137
|
-
end.flatten.compact
|
138
|
-
end
|
139
|
-
|
140
|
-
# Like #month_subtimeframes, but will discard partial months
|
141
|
-
# multiyear safe
|
142
|
-
def full_month_subtimeframes
|
143
|
-
month_subtimeframes.map { |st| Timeframe.new(:year => st.from.year, :month => st.from.month) }
|
144
|
-
end
|
145
|
-
|
146
|
-
# Divides a Timeframe into component parts, each no more than a year long.
|
147
|
-
#--
|
148
|
-
# multiyear safe
|
149
|
-
def year_subtimeframes
|
150
|
-
(from.year..to.yesterday.year).map do |year|
|
151
|
-
Timeframe.new(:year => year) & self
|
152
|
-
end
|
183
|
+
raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#year' unless start_date.year == end_date.yesterday.year
|
184
|
+
Timeframe.new :year => start_date.year
|
153
185
|
end
|
154
|
-
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
186
|
+
|
187
|
+
# Returns an Array of month-long Timeframes. Partial months are **not** included by default.
|
188
|
+
# http://stackoverflow.com/questions/1724639/iterate-every-month-with-date-objects
|
189
|
+
def months
|
190
|
+
memo = []
|
191
|
+
ptr = start_date
|
192
|
+
while ptr <= end_date do
|
193
|
+
memo.push(Timeframe.new(:year => ptr.year, :month => ptr.month) & self)
|
194
|
+
ptr = ptr >> 1
|
161
195
|
end
|
196
|
+
memo.flatten.compact
|
162
197
|
end
|
163
|
-
|
198
|
+
|
164
199
|
# Crop a Timeframe to end no later than the provided date.
|
165
|
-
#--
|
166
|
-
# multiyear safe
|
167
200
|
def ending_no_later_than(date)
|
168
|
-
if
|
201
|
+
if end_date < date
|
169
202
|
self
|
170
|
-
elsif
|
203
|
+
elsif start_date >= date
|
171
204
|
nil
|
172
205
|
else
|
173
|
-
Timeframe.
|
206
|
+
Timeframe.new start_date, date
|
174
207
|
end
|
175
208
|
end
|
176
|
-
|
209
|
+
|
177
210
|
# Returns a timeframe representing the intersection of the given timeframes
|
178
211
|
def &(other_timeframe)
|
179
212
|
this_timeframe = self
|
180
213
|
if other_timeframe == this_timeframe
|
181
214
|
this_timeframe
|
182
|
-
elsif this_timeframe.
|
215
|
+
elsif this_timeframe.start_date > other_timeframe.start_date and this_timeframe.end_date < other_timeframe.end_date
|
183
216
|
this_timeframe
|
184
|
-
elsif other_timeframe.
|
217
|
+
elsif other_timeframe.start_date > this_timeframe.start_date and other_timeframe.end_date < this_timeframe.end_date
|
185
218
|
other_timeframe
|
186
|
-
elsif this_timeframe.
|
219
|
+
elsif this_timeframe.start_date >= other_timeframe.end_date or this_timeframe.end_date <= other_timeframe.start_date
|
187
220
|
nil
|
188
221
|
else
|
189
|
-
Timeframe.new [this_timeframe.
|
222
|
+
Timeframe.new [this_timeframe.start_date, other_timeframe.start_date].max, [this_timeframe.end_date, other_timeframe.end_date].min
|
190
223
|
end
|
191
224
|
end
|
192
|
-
|
225
|
+
|
193
226
|
# Returns the fraction (as a Float) of another Timeframe that this Timeframe represents
|
194
227
|
def /(other_timeframe)
|
195
228
|
raise ArgumentError, 'You can only divide a Timeframe by another Timeframe' unless other_timeframe.is_a? Timeframe
|
196
229
|
self.days.to_f / other_timeframe.days.to_f
|
197
230
|
end
|
198
|
-
|
231
|
+
|
199
232
|
# Crop a Timeframe by another Timeframe
|
200
233
|
def crop(container)
|
201
234
|
raise ArgumentError, 'You can only crop a timeframe by another timeframe' unless container.is_a? Timeframe
|
202
|
-
self.class.new [
|
235
|
+
self.class.new [start_date, container.start_date].max, [end_date, container.end_date].min
|
203
236
|
end
|
204
|
-
|
237
|
+
|
205
238
|
# Returns an array of Timeframes representing the gaps left in the Timeframe after removing all given Timeframes
|
206
239
|
def gaps_left_by(*timeframes)
|
207
240
|
# remove extraneous timeframes
|
208
|
-
timeframes.reject! { |t| t.
|
209
|
-
timeframes.reject! { |t| t.
|
241
|
+
timeframes.reject! { |t| t.end_date <= start_date }
|
242
|
+
timeframes.reject! { |t| t.start_date >= end_date }
|
210
243
|
|
211
244
|
# crop timeframes
|
212
245
|
timeframes.map! { |t| t.crop self }
|
213
|
-
|
246
|
+
|
214
247
|
# remove proper subtimeframes
|
215
248
|
timeframes.reject! { |t| timeframes.detect { |u| u.proper_include? t } }
|
216
|
-
|
249
|
+
|
217
250
|
# escape
|
218
251
|
return [self] if timeframes.empty?
|
219
252
|
|
220
|
-
timeframes.sort! { |x, y| x.
|
253
|
+
timeframes.sort! { |x, y| x.start_date <=> y.start_date }
|
221
254
|
|
222
|
-
a = [
|
223
|
-
b = timeframes.collect(&:
|
255
|
+
a = [ start_date ] + timeframes.collect(&:end_date)
|
256
|
+
b = timeframes.collect(&:start_date) + [ end_date ]
|
224
257
|
|
225
258
|
a.zip(b).map do |gap|
|
226
|
-
Timeframe.new(*gap) if gap[1] > gap[0]
|
259
|
+
Timeframe.new(*gap, :skip_year_boundary_crossing_check => true) if gap[1] > gap[0]
|
227
260
|
end.compact
|
228
261
|
end
|
229
|
-
|
262
|
+
|
230
263
|
# Returns true if the union of the given Timeframes includes the Timeframe
|
231
264
|
def covered_by?(*timeframes)
|
232
265
|
gaps_left_by(*timeframes).empty?
|
233
266
|
end
|
234
|
-
|
267
|
+
|
235
268
|
# Returns the same Timeframe, only a year earlier
|
236
269
|
def last_year
|
237
|
-
self.class.new((
|
238
|
-
end
|
239
|
-
|
240
|
-
def as_json(*)
|
241
|
-
to_param
|
270
|
+
self.class.new((start_date - 1.year), (end_date - 1.year))
|
242
271
|
end
|
243
272
|
|
244
|
-
# overriding this so that as_json is not used
|
245
273
|
def to_json(*)
|
246
|
-
|
274
|
+
%({"startDate":"#{start_date.iso8601}","endDate":"#{end_date.iso8601}"})
|
247
275
|
end
|
248
276
|
|
277
|
+
def as_json(*)
|
278
|
+
{ :startDate => start_date.iso8601, :endDate => end_date.iso8601 }
|
279
|
+
end
|
280
|
+
|
249
281
|
# An ISO 8601 "time interval" like YYYY-MM-DD/YYYY-MM-DD
|
250
|
-
def
|
251
|
-
"#{
|
282
|
+
def iso8601
|
283
|
+
"#{start_date.iso8601}/#{end_date.iso8601}"
|
252
284
|
end
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
285
|
+
alias :to_s :iso8601
|
286
|
+
alias :to_param :iso8601
|
287
|
+
|
288
|
+
# Deprecated
|
289
|
+
def from # :nodoc:
|
290
|
+
@start_date
|
257
291
|
end
|
258
292
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
end
|
263
|
-
|
264
|
-
# Shortcut method to return the Timeframe representing the current year (as defined by Time.now)
|
265
|
-
def this_year
|
266
|
-
new :year => Time.now.year
|
267
|
-
end
|
268
|
-
|
269
|
-
# Construct a new Timeframe, but constrain it by another
|
270
|
-
def constrained_new(from, to, constraint)
|
271
|
-
from, to = make_dates from, to
|
272
|
-
raise ArgumentError, 'Constraint must be a Timeframe' unless constraint.is_a? Timeframe
|
273
|
-
raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
|
274
|
-
if to <= constraint.from or from >= constraint.to
|
275
|
-
new constraint.from, constraint.from
|
276
|
-
elsif from.year == to.yesterday.year
|
277
|
-
new(from, to) & constraint
|
278
|
-
elsif from.year < constraint.from.year and constraint.from.year < to.yesterday.year
|
279
|
-
constraint
|
280
|
-
else
|
281
|
-
new [constraint.from, from].max, [constraint.to, to].min
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
# Shortcut for #new that automatically skips year boundary crossing checks
|
286
|
-
def multiyear(from, to)
|
287
|
-
from, to = make_dates from, to
|
288
|
-
new from, to, :skip_year_boundary_crossing_check => true
|
289
|
-
end
|
290
|
-
|
291
|
-
# Create a multiyear timeframe +/- number of years around today
|
292
|
-
def mid(number)
|
293
|
-
from = Time.zone.today - number.years
|
294
|
-
to = Time.zone.today + number.years
|
295
|
-
multiyear from, to
|
296
|
-
end
|
297
|
-
|
298
|
-
# Construct a new Timeframe by parsing an ISO 8601 time interval string
|
299
|
-
# http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
|
300
|
-
def interval(str)
|
301
|
-
raise ArgumentError, 'Intervals should be specified as a string' unless str.is_a? String
|
302
|
-
raise ArgumentError, 'Intervals should be specified according to ISO 8601, method 1, eliding times' unless str =~ /^\d\d\d\d-\d\d-\d\d\/\d\d\d\d-\d\d-\d\d$/
|
303
|
-
multiyear *str.split('/')
|
304
|
-
end
|
305
|
-
alias :from_json :interval
|
293
|
+
# Deprecated
|
294
|
+
def to # :nodoc:
|
295
|
+
@end_date
|
306
296
|
end
|
307
297
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Array
|
2
|
+
# Constructs an array of timeframes representing the "gaps" left by the given array of timeframes.
|
3
|
+
#
|
4
|
+
# To use this feature, you must explicitly require 'timeframe/core_ext/array'
|
5
|
+
def multiple_timeframes_gaps_left_by(*time_frames)
|
6
|
+
raise ArgumentError.new 'You can only use timeframe for this operation' unless [self + time_frames].flatten.all?{|el| el.is_a?(Timeframe)}
|
7
|
+
self.inject([]){|a,b| a << b.gaps_left_by(*time_frames)}.flatten
|
8
|
+
end
|
9
|
+
end
|
data/lib/timeframe/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler'
|
3
3
|
Bundler.setup
|
4
|
-
require 'spec'
|
5
|
-
require '
|
4
|
+
require 'minitest/spec'
|
5
|
+
require 'minitest/autorun'
|
6
6
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
7
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
-
require 'timeframe'
|
9
|
-
|
10
|
-
Spec::Runner.configure do |config|
|
11
|
-
|
12
|
-
end
|
8
|
+
require File.expand_path('../../lib/timeframe.rb', __FILE__)
|
9
|
+
require File.expand_path('../../lib/timeframe/core_ext/array.rb', __FILE__)
|
data/spec/timeframe_spec.rb
CHANGED
@@ -1,58 +1,52 @@
|
|
1
|
-
require File.expand_path(
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
|
3
3
|
describe Timeframe do
|
4
4
|
describe 'initialization' do
|
5
5
|
it 'should create a timeframe using date strings' do
|
6
6
|
tf = Timeframe.new('2008-02-14', '2008-05-10')
|
7
|
-
tf.from.
|
8
|
-
tf.to.
|
7
|
+
tf.from.must_equal Date.parse('2008-02-14')
|
8
|
+
tf.to.must_equal Date.parse('2008-05-10')
|
9
9
|
end
|
10
10
|
it 'should create a timeframe using date objects' do
|
11
11
|
start = Date.parse('2008-02-14')
|
12
12
|
finish = Date.parse('2008-05-10')
|
13
13
|
tf = Timeframe.new(start, finish)
|
14
|
-
tf.from.
|
15
|
-
tf.to.
|
14
|
+
tf.from.must_equal start
|
15
|
+
tf.to.must_equal finish
|
16
16
|
end
|
17
17
|
it "should accept months" do
|
18
18
|
timeframe = Timeframe.new(:month => 1)
|
19
|
-
timeframe.from.
|
20
|
-
timeframe.to.
|
19
|
+
timeframe.from.must_equal Date.today.change(:month => 1, :day => 1)
|
20
|
+
timeframe.to.must_equal Date.today.change(:month => 2, :day => 1)
|
21
21
|
end
|
22
22
|
it "should accept month names" do
|
23
23
|
timeframe = Timeframe.new(:month => 'february')
|
24
|
-
timeframe.from.
|
25
|
-
timeframe.to.
|
24
|
+
timeframe.from.must_equal Date.today.change(:month => 2, :day => 1)
|
25
|
+
timeframe.to.must_equal Date.today.change(:month => 3, :day => 1)
|
26
26
|
end
|
27
27
|
it "should accept years" do
|
28
28
|
timeframe = Timeframe.new(:year => 2004)
|
29
|
-
timeframe.from.
|
30
|
-
timeframe.to.
|
29
|
+
timeframe.from.must_equal Date.new(2004, 1, 1)
|
30
|
+
timeframe.to.must_equal Date.new(2005, 1, 1)
|
31
31
|
end
|
32
32
|
it "should accept years and months" do
|
33
33
|
timeframe = Timeframe.new(:year => 2005, :month => 5)
|
34
|
-
timeframe.from.
|
35
|
-
timeframe.to.
|
34
|
+
timeframe.from.must_equal Date.new(2005, 5, 1)
|
35
|
+
timeframe.to.must_equal Date.new(2005, 6, 1)
|
36
36
|
end
|
37
37
|
it "should not accept just one date argument" do
|
38
38
|
lambda {
|
39
39
|
Timeframe.new Date.new(2007, 2, 1)
|
40
|
-
}.
|
40
|
+
}.must_raise(ArgumentError, /supply/)
|
41
41
|
end
|
42
42
|
it "should not accept end date that is earlier than start date" do
|
43
43
|
lambda {
|
44
44
|
timeframe = Timeframe.new Date.new(2008, 1, 1), Date.new(2007, 1, 1)
|
45
|
-
}.
|
45
|
+
}.must_raise(ArgumentError, /earlier/)
|
46
46
|
end
|
47
|
-
it "should
|
48
|
-
|
49
|
-
|
50
|
-
}.should raise_error(ArgumentError, /cross/)
|
51
|
-
end
|
52
|
-
it "should optionally accept timeframes that cross year boundaries" do
|
53
|
-
lambda {
|
54
|
-
timeframe = Timeframe.new Date.new(2007, 1, 1), Date.new(2008, 1, 2), :skip_year_boundary_crossing_check => true
|
55
|
-
}.should_not raise_error
|
47
|
+
it "should always accept timeframes that cross year boundaries" do
|
48
|
+
timeframe = Timeframe.new Date.new(2007, 1, 1), Date.new(2008, 1, 2)
|
49
|
+
timeframe.start_date.must_equal Date.new(2007, 1, 1)
|
56
50
|
end
|
57
51
|
end
|
58
52
|
|
@@ -61,7 +55,7 @@ describe Timeframe do
|
|
61
55
|
start = Date.parse('2008-02-14')
|
62
56
|
finish = Date.parse('2008-05-10')
|
63
57
|
tf = Timeframe.new(start, finish)
|
64
|
-
tf.inspect.
|
58
|
+
tf.inspect.must_match %r{<Timeframe\(-?\d+\) 86 days starting 2008-02-14 ending 2008-05-10>}
|
65
59
|
end
|
66
60
|
end
|
67
61
|
|
@@ -76,60 +70,60 @@ describe Timeframe do
|
|
76
70
|
constraint = Timeframe.new :year => 2008
|
77
71
|
may = Timeframe.new Date.new(2008,5,1), Date.new(2008,6,1)
|
78
72
|
january = Timeframe.new Date.new(2008,1,1), Date.new(2008,2,1)
|
79
|
-
Timeframe.constrained_new(may.from, may.to, constraint).
|
80
|
-
Timeframe.constrained_new(Date.new(2007,1,1), Date.new(2010,1,1), constraint).
|
81
|
-
Timeframe.constrained_new(Date.new(2007,11,1), Date.new(2008,2,1), constraint).
|
73
|
+
Timeframe.constrained_new(may.from, may.to, constraint).must_equal may
|
74
|
+
Timeframe.constrained_new(Date.new(2007,1,1), Date.new(2010,1,1), constraint).must_equal constraint
|
75
|
+
Timeframe.constrained_new(Date.new(2007,11,1), Date.new(2008,2,1), constraint).must_equal january
|
82
76
|
end
|
83
77
|
it 'should return a timeframe spanning start and end date if within constraint' do
|
84
78
|
tf = Timeframe.constrained_new(start, finish, constraint)
|
85
|
-
tf.from.
|
86
|
-
tf.to.
|
79
|
+
tf.from.must_equal start
|
80
|
+
tf.to.must_equal finish
|
87
81
|
end
|
88
82
|
it 'should return a timeframe spanning constraint start and end date if outside constraint' do
|
89
83
|
constraint = Timeframe.new(start, finish)
|
90
84
|
tf = Timeframe.constrained_new(constraint_start, constraint_finish, constraint)
|
91
|
-
tf.from.
|
92
|
-
tf.to.
|
85
|
+
tf.from.must_equal start
|
86
|
+
tf.to.must_equal finish
|
93
87
|
end
|
94
88
|
it 'should return a timeframe starting at constraint start' do
|
95
89
|
start = Date.parse('2008-01-01')
|
96
90
|
constraint_start = Date.parse('2008-01-14')
|
97
91
|
constraint = Timeframe.new(constraint_start, constraint_finish)
|
98
92
|
tf = Timeframe.constrained_new(start, finish, constraint)
|
99
|
-
tf.from.
|
100
|
-
tf.to.
|
93
|
+
tf.from.must_equal constraint_start
|
94
|
+
tf.to.must_equal finish
|
101
95
|
end
|
102
96
|
it 'should return a timeframe ending at constraint end' do
|
103
97
|
constraint_finish = Date.parse('2008-04-14')
|
104
98
|
constraint = Timeframe.new(constraint_start, constraint_finish)
|
105
99
|
tf = Timeframe.constrained_new(start, finish, constraint)
|
106
|
-
tf.from.
|
107
|
-
tf.to.
|
100
|
+
tf.from.must_equal start
|
101
|
+
tf.to.must_equal constraint_finish
|
108
102
|
end
|
109
103
|
it "should return a 0-length timeframe when constraining a timeframe by a disjoint timeframe" do
|
110
104
|
constraint = Timeframe.new :year => 2010
|
111
105
|
timeframe = Timeframe.constrained_new(
|
112
106
|
Date.new(2009,1,1), Date.new(2010,1,1), constraint)
|
113
|
-
timeframe.days.
|
107
|
+
timeframe.days.must_equal 0
|
114
108
|
end
|
115
109
|
end
|
116
110
|
|
117
111
|
describe '.this_year' do
|
118
112
|
it "should return the current year" do
|
119
|
-
Timeframe.this_year.
|
113
|
+
Timeframe.this_year.must_equal Timeframe.new(:year => Time.now.year)
|
120
114
|
end
|
121
115
|
end
|
122
116
|
|
123
117
|
describe '#days' do
|
124
118
|
it "should return them number of days included" do
|
125
119
|
#TODO: make these separate "it" blocks, per best practices
|
126
|
-
Timeframe.new(Date.new(2007, 1, 1), Date.new(2008, 1, 1)).days.
|
127
|
-
Timeframe.new(Date.new(2008, 1, 1), Date.new(2009, 1, 1)).days.
|
128
|
-
Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days.
|
129
|
-
Timeframe.new(Date.new(2007, 11, 1), Date.new(2008, 1, 1)).days.
|
130
|
-
Timeframe.new(Date.new(2007, 2, 1), Date.new(2007, 3, 1)).days.
|
131
|
-
Timeframe.new(Date.new(2008, 2, 1), Date.new(2008, 3, 1)).days.
|
132
|
-
Timeframe.new(Date.new(2008, 2, 1), Date.new(2008, 2, 1)).days.
|
120
|
+
Timeframe.new(Date.new(2007, 1, 1), Date.new(2008, 1, 1)).days.must_equal 365
|
121
|
+
Timeframe.new(Date.new(2008, 1, 1), Date.new(2009, 1, 1)).days.must_equal 366 #leap year
|
122
|
+
Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days.must_equal 30
|
123
|
+
Timeframe.new(Date.new(2007, 11, 1), Date.new(2008, 1, 1)).days.must_equal 61
|
124
|
+
Timeframe.new(Date.new(2007, 2, 1), Date.new(2007, 3, 1)).days.must_equal 28
|
125
|
+
Timeframe.new(Date.new(2008, 2, 1), Date.new(2008, 3, 1)).days.must_equal 29
|
126
|
+
Timeframe.new(Date.new(2008, 2, 1), Date.new(2008, 2, 1)).days.must_equal 0
|
133
127
|
end
|
134
128
|
end
|
135
129
|
|
@@ -142,69 +136,61 @@ describe Timeframe do
|
|
142
136
|
Date.new(2008, 2, 5),
|
143
137
|
Date.new(2008, 2, 29)
|
144
138
|
].each do |date|
|
145
|
-
timeframe.include?(date).
|
139
|
+
timeframe.include?(date).must_equal true
|
146
140
|
end
|
147
141
|
[
|
148
142
|
Date.new(2008, 1, 1),
|
149
143
|
Date.new(2007, 2, 1),
|
150
144
|
Date.new(2008, 3, 1)
|
151
145
|
].each do |date|
|
152
|
-
timeframe.include?(date).
|
146
|
+
timeframe.include?(date).must_equal false
|
153
147
|
end
|
154
148
|
end
|
155
149
|
end
|
156
150
|
|
157
151
|
describe '#==' do
|
158
152
|
it "should be able to know if it's equal to another Timeframe object" do
|
159
|
-
Timeframe.new(:year => 2007).
|
160
|
-
Timeframe.new(:year => 2004, :month => 1).
|
153
|
+
Timeframe.new(:year => 2007).must_equal Timeframe.new(:year => 2007)
|
154
|
+
Timeframe.new(:year => 2004, :month => 1).must_equal Timeframe.new(:year => 2004, :month => 1)
|
161
155
|
end
|
162
|
-
|
156
|
+
|
163
157
|
it "should hash equal hash values when the timeframe is equal" do
|
164
|
-
Timeframe.new(:year => 2007).hash.
|
165
|
-
Timeframe.new(:year => 2004, :month => 1).hash.
|
158
|
+
Timeframe.new(:year => 2007).hash.must_equal Timeframe.new(:year => 2007).hash
|
159
|
+
Timeframe.new(:year => 2004, :month => 1).hash.must_equal Timeframe.new(:year => 2004, :month => 1).hash
|
166
160
|
end
|
167
161
|
end
|
168
162
|
|
169
163
|
describe '#months' do
|
170
164
|
it "should return an array of month-long subtimeframes" do
|
171
|
-
Timeframe.new(:year => 2009).months.length.
|
172
|
-
end
|
173
|
-
it "should not return an array of month-long subtimeframes if provided an inappropriate range" do
|
174
|
-
lambda {
|
175
|
-
Timeframe.new(Date.new(2009, 3, 2), Date.new(2009, 3, 5)).months
|
176
|
-
}.should raise_error(ArgumentError, /whole/)
|
177
|
-
lambda {
|
178
|
-
Timeframe.new(Date.new(2009, 1, 1), Date.new(2012, 1, 1), :skip_year_boundary_crossing_check => true).months
|
179
|
-
}.should raise_error(ArgumentError, /dangerous during/)
|
165
|
+
Timeframe.new(:year => 2009).months.length.must_equal 12
|
180
166
|
end
|
181
167
|
end
|
182
168
|
|
183
169
|
describe '#year' do
|
184
170
|
it "should return the relevant year of a timeframe" do
|
185
|
-
Timeframe.new(Date.new(2009, 2, 1), Date.new(2009, 4, 1)).year.
|
171
|
+
Timeframe.new(Date.new(2009, 2, 1), Date.new(2009, 4, 1)).year.must_equal Timeframe.new(:year => 2009)
|
186
172
|
end
|
187
173
|
it "should not return the relevant year of a timeframe if provided an inappropriate range" do
|
188
174
|
lambda {
|
189
|
-
Timeframe.new(Date.new(2009, 1, 1), Date.new(2012, 1, 1)
|
190
|
-
}.
|
175
|
+
Timeframe.new(Date.new(2009, 1, 1), Date.new(2012, 1, 1)).year
|
176
|
+
}.must_raise(ArgumentError, /dangerous during/)
|
191
177
|
end
|
192
178
|
end
|
193
179
|
|
194
180
|
describe '#&' do
|
195
181
|
it "should return its intersection with another timeframe" do
|
196
182
|
#TODO: make these separate "it" blocks, per best practices
|
197
|
-
(Timeframe.new(:month => 4) & Timeframe.new(:month => 6)).
|
198
|
-
(Timeframe.new(:month => 4) & Timeframe.new(:month => 5)).
|
199
|
-
(Timeframe.new(:month => 4) & Timeframe.new(:month => 4)).
|
200
|
-
(Timeframe.new(:year => Time.now.year) & Timeframe.new(:month => 4)).
|
201
|
-
(Timeframe.new(Date.new(2009, 2, 1), Date.new(2009, 6, 1)) & Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 8, 1))).
|
183
|
+
(Timeframe.new(:month => 4) & Timeframe.new(:month => 6)).must_be_nil
|
184
|
+
(Timeframe.new(:month => 4) & Timeframe.new(:month => 5)).must_be_nil
|
185
|
+
(Timeframe.new(:month => 4) & Timeframe.new(:month => 4)).must_equal Timeframe.new(:month => 4)
|
186
|
+
(Timeframe.new(:year => Time.now.year) & Timeframe.new(:month => 4)).must_equal Timeframe.new(:month => 4)
|
187
|
+
(Timeframe.new(Date.new(2009, 2, 1), Date.new(2009, 6, 1)) & Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 8, 1))).must_equal Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 6, 1))
|
202
188
|
end
|
203
189
|
end
|
204
190
|
|
205
191
|
describe '#/' do
|
206
192
|
it "should return a fraction of another timeframe" do
|
207
|
-
(Timeframe.new(:month => 4, :year => 2009) / Timeframe.new(:year => 2009)).
|
193
|
+
(Timeframe.new(:month => 4, :year => 2009) / Timeframe.new(:year => 2009)).must_equal (30.0 / 365.0)
|
208
194
|
end
|
209
195
|
end
|
210
196
|
|
@@ -215,67 +201,96 @@ describe Timeframe do
|
|
215
201
|
Timeframe.new(:year => 2009, :month => 5),
|
216
202
|
Timeframe.new(Date.new(2009, 8, 1), Date.new(2009, 11, 1)),
|
217
203
|
Timeframe.new(Date.new(2009, 9, 1), Date.new(2009, 10, 1))
|
218
|
-
).
|
204
|
+
).must_equal(
|
219
205
|
[ Timeframe.new(Date.new(2009, 1, 1), Date.new(2009, 3, 1)),
|
220
206
|
Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 5, 1)),
|
221
207
|
Timeframe.new(Date.new(2009, 6, 1), Date.new(2009, 8, 1)),
|
222
|
-
Timeframe.new(Date.new(2009, 11, 1), Date.new(2010, 1, 1)) ]
|
208
|
+
Timeframe.new(Date.new(2009, 11, 1), Date.new(2010, 1, 1)) ])
|
223
209
|
end
|
224
210
|
end
|
225
211
|
|
226
212
|
describe '#covered_by?' do
|
227
213
|
it "should be able to ascertain gaps left by a list of other Timeframes" do
|
228
214
|
Timeframe.new(:year => 2009).covered_by?(
|
229
|
-
Timeframe.new(:month => 1, :year => 2009),
|
215
|
+
Timeframe.new(:month => 1, :year => 2009),
|
230
216
|
Timeframe.new(Date.new(2009, 2, 1), Date.new(2010, 1, 1))
|
231
|
-
).
|
217
|
+
).must_equal(true)
|
232
218
|
end
|
233
219
|
end
|
234
220
|
|
235
221
|
describe '#last_year' do
|
236
222
|
it "should return its predecessor in a previous year" do
|
237
|
-
Timeframe.this_year.last_year.
|
238
|
-
Timeframe.new(Date.new(Date.today.year - 1, 1, 1), Date.new(Date.today.year, 1, 1))
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
describe 'Timeframe:class#interval' do
|
243
|
-
it 'should parse ISO 8601 interval format' do
|
244
|
-
Timeframe.interval('2009-01-01/2010-01-01').should == Timeframe.new(:year => 2009)
|
245
|
-
end
|
246
|
-
it 'should skip year boundary checking' do
|
247
|
-
lambda {
|
248
|
-
Timeframe.interval '2009-01-01/2011-01-01'
|
249
|
-
}.should_not raise_error
|
250
|
-
end
|
251
|
-
it 'should understand its own #to_param' do
|
252
|
-
t = Timeframe.new(:year => 2009)
|
253
|
-
Timeframe.interval(t.to_param).should == t
|
223
|
+
Timeframe.this_year.last_year.must_equal Timeframe.new(Date.new(Date.today.year - 1, 1, 1), Date.new(Date.today.year, 1, 1))
|
254
224
|
end
|
255
225
|
end
|
256
226
|
|
257
|
-
describe '
|
258
|
-
it '
|
259
|
-
|
260
|
-
|
227
|
+
describe '.parse' do
|
228
|
+
it 'understands ISO8601' do
|
229
|
+
Timeframe.parse('2009-01-01/2010-01-01').must_equal Timeframe.new(:year => 2009)
|
230
|
+
end
|
231
|
+
it 'understands plain year' do
|
232
|
+
plain_year = 2009
|
233
|
+
Timeframe.parse(plain_year).must_equal Timeframe.new(:year => plain_year)
|
234
|
+
Timeframe.parse(plain_year.to_s).must_equal Timeframe.new(:year => plain_year)
|
235
|
+
end
|
236
|
+
it 'understands JSON' do
|
237
|
+
json =<<-EOS
|
238
|
+
{"startDate":"2009-05-01", "endDate":"2009-06-01"}
|
239
|
+
EOS
|
240
|
+
Timeframe.parse(json).must_equal Timeframe.new(:year => 2009, :month => 5)
|
241
|
+
end
|
242
|
+
it 'understands a Ruby hash' do
|
243
|
+
hsh = { :startDate => '2009-05-01', :endDate => '2009-06-01' }
|
244
|
+
Timeframe.parse(hsh).must_equal Timeframe.new(:year => 2009, :month => 5)
|
245
|
+
Timeframe.parse(hsh.stringify_keys).must_equal Timeframe.new(:year => 2009, :month => 5)
|
261
246
|
end
|
262
247
|
end
|
263
|
-
|
248
|
+
|
264
249
|
describe '#to_json' do
|
265
250
|
it 'should generate JSON (test fails on ruby 1.8)' do
|
266
|
-
Timeframe.new(:year => 2009).to_json.
|
251
|
+
Timeframe.new(:year => 2009).to_json.must_equal %({"startDate":"2009-01-01","endDate":"2010-01-01"})
|
252
|
+
end
|
253
|
+
it 'understands its own #to_json' do
|
254
|
+
t = Timeframe.new(:year => 2009, :month => 5)
|
255
|
+
Timeframe.from_json(t.to_json).must_equal t
|
267
256
|
end
|
268
257
|
end
|
269
|
-
|
258
|
+
|
270
259
|
describe '#to_param' do
|
271
260
|
it 'should generate a URL-friendly parameter' do
|
272
|
-
Timeframe.new(:year => 2009).to_param.
|
261
|
+
Timeframe.new(:year => 2009).to_param.must_equal "2009-01-01/2010-01-01"
|
262
|
+
end
|
263
|
+
it 'understands its own #to_param' do
|
264
|
+
t = Timeframe.new(:year => 2009, :month => 5)
|
265
|
+
Timeframe.parse(t.to_param).must_equal t
|
273
266
|
end
|
274
267
|
end
|
275
|
-
|
268
|
+
|
276
269
|
describe '#to_s' do
|
277
270
|
it 'should not only look at month numbers when describing multi-year timeframes' do
|
278
|
-
Timeframe.
|
271
|
+
Timeframe.new(Date.parse('2008-01-01'), Date.parse('2010-01-01')).to_s.must_equal "2008-01-01/2010-01-01"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "Array#multiple_timeframes_gaps_left_by" do
|
276
|
+
it "should raise error if not a Timeframes are going to be merged" do
|
277
|
+
lambda {
|
278
|
+
[Timeframe.new(Date.parse('2011-10-10'), Date.parse('2011-10-28'))].multiple_timeframes_gaps_left_by(1,2)
|
279
|
+
}.must_raise(ArgumentError, /only use timeframe/)
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should properly work with multiple timeframes" do
|
283
|
+
t1 = Timeframe.new(Date.parse('2011-10-10'), Date.parse('2011-10-28'))
|
284
|
+
t2 = Timeframe.new(Date.parse('2011-11-01'), Date.parse('2011-11-12'))
|
285
|
+
|
286
|
+
t3 = Timeframe.new(Date.parse('2011-10-11'), Date.parse('2011-10-15'))
|
287
|
+
t4 = Timeframe.new(Date.parse('2011-11-01'), Date.parse('2011-11-08'))
|
288
|
+
|
289
|
+
[t1, t2].multiple_timeframes_gaps_left_by(t3, t4).must_equal(
|
290
|
+
[ Timeframe.new(Date.parse('2011-10-10'), Date.parse('2011-10-11')),
|
291
|
+
Timeframe.new(Date.parse('2011-10-15'), Date.parse('2011-10-28')),
|
292
|
+
Timeframe.new(Date.parse('2011-11-08'), Date.parse('2011-11-12'))
|
293
|
+
])
|
279
294
|
end
|
280
295
|
end
|
281
296
|
end
|
data/timeframe.gemspec
CHANGED
@@ -19,8 +19,10 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_development_dependency "
|
22
|
+
s.add_development_dependency "minitest"
|
23
23
|
s.add_development_dependency 'home_run'
|
24
|
-
s.
|
25
|
-
s.
|
24
|
+
s.add_development_dependency 'rake'
|
25
|
+
s.add_runtime_dependency 'activesupport', '>=2.3.5'
|
26
|
+
s.add_runtime_dependency 'i18n'
|
27
|
+
s.add_runtime_dependency 'multi_json'
|
26
28
|
end
|
metadata
CHANGED
@@ -1,69 +1,120 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: timeframe
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Andy Rossmeissl
|
9
14
|
- Seamus Abshere
|
10
15
|
- Derek Kastner
|
11
16
|
autorequire:
|
12
17
|
bindir: bin
|
13
18
|
cert_chain: []
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
|
20
|
+
date: 2012-01-06 00:00:00 Z
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: minitest
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
26
|
none: false
|
20
|
-
requirements:
|
21
|
-
- -
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
24
34
|
type: :development
|
25
|
-
|
26
|
-
|
27
|
-
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
28
37
|
name: home_run
|
29
|
-
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
40
|
none: false
|
31
|
-
requirements:
|
32
|
-
- -
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
35
48
|
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rake
|
36
52
|
prerelease: false
|
37
|
-
|
38
|
-
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
39
65
|
name: activesupport
|
40
|
-
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
41
68
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 9
|
73
|
+
segments:
|
74
|
+
- 2
|
75
|
+
- 3
|
76
|
+
- 5
|
45
77
|
version: 2.3.5
|
46
78
|
type: :runtime
|
47
|
-
|
48
|
-
|
49
|
-
- !ruby/object:Gem::Dependency
|
79
|
+
version_requirements: *id004
|
80
|
+
- !ruby/object:Gem::Dependency
|
50
81
|
name: i18n
|
51
|
-
|
82
|
+
prerelease: false
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
52
84
|
none: false
|
53
|
-
requirements:
|
54
|
-
- -
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
57
92
|
type: :runtime
|
93
|
+
version_requirements: *id005
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: multi_json
|
58
96
|
prerelease: false
|
59
|
-
|
97
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
type: :runtime
|
107
|
+
version_requirements: *id006
|
60
108
|
description: A Ruby class for describing and interacting with timeframes.
|
61
|
-
email:
|
109
|
+
email:
|
62
110
|
- andy@rossmeissl.net
|
63
111
|
executables: []
|
112
|
+
|
64
113
|
extensions: []
|
114
|
+
|
65
115
|
extra_rdoc_files: []
|
66
|
-
|
116
|
+
|
117
|
+
files:
|
67
118
|
- .document
|
68
119
|
- .gitignore
|
69
120
|
- Gemfile
|
@@ -71,36 +122,44 @@ files:
|
|
71
122
|
- README.rdoc
|
72
123
|
- Rakefile
|
73
124
|
- lib/timeframe.rb
|
125
|
+
- lib/timeframe/core_ext/array.rb
|
74
126
|
- lib/timeframe/version.rb
|
75
|
-
- spec/spec.opts
|
76
127
|
- spec/spec_helper.rb
|
77
128
|
- spec/timeframe_spec.rb
|
78
129
|
- timeframe.gemspec
|
79
130
|
homepage: http://github.com/rossmeissl/timeframe
|
80
131
|
licenses: []
|
132
|
+
|
81
133
|
post_install_message:
|
82
134
|
rdoc_options: []
|
83
|
-
|
135
|
+
|
136
|
+
require_paths:
|
84
137
|
- lib
|
85
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
139
|
none: false
|
87
|
-
requirements:
|
88
|
-
- -
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
|
91
|
-
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
hash: 3
|
144
|
+
segments:
|
145
|
+
- 0
|
146
|
+
version: "0"
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
148
|
none: false
|
93
|
-
requirements:
|
94
|
-
- -
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
hash: 3
|
153
|
+
segments:
|
154
|
+
- 0
|
155
|
+
version: "0"
|
97
156
|
requirements: []
|
157
|
+
|
98
158
|
rubyforge_project: timeframe
|
99
|
-
rubygems_version: 1.8.
|
159
|
+
rubygems_version: 1.8.8
|
100
160
|
signing_key:
|
101
161
|
specification_version: 3
|
102
162
|
summary: Date intervals
|
103
|
-
test_files:
|
104
|
-
|
105
|
-
|
106
|
-
- spec/timeframe_spec.rb
|
163
|
+
test_files: []
|
164
|
+
|
165
|
+
has_rdoc:
|
data/spec/spec.opts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|