timeframe 0.0.1

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 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