timeframe 0.0.11 → 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.
- 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
|