timeframe 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +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.
data/README.rdoc ADDED
@@ -0,0 +1,13 @@
1
+ = timeframe
2
+
3
+ A Ruby class for describing and interacting with timeframes.
4
+
5
+ == Documentation
6
+
7
+ == Acknoweldgements
8
+
9
+ The good parts of Timeframe all came from the gentlemen at Fingertips[http://fngtps.com].
10
+
11
+ == Copyright
12
+
13
+ Copyright (c) 2010 Andy Rossmeissl.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "timeframe"
8
+ gem.summary = %Q{Date intervals}
9
+ gem.description = %Q{A Ruby class for describing and interacting with timeframes.}
10
+ gem.email = "andy@rossmeissl.net"
11
+ gem.homepage = "http://github.com/rossmeissl/timeframe"
12
+ gem.authors = ["Andy Rossmeissl"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_dependency 'activesupport', '= 3.0.0.beta4'
15
+ gem.add_dependency 'andand'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "timeframe #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/timeframe.rb ADDED
@@ -0,0 +1,297 @@
1
+ require 'rubygems'
2
+ require 'active_support/version'
3
+ %w{
4
+ active_support/core_ext/array/extract_options
5
+ active_support/core_ext/string/conversions
6
+ active_support/core_ext/date/conversions
7
+ active_support/core_ext/integer/time
8
+ active_support/core_ext/numeric/time
9
+ }.each do |active_support_3_requirement|
10
+ require active_support_3_requirement
11
+ end if ActiveSupport::VERSION::MAJOR == 3
12
+ require 'andand'
13
+ require 'timeframe/ykk'
14
+
15
+ # Encapsulates a timeframe between two dates. The dates provided to the class are always until the last date. That means
16
+ # that the last date is excluded.
17
+ #
18
+ # # from 2007-10-01 00:00:00.000 to 2007-10-31 23:59:59.999
19
+ # Timeframe.new(Date(2007,10,1), Date(2007,11,1))
20
+ # # and holds 31 days
21
+ # Timeframe.new(Date(2007,10,1), Date(2007,11,1)).days #=> 31
22
+ class Timeframe
23
+ attr_accessor :from, :to
24
+
25
+ # Creates a new instance of Timeframe. You can either pass a start and end Date or a Hash with named arguments,
26
+ # with the following options:
27
+ #
28
+ # <tt>:month</tt>: Start date becomes the first day of this month, and the end date becomes the first day of
29
+ # the next month. If no <tt>:year</tt> is specified, the current year is used.
30
+ # <tt>:year</tt>: Start date becomes the first day of this year, and the end date becomes the first day of the
31
+ # next year.
32
+ #
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
+ # Examples:
37
+ #
38
+ # Timeframe.new Date.new(2007, 2, 1), Date.new(2007, 4, 1) # February and March
39
+ # Timeframe.new :year => 2004 # The year 2004
40
+ # Timeframe.new :month => 4 # April
41
+ # Timeframe.new :year => 2004, :month => 2 # Feburary 2004
42
+ def initialize(*args)
43
+ options = args.extract_options!
44
+
45
+ if month = options[:month]
46
+ month = Date.parse(month).month if month.is_a? String
47
+ year = options[:year] || Date.today.year
48
+ from = Date.new(year, month, 1)
49
+ to = from.next_month
50
+ elsif year = options[:year]
51
+ from = Date.new(year, 1, 1)
52
+ to = Date.new(year+1, 1, 1)
53
+ end
54
+
55
+ from ||= args.shift.andand.to_date
56
+ to ||= args.shift.andand.to_date
57
+
58
+ raise ArgumentError, "Please supply a start and end date, `#{args.map(&:inspect).to_sentence}' is not enough" if from.nil? or to.nil?
59
+ raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
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
61
+
62
+ @from, @to = from, to
63
+ end
64
+
65
+ def inspect # :nodoc:
66
+ "<Timeframe(#{object_id}) #{days} days starting #{from} ending #{to}>"
67
+ end
68
+
69
+ # The number of days in the timeframe
70
+ #
71
+ # Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days #=> 30
72
+ # Timeframe.new(:month => 1).days #=> 31
73
+ # Timeframe.new(:year => 2004).days #=> 366
74
+ def days
75
+ (to - from).to_i
76
+ end
77
+
78
+ # Returns a string representation of the timeframe
79
+ def to_s
80
+ if [from.day, from.month, to.day, to.month].uniq == [1]
81
+ from.year.to_s
82
+ elsif from.day == 1 and to.day == 1 and to.month - from.month == 1
83
+ "#{Date::MONTHNAMES[from.month]} #{from.year}"
84
+ else
85
+ "the period from #{from.strftime('%d %B')} to #{to.yesterday.strftime('%d %B %Y')}"
86
+ end
87
+ end
88
+
89
+ # Returns true when a Date or other Timeframe is included in this Timeframe
90
+ def include?(obj)
91
+ # puts "checking to see if #{date} is between #{from} and #{to}" if Emitter::DEBUG
92
+ case obj
93
+ when Date
94
+ (from...to).include?(obj)
95
+ when Time
96
+ # (from...to).include?(obj.to_date)
97
+ raise "this wasn't previously supported, but it could be"
98
+ when Timeframe
99
+ from <= obj.from and to >= obj.to
100
+ end
101
+ end
102
+
103
+ # Returns true when the parameter Timeframe is properly included in the Timeframe
104
+ def proper_include?(other_timeframe)
105
+ raise ArgumentError, 'Proper inclusion only makes sense when testing other Timeframes' unless other_timeframe.is_a? Timeframe
106
+ (from < other_timeframe.from) and (to > other_timeframe.to)
107
+ end
108
+
109
+ # Returns true when this timeframe is equal to the other timeframe
110
+ def ==(other)
111
+ # puts "checking to see if #{self} is equal to #{other}" if Emitter::DEBUG
112
+ return false unless other.is_a?(Timeframe)
113
+ from == other.from and to == other.to
114
+ end
115
+ alias :eql? :==
116
+
117
+ # Calculates a hash value for the Timeframe, used for equality checking and Hash lookups.
118
+ #--
119
+ # This needs to be an integer or else it won't use #eql?
120
+ def hash
121
+ from.hash + to.hash
122
+ end
123
+ alias :to_param :hash
124
+
125
+ # Returns an array of month-long subtimeframes
126
+ #--
127
+ # TODO: rename to month_subtimeframes
128
+ def months
129
+ raise ArgumentError, "Please only provide whole-month timeframes to Timeframe#months" unless from.day == 1 and to.day == 1
130
+ raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#months' unless from.year == to.yesterday.year
131
+ year = from.year # therefore this only works in the from year
132
+ (from.month..to.yesterday.month).map { |m| Timeframe.new :month => m, :year => year }
133
+ end
134
+
135
+ # Returns the relevant year as a Timeframe
136
+ def year
137
+ raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#year' unless from.year == to.yesterday.year
138
+ Timeframe.new :year => from.year
139
+ end
140
+
141
+ # Divides a Timeframe into component parts, each no more than a month long.
142
+ #--
143
+ # multiyear safe
144
+ def month_subtimeframes
145
+ (from.year..to.yesterday.year).map do |year|
146
+ (1..12).map do |month|
147
+ Timeframe.new(:year => year, :month => month) & self
148
+ end
149
+ end.flatten.compact
150
+ end
151
+
152
+ # Like #month_subtimeframes, but will discard partial months
153
+ # multiyear safe
154
+ def full_month_subtimeframes
155
+ month_subtimeframes.map { |st| Timeframe.new(:year => st.from.year, :month => st.from.month) }
156
+ end
157
+
158
+ # Divides a Timeframe into component parts, each no more than a year long.
159
+ #--
160
+ # multiyear safe
161
+ def year_subtimeframes
162
+ (from.year..to.yesterday.year).map do |year|
163
+ Timeframe.new(:year => year) & self
164
+ end
165
+ end
166
+
167
+ # Like #year_subtimeframes, but will discard partial years
168
+ #--
169
+ # multiyear safe
170
+ def full_year_subtimeframes
171
+ (from.year..to.yesterday.year).map do |year|
172
+ Timeframe.new :year => year
173
+ end
174
+ end
175
+
176
+ # Crop a Timeframe to end no later than the provided date.
177
+ #--
178
+ # multiyear safe
179
+ def ending_no_later_than(date)
180
+ if to < date
181
+ self
182
+ elsif from >= date
183
+ nil
184
+ else
185
+ Timeframe.multiyear from, date
186
+ end
187
+ end
188
+
189
+ # Returns a timeframe representing the intersection of the given timeframes
190
+ def &(other_timeframe)
191
+ this_timeframe = self
192
+ if other_timeframe == this_timeframe
193
+ this_timeframe
194
+ elsif this_timeframe.from > other_timeframe.from and this_timeframe.to < other_timeframe.to
195
+ this_timeframe
196
+ elsif other_timeframe.from > this_timeframe.from and other_timeframe.to < this_timeframe.to
197
+ other_timeframe
198
+ elsif this_timeframe.from >= other_timeframe.to or this_timeframe.to <= other_timeframe.from
199
+ nil
200
+ else
201
+ Timeframe.new [this_timeframe.from, other_timeframe.from].max, [this_timeframe.to, other_timeframe.to].min, :skip_year_boundary_crossing_check => true
202
+ end
203
+ end
204
+
205
+ # Returns the fraction (as a Float) of another Timeframe that this Timeframe represents
206
+ def /(other_timeframe)
207
+ raise ArgumentError, 'You can only divide a Timeframe by another Timeframe' unless other_timeframe.is_a? Timeframe
208
+ self.days.to_f / other_timeframe.days.to_f
209
+ end
210
+
211
+ # Crop a Timeframe by another Timeframe
212
+ def crop(container)
213
+ raise ArgumentError, 'You can only crop a timeframe by another timeframe' unless container.is_a? Timeframe
214
+ self.class.new [from, container.from].max, [to, container.to].min
215
+ end
216
+
217
+ # Returns an array of Timeframes representing the gaps left in the Timeframe after removing all given Timeframes
218
+ def gaps_left_by(*timeframes)
219
+ # remove extraneous timeframes
220
+ timeframes.reject! { |t| t.to <= from }
221
+ timeframes.reject! { |t| t.from >= to }
222
+
223
+ # crop timeframes
224
+ timeframes.map! { |t| t.crop self }
225
+
226
+ # remove proper subtimeframes
227
+ timeframes.reject! { |t| timeframes.detect { |u| u.proper_include? t } }
228
+
229
+ # escape
230
+ return [self] if timeframes.empty?
231
+
232
+ timeframes.sort! { |x, y| x.from <=> y.from }
233
+
234
+ timeframes.collect(&:to).unshift(from).extend(Ykk).ykk(timeframes.collect(&:from).push(to)) do |gap|
235
+ Timeframe.new(*gap) if gap[1] > gap[0]
236
+ end.compact
237
+ end
238
+
239
+ # Returns true if the union of the given Timeframes includes the Timeframe
240
+ def covered_by?(*timeframes)
241
+ gaps_left_by(*timeframes).empty?
242
+ end
243
+
244
+ # Returns the same Timeframe, only a year earlier
245
+ def last_year
246
+ self.class.new((from - 1.year), (to - 1.year))
247
+ end
248
+
249
+ def to_json # :nodoc:
250
+ { :from => from, :to => to }.to_json
251
+ end
252
+
253
+ class << self
254
+ # Shortcut method to return the Timeframe representing the current year (as defined by Time.now)
255
+ def this_year
256
+ new :year => Time.now.year
257
+ end
258
+
259
+ # Construct a new Timeframe, but constrain it by another
260
+ def constrained_new(from, to, constraint)
261
+ raise ArgumentError, 'Need Date, Date, Timeframe as args' unless from.is_a? Date and to.is_a? Date and constraint.is_a? Timeframe
262
+ raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
263
+ if to <= constraint.from or from >= constraint.to
264
+ new constraint.from, constraint.from
265
+ elsif from.year == to.yesterday.year
266
+ new(from, to) & constraint
267
+ elsif from.year < constraint.from.year and constraint.from.year < to.yesterday.year
268
+ constraint
269
+ else
270
+ new [constraint.from, from].max, [constraint.to, to].min
271
+ end
272
+ end
273
+
274
+ # Shortcut for #new that automatically skips year boundary crossing checks
275
+ def multiyear(from, to)
276
+ from = Date.parse(from) if from.is_a?(String)
277
+ to = Date.parse(to) if to.is_a?(String)
278
+ new from, to, :skip_year_boundary_crossing_check => true
279
+ end
280
+
281
+ # Create a multiyear timeframe +/- number of years around today
282
+ def mid(number)
283
+ from = Time.zone.today - number.years
284
+ to = Time.zone.today + number.years
285
+ multiyear from, to
286
+ end
287
+
288
+ # Construct a new Timeframe by parsing an ISO 8601 time interval string
289
+ # http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
290
+ def interval(interval)
291
+ raise ArgumentError, 'Intervals should be specified as a string' unless interval.is_a? String
292
+ raise ArgumentError, 'Intervals should be specified according to ISO 8601, method 1, eliding times' unless interval =~ /^\d\d\d\d-\d\d-\d\d\/\d\d\d\d-\d\d-\d\d$/
293
+
294
+ new(*interval.split('/').map { |date| Date.parse date })
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,6 @@
1
+ module Ykk
2
+ def ykk(*arys, &blk) # YKK: a better zipper
3
+ return zip(*arys) unless block_given?
4
+ zip(*arys).collect { |a| yield a }
5
+ end
6
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'timeframe'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require 'date'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
@@ -0,0 +1,248 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'date'
3
+
4
+ describe Timeframe do
5
+ describe 'initialization' do
6
+ it 'should create a timeframe using date strings' do
7
+ tf = Timeframe.new('2008-02-14', '2008-05-10')
8
+ tf.from.should == Date.parse('2008-02-14')
9
+ tf.to.should == Date.parse('2008-05-10')
10
+ end
11
+ it 'should create a timeframe using date objects' do
12
+ start = Date.parse('2008-02-14')
13
+ finish = Date.parse('2008-05-10')
14
+ tf = Timeframe.new(start, finish)
15
+ tf.from.should == start
16
+ tf.to.should == finish
17
+ end
18
+ it "should accept months" do
19
+ timeframe = Timeframe.new(:month => 1)
20
+ timeframe.from.should == Date.today.change(:month => 1, :day => 1)
21
+ timeframe.to.should == Date.today.change(:month => 2, :day => 1)
22
+ end
23
+ it "should accept month names" do
24
+ timeframe = Timeframe.new(:month => 'february')
25
+ timeframe.from.should == Date.today.change(:month => 2, :day => 1)
26
+ timeframe.to.should == Date.today.change(:month => 3, :day => 1)
27
+ end
28
+ it "should accept years" do
29
+ timeframe = Timeframe.new(:year => 2004)
30
+ timeframe.from.should == Date.new(2004, 1, 1)
31
+ timeframe.to.should == Date.new(2005, 1, 1)
32
+ end
33
+ it "should accept years and months" do
34
+ timeframe = Timeframe.new(:year => 2005, :month => 5)
35
+ timeframe.from.should == Date.new(2005, 5, 1)
36
+ timeframe.to.should == Date.new(2005, 6, 1)
37
+ end
38
+ it "should not accept just one date argument" do
39
+ lambda {
40
+ Timeframe.new Date.new(2007, 2, 1)
41
+ }.should raise_error(ArgumentError, /supply/)
42
+ end
43
+ it "should not accept end date that is earlier than start date" do
44
+ lambda {
45
+ timeframe = Timeframe.new Date.new(2008, 1, 1), Date.new(2007, 1, 1)
46
+ }.should raise_error(ArgumentError, /earlier/)
47
+ end
48
+ it "should not accept timeframes that cross year boundaries" do
49
+ lambda {
50
+ timeframe = Timeframe.new Date.new(2007, 1, 1), Date.new(2008, 1, 2)
51
+ }.should raise_error(ArgumentError, /cross/)
52
+ end
53
+ it "should optionally accept timeframes that cross year boundaries" do
54
+ lambda {
55
+ timeframe = Timeframe.new Date.new(2007, 1, 1), Date.new(2008, 1, 2), :skip_year_boundary_crossing_check => true
56
+ }.should_not raise_error
57
+ end
58
+ end
59
+
60
+ describe '#inspect' do
61
+ it 'should return the time frame in readable text' do
62
+ start = Date.parse('2008-02-14')
63
+ finish = Date.parse('2008-05-10')
64
+ tf = Timeframe.new(start, finish)
65
+ tf.inspect.should =~ /<Timeframe\(-?\d+\) 86 days starting 2008-02-14 ending 2008-05-10>/
66
+ end
67
+ end
68
+
69
+ describe '.constrained_new' do
70
+ let(:start) { Date.parse('2008-02-14') }
71
+ let(:finish) { Date.parse('2008-05-10') }
72
+ let(:constraint_start) { Date.parse('2008-01-01') }
73
+ let(:constraint_finish) { Date.parse('2008-12-01') }
74
+ let(:constraint) { Timeframe.new(constraint_start, constraint_finish) }
75
+
76
+ it "should allow for constrained creation" do
77
+ constraint = Timeframe.new :year => 2008
78
+ may = Timeframe.new Date.new(2008,5,1), Date.new(2008,6,1)
79
+ january = Timeframe.new Date.new(2008,1,1), Date.new(2008,2,1)
80
+ Timeframe.constrained_new(may.from, may.to, constraint).should == may
81
+ Timeframe.constrained_new(Date.new(2007,1,1), Date.new(2010,1,1), constraint).should == constraint
82
+ Timeframe.constrained_new(Date.new(2007,11,1), Date.new(2008,2,1), constraint).should == january
83
+ end
84
+ it 'should return a timeframe spanning start and end date if within constraint' do
85
+ tf = Timeframe.constrained_new(start, finish, constraint)
86
+ tf.from.should == start
87
+ tf.to.should == finish
88
+ end
89
+ it 'should return a timeframe spanning constraint start and end date if outside constraint' do
90
+ constraint = Timeframe.new(start, finish)
91
+ tf = Timeframe.constrained_new(constraint_start, constraint_finish, constraint)
92
+ tf.from.should == start
93
+ tf.to.should == finish
94
+ end
95
+ it 'should return a timeframe starting at constraint start' do
96
+ start = Date.parse('2008-01-01')
97
+ constraint_start = Date.parse('2008-01-14')
98
+ constraint = Timeframe.new(constraint_start, constraint_finish)
99
+ tf = Timeframe.constrained_new(start, finish, constraint)
100
+ tf.from.should == constraint_start
101
+ tf.to.should == finish
102
+ end
103
+ it 'should return a timeframe ending at constraint end' do
104
+ constraint_finish = Date.parse('2008-04-14')
105
+ constraint = Timeframe.new(constraint_start, constraint_finish)
106
+ tf = Timeframe.constrained_new(start, finish, constraint)
107
+ tf.from.should == start
108
+ tf.to.should == constraint_finish
109
+ end
110
+ it "should return a 0-length timeframe when constraining a timeframe by a disjoint timeframe" do
111
+ constraint = Timeframe.new :year => 2010
112
+ timeframe = Timeframe.constrained_new(
113
+ Date.new(2009,1,1), Date.new(2010,1,1), constraint)
114
+ timeframe.days.should == 0
115
+ end
116
+ end
117
+
118
+ describe '.this_year' do
119
+ it "should return the current year" do
120
+ Timeframe.this_year.should == Timeframe.new(:year => Time.now.year)
121
+ end
122
+ end
123
+
124
+ describe '#days' do
125
+ it "should return them number of days included" do
126
+ #TODO: make these separate "it" blocks, per best practices
127
+ Timeframe.new(Date.new(2007, 1, 1), Date.new(2008, 1, 1)).days.should == 365
128
+ Timeframe.new(Date.new(2008, 1, 1), Date.new(2009, 1, 1)).days.should == 366 #leap year
129
+ Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days.should == 30
130
+ Timeframe.new(Date.new(2007, 11, 1), Date.new(2008, 1, 1)).days.should == 61
131
+ Timeframe.new(Date.new(2007, 2, 1), Date.new(2007, 3, 1)).days.should == 28
132
+ Timeframe.new(Date.new(2008, 2, 1), Date.new(2008, 3, 1)).days.should == 29
133
+ Timeframe.new(Date.new(2008, 2, 1), Date.new(2008, 2, 1)).days.should == 0
134
+ end
135
+ end
136
+
137
+ describe '#include?' do
138
+ it "should know if a certain date is included in the Timeframe" do
139
+ #TODO: make these separate "it" blocks, per best practices
140
+ timeframe = Timeframe.new :year => 2008, :month => 2
141
+ [
142
+ Date.new(2008, 2, 1),
143
+ Date.new(2008, 2, 5),
144
+ Date.new(2008, 2, 29)
145
+ ].each do |date|
146
+ timeframe.include?(date).should == true
147
+ end
148
+ [
149
+ Date.new(2008, 1, 1),
150
+ Date.new(2007, 2, 1),
151
+ Date.new(2008, 3, 1)
152
+ ].each do |date|
153
+ timeframe.include?(date).should == false
154
+ end
155
+ end
156
+ end
157
+
158
+ describe '#==' do
159
+ it "should be able to know if it's equal to another Timeframe object" do
160
+ Timeframe.new(:year => 2007).should == Timeframe.new(:year => 2007)
161
+ Timeframe.new(:year => 2004, :month => 1).should == Timeframe.new(:year => 2004, :month => 1)
162
+ end
163
+
164
+ it "should hash equal hash values when the timeframe is equal" do
165
+ Timeframe.new(:year => 2007).hash.should == Timeframe.new(:year => 2007).hash
166
+ Timeframe.new(:year => 2004, :month => 1).hash.should == Timeframe.new(:year => 2004, :month => 1).hash
167
+ end
168
+ end
169
+
170
+ describe '#months' do
171
+ it "should return an array of month-long subtimeframes" do
172
+ Timeframe.new(:year => 2009).months.length.should == 12
173
+ end
174
+ it "should not return an array of month-long subtimeframes if provided an inappropriate range" do
175
+ lambda {
176
+ Timeframe.new(Date.new(2009, 3, 2), Date.new(2009, 3, 5)).months
177
+ }.should raise_error(ArgumentError, /whole/)
178
+ lambda {
179
+ Timeframe.new(Date.new(2009, 1, 1), Date.new(2012, 1, 1), :skip_year_boundary_crossing_check => true).months
180
+ }.should raise_error(ArgumentError, /dangerous during/)
181
+ end
182
+ end
183
+
184
+ describe '#year' do
185
+ it "should return the relevant year of a timeframe" do
186
+ Timeframe.new(Date.new(2009, 2, 1), Date.new(2009, 4, 1)).year.should == Timeframe.new(:year => 2009)
187
+ end
188
+ it "should not return the relevant year of a timeframe if provided an inappropriate range" do
189
+ lambda {
190
+ Timeframe.new(Date.new(2009, 1, 1), Date.new(2012, 1, 1), :skip_year_boundary_crossing_check => true).year
191
+ }.should raise_error(ArgumentError, /dangerous during/)
192
+ end
193
+ end
194
+
195
+ describe '#&' do
196
+ it "should return its intersection with another timeframe" do
197
+ #TODO: make these separate "it" blocks, per best practices
198
+ (Timeframe.new(:month => 4) & Timeframe.new(:month => 6)).should be_nil
199
+ (Timeframe.new(:month => 4) & Timeframe.new(:month => 5)).should be_nil
200
+ (Timeframe.new(:month => 4) & Timeframe.new(:month => 4)).should == Timeframe.new(:month => 4)
201
+ (Timeframe.new(:year => Time.now.year) & Timeframe.new(:month => 4)).should == Timeframe.new(:month => 4)
202
+ (Timeframe.new(Date.new(2009, 2, 1), Date.new(2009, 6, 1)) & Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 8, 1))).should == Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 6, 1))
203
+ end
204
+ end
205
+
206
+ describe '#/' do
207
+ it "should return a fraction of another timeframe" do
208
+ (Timeframe.new(:month => 4, :year => 2009) / Timeframe.new(:year => 2009)).should == (30.0 / 365.0)
209
+ end
210
+ end
211
+
212
+ describe '#gaps_left_by' do
213
+ it "should be able to ascertain gaps left by a list of other Timeframes" do
214
+ Timeframe.new(:year => 2009).gaps_left_by(
215
+ Timeframe.new(:year => 2009, :month => 3),
216
+ Timeframe.new(:year => 2009, :month => 5),
217
+ Timeframe.new(Date.new(2009, 8, 1), Date.new(2009, 11, 1)),
218
+ Timeframe.new(Date.new(2009, 9, 1), Date.new(2009, 10, 1))
219
+ ).should ==
220
+ [ Timeframe.new(Date.new(2009, 1, 1), Date.new(2009, 3, 1)),
221
+ Timeframe.new(Date.new(2009, 4, 1), Date.new(2009, 5, 1)),
222
+ Timeframe.new(Date.new(2009, 6, 1), Date.new(2009, 8, 1)),
223
+ Timeframe.new(Date.new(2009, 11, 1), Date.new(2010, 1, 1)) ]
224
+ end
225
+ end
226
+
227
+ describe '#covered_by?' do
228
+ it "should be able to ascertain gaps left by a list of other Timeframes" do
229
+ Timeframe.new(:year => 2009).covered_by?(
230
+ Timeframe.new(:month => 1, :year => 2009),
231
+ Timeframe.new(Date.new(2009, 2, 1), Date.new(2010, 1, 1))
232
+ ).should be_true
233
+ end
234
+ end
235
+
236
+ describe '#last_year' do
237
+ it "should return its predecessor in a previous year" do
238
+ Timeframe.this_year.last_year.should ==
239
+ Timeframe.new(Date.new(Date.today.year - 1, 1, 1), Date.new(Date.today.year, 1, 1))
240
+ end
241
+ end
242
+
243
+ describe 'Timeframe:class#interval' do
244
+ it 'should parse ISO 8601 interval format' do
245
+ Timeframe.interval('2009-01-01/2010-01-01').should == Timeframe.new(:year => 2009)
246
+ end
247
+ end
248
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timeframe
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Andy Rossmeissl
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-15 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 9
31
+ version: 1.2.9
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: activesupport
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 3
43
+ - 0
44
+ - 0
45
+ - beta4
46
+ version: 3.0.0.beta4
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: andand
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :runtime
60
+ version_requirements: *id003
61
+ description: A Ruby class for describing and interacting with timeframes.
62
+ email: andy@rossmeissl.net
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ extra_rdoc_files:
68
+ - LICENSE
69
+ - README.rdoc
70
+ files:
71
+ - .document
72
+ - .gitignore
73
+ - LICENSE
74
+ - README.rdoc
75
+ - Rakefile
76
+ - VERSION
77
+ - lib/timeframe.rb
78
+ - lib/timeframe/ykk.rb
79
+ - spec/spec.opts
80
+ - spec/spec_helper.rb
81
+ - spec/timeframe_spec.rb
82
+ has_rdoc: true
83
+ homepage: http://github.com/rossmeissl/timeframe
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options:
88
+ - --charset=UTF-8
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ requirements: []
106
+
107
+ rubyforge_project:
108
+ rubygems_version: 1.3.6
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Date intervals
112
+ test_files:
113
+ - spec/spec_helper.rb
114
+ - spec/timeframe_spec.rb