timespan 0.4.6 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ gem 'chronic'
4
4
  gem 'chronic_duration'
5
5
  gem 'activesupport', '>= 3.0.0'
6
6
  gem 'spanner'
7
+ gem 'sugar-high', '~> 0.7.2' # for range intersect
7
8
  gem 'xduration', '~> 2.2'
8
9
 
9
10
  group :test, :development do
@@ -12,10 +13,12 @@ group :test, :development do
12
13
  # gem 'mongoid', '>= 2.4'
13
14
  # gem 'bson', '>= 1.6'
14
15
 
15
- gem 'mongoid', '~> 3.0.0' #, :git => 'git://github.com/mongoid/mongoid.git'
16
+ gem 'mongoid', '~> 3.0'
17
+ gem 'origin-selectable_ext', '~> 0.1.1'
16
18
  # gem 'i18n'
17
19
  end
18
20
 
21
+
19
22
  group :development do
20
23
  gem "rdoc", ">= 3.12"
21
24
  gem "bundler", ">= 1.0.0"
data/Gemfile.lock CHANGED
@@ -58,7 +58,9 @@ GEM
58
58
  moped (1.2.0)
59
59
  multi_json (1.3.6)
60
60
  numerizer (0.1.1)
61
- origin (1.0.4)
61
+ origin (1.0.9)
62
+ origin-selectable_ext (0.1.1)
63
+ origin
62
64
  polyglot (0.3.3)
63
65
  rack (1.4.1)
64
66
  rack-cache (1.2)
@@ -102,6 +104,7 @@ GEM
102
104
  hike (~> 1.2)
103
105
  rack (~> 1.0)
104
106
  tilt (~> 1.1, != 1.3.0)
107
+ sugar-high (0.7.2)
105
108
  thor (0.15.4)
106
109
  tilt (1.3.3)
107
110
  treetop (1.4.10)
@@ -121,10 +124,12 @@ DEPENDENCIES
121
124
  chronic
122
125
  chronic_duration
123
126
  jeweler (>= 1.8.3)
124
- mongoid (~> 3.0.0)
127
+ mongoid (~> 3.0)
128
+ origin-selectable_ext (~> 0.1.1)
125
129
  rails (~> 3.2.7)
126
130
  rdoc (>= 3.12)
127
131
  rspec (>= 2.8.0)
128
132
  simplecov (>= 0.5)
129
133
  spanner
134
+ sugar-high (~> 0.7.2)
130
135
  xduration (~> 2.2)
data/README.md CHANGED
@@ -309,6 +309,23 @@ TimeSpan.start_field = :start
309
309
  TimeSpan.end_field = :end
310
310
  ```
311
311
 
312
+ ## Ranges
313
+
314
+ A Range can be converted into either a `Timespan` or a `DurationRange`
315
+
316
+ ### DurationRange
317
+
318
+ ```ruby
319
+ dr = (1..5).days # => DurationRange 1..5, :days
320
+ ts =(1..5).days(:timespan) # => Timespan start_date: 1.day.from_now, end_date: 5.days.from_now
321
+
322
+ dr.between?(4.days) # => true
323
+ ```
324
+
325
+ You can also use Range#intersect from *sugar-high* gem to test intersection of time ranges ;)
326
+
327
+ ### Timespan
328
+
312
329
  ## Contributing to Timespan
313
330
 
314
331
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.6
1
+ 0.4.9
@@ -0,0 +1,12 @@
1
+ class Hash
2
+ def __evolve_to_timespan__
3
+ serializer = Mongoid::Fields::Timespan
4
+ object = self
5
+ ::Timespan.new :from => serializer.from(object), :to => serializer.to(object), asap: serializer.asap(object)
6
+ end
7
+
8
+ def __evolve_to_duration_range__
9
+ range = Range.new self['from'], self['to']
10
+ ::DurationRange.new range, :seconds
11
+ end
12
+ end
@@ -0,0 +1,119 @@
1
+ class TimespanRange < DelegateDecorator
2
+ attr_accessor :unit, :range
3
+
4
+ def initialize range, unit
5
+ super(range)
6
+ @range = Timespan.new between: range
7
+ @unit = unit.to_s.singularize.to_sym
8
+ end
9
+ end
10
+
11
+ class DurationRange < DelegateDecorator
12
+ attr_accessor :unit, :range
13
+
14
+ def initialize range, unit
15
+ super(range)
16
+ @unit = unit.to_s.singularize.to_sym
17
+ @range = range
18
+ end
19
+
20
+ def __evolve_to_duration_range__
21
+ self
22
+ end
23
+
24
+ def mongoize
25
+ {:from => range.min.to_i, :to => range.max.to_i}
26
+ end
27
+
28
+ def between? duration
29
+ obj = case duration
30
+ when Duration
31
+ duration
32
+ else
33
+ Duration.new duration
34
+ end
35
+ obj.total >= min && obj.total <= max
36
+ end
37
+
38
+ class << self
39
+ # See http://mongoid.org/en/mongoid/docs/upgrading.html
40
+
41
+ # Serialize a Hash (with DurationRange keys) or a DurationRange to
42
+ # a BSON serializable type.
43
+ #
44
+ # @param [Timespan, Hash, Integer, String] value
45
+ # @return [Hash] Timespan in seconds
46
+ def mongoize object
47
+ mongoized = case object
48
+ when DurationRange then object.mongoize
49
+ when Hash
50
+ object
51
+ when Range
52
+ object.send(:seconds).mongoize
53
+ else
54
+ object
55
+ end
56
+ # puts "mongoized: #{mongoized} - Hash"
57
+ mongoized
58
+ end
59
+
60
+ # Deserialize a Timespan given the hash stored by Mongodb
61
+ #
62
+ # @param [Hash] Timespan as hash
63
+ # @return [Timespan] deserialized Timespan
64
+ def demongoize(object)
65
+ return if !object
66
+
67
+ demongoized = case object
68
+ when Hash
69
+ object.__evolve_to_duration_range__
70
+ else
71
+ raise "Unable to demongoize DurationRange from: #{object}"
72
+ end
73
+ # puts "demongoized: #{demongoized} - DurationRange"
74
+ demongoized
75
+ end
76
+
77
+ # Converts the object that was supplied to a criteria and converts it
78
+ # into a database friendly form.
79
+ def evolve(object)
80
+ object.__evolve_to_duration_range__.mongoize
81
+ end
82
+
83
+ protected
84
+
85
+ def parse duration
86
+ if duration.kind_of? Numeric
87
+ return Duration.new duration
88
+ else
89
+ case duration
90
+ when Timespan
91
+ duration.duration
92
+ when Duration
93
+ duration
94
+ when Hash
95
+ Duration.new duration
96
+ when Time
97
+ duration.to_i
98
+ when DateTime, Date
99
+ duration.to_time.to_i
100
+ when String
101
+ Duration.new parse_duration(duration)
102
+ else
103
+ raise ArgumentError, "Unsupported duration type: #{duration.inspect} of class #{duration.class}"
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+ end
110
+
111
+
112
+ class Range
113
+ [:seconds, :minutes, :hours, :days, :weeks, :months, :years].each do |unit|
114
+ define_method unit do |type = :duration|
115
+ timerange = Range.new self.min.send(unit), self.max.send(unit)
116
+ type == :timespan ? TimespanRange.new(timerange, unit) : DurationRange.new(timerange, unit)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1 @@
1
+ require 'timespan/core_ext/range'
@@ -1,6 +1,14 @@
1
+ require 'origin-selectable_ext'
2
+ require 'timespan/core_ext/hash'
3
+
1
4
  class Timespan
2
5
  Serializer = Mongoid::Fields::Timespan
3
6
 
7
+ def __evolve_to_timespan__
8
+ self
9
+ end
10
+
11
+
4
12
  # See http://mongoid.org/en/mongoid/docs/upgrading.html
5
13
 
6
14
  # Serialize a Timespan or a Hash (with Timespan units) or a Duration in some form to
@@ -9,7 +17,7 @@ class Timespan
9
17
  # @param [Timespan, Hash, Integer, String] value
10
18
  # @return [Hash] Timespan in seconds
11
19
  def mongoize
12
- {:from => Serializer.serialize_time(start_time), :to => Serializer.serialize_time(end_time), :duration => duration.total }
20
+ {:from => Serializer.serialize_time(start_time), :to => Serializer.serialize_time(end_time), :duration => duration.total, :asap => asap? }
13
21
  end
14
22
 
15
23
  class << self
@@ -38,7 +46,7 @@ class Timespan
38
46
  return if !object
39
47
  case object
40
48
  when Hash
41
- ::Timespan.new :from => Serializer.from(object), :to => Serializer.to(object)
49
+ object.__evolve_to_timespan__
42
50
  else
43
51
  ::Timespan.new object
44
52
  end
@@ -47,10 +55,43 @@ class Timespan
47
55
  # Converts the object that was supplied to a criteria and converts it
48
56
  # into a database friendly form.
49
57
  def evolve(object)
50
- case object
51
- when Timespan then object.mongoize
52
- else object
58
+ object.__evolve_to_timespan__.mongoize
59
+ end
60
+
61
+ def custom_serialization?(operator)
62
+ return false unless operator
63
+ case operator
64
+ when '$gte', '$gt', '$lt', '$lte', '$eq', '$between', '$btw'
65
+ true
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ def custom_specify(name, operator, value, options = {})
72
+ puts "custom_specify: #{name}"
73
+ timespan = value.__evolve_to_timespan__
74
+ case operator
75
+ when '$gte', '$gt', '$lt', '$lte', '$eq', '$between', '$btw'
76
+ specify_with_asap(name, operator, timespan, options)
77
+ else
78
+ raise RuntimeError, "Unsupported operator"
79
+ end
80
+ end
81
+
82
+ def specify_with_asap(name, operator, timespan, options)
83
+ query = { 'from' => {}, 'to' => {} }
84
+ case operator
85
+ when '$gte', '$gt'
86
+ query['from'][operator] = Serializer.serialize_time(timespan.min)
87
+ when '$lte', '$lt', '$eq'
88
+ query['to'][operator] = Serializer.serialize_time(timespan.min)
89
+ when '$between', '$btw'
90
+ query['from']['$gte'] = Serializer.serialize_time(timespan.min)
91
+ query['to']['$lte'] = Serializer.serialize_time(timespan.max)
53
92
  end
93
+ puts "query: #{query}"
94
+ query
54
95
  end
55
96
  end
56
97
  end
@@ -39,6 +39,12 @@ module Mongoid
39
39
  deserialize_time to_value
40
40
  end
41
41
 
42
+ def asap hash
43
+ asap_value = hash['asap'] || hash[:asap]
44
+ raise ArgumentError, ":asap is nil, #{hash.inspect}" if ![true, false, nil].include? asap_value
45
+ asap_value
46
+ end
47
+
42
48
  def serialize_time time
43
49
  raise ArgumentError, "Can't serialize time from nil" if time.nil?
44
50
  time.to_i
data/lib/timespan.rb CHANGED
@@ -3,6 +3,13 @@ require 'chronic'
3
3
  require 'chronic_duration'
4
4
  require 'spanner'
5
5
 
6
+ # Range intersection that works with dates!
7
+ require 'sugar-high/range'
8
+ require 'sugar-high/delegate'
9
+ require 'sugar-high/kind_of'
10
+
11
+ require 'timespan/core_ext'
12
+
6
13
  require 'timespan/units'
7
14
  require 'timespan/compare'
8
15
  require 'timespan/printer'
@@ -44,7 +51,6 @@ class Timespan
44
51
 
45
52
  def initialize options = {}
46
53
  @is_new = true
47
-
48
54
  @init_options = options
49
55
  validate! if options == {}
50
56
 
@@ -60,41 +66,70 @@ class Timespan
60
66
  @is_new = false
61
67
  end
62
68
 
63
- def self.from start, duration, options = {}
64
- start = case start.to_sym
65
- when :now, :asap
66
- Time.now
67
- when :today
68
- Date.today
69
- when :tomorrow
70
- Date.today + 1.day
71
- when :next_week # requires active_support
72
- date = Date.today.next_week
73
- options[:start] ? date.beginning_of_week : date
74
- when :next_month
75
- date = Date.today.next_month
76
- options[:start] ? date.at_beginning_of_month.next_month : date
77
- else
78
- start
69
+ class << self
70
+ def max_date
71
+ @max_date ||= Time.now + 10.years
79
72
  end
80
73
 
81
- self.new start_date: start, duration: duration
82
- end
74
+ def min_date
75
+ @min_date ||= Time.now
76
+ end
83
77
 
84
- def self.untill ending
85
- ending = case ending.to_sym
86
- when :tomorrow
87
- Date.today + 1.day
88
- when :next_week # requires active_support
89
- date = Date.today.next_week
90
- options[:start] ? date.beginning_of_week : date
91
- when :next_month
92
- date = Date.today.next_month
93
- options[:start] ? date.at_beginning_of_month.next_month : date
94
- else
95
- ending
78
+ def max_date= date
79
+ @max_date = date if valid_date?(date)
80
+ end
81
+
82
+ def min_date= date
83
+ @max_date = date if valid_date?(date)
84
+ end
85
+
86
+ def asap options = {}
87
+ self.new options.merge(asap: true)
88
+ end
89
+
90
+ def from start, duration, options = {}
91
+ start = case start.to_sym
92
+ when :now, :asap
93
+ Time.now
94
+ when :today
95
+ Date.today
96
+ when :tomorrow
97
+ Date.today + 1.day
98
+ when :next_week # requires active_support
99
+ date = Date.today.next_week
100
+ options[:start] ? date.beginning_of_week : date
101
+ when :next_month
102
+ date = Date.today.next_month
103
+ options[:start] ? date.at_beginning_of_month.next_month : date
104
+ else
105
+ start
106
+ end
107
+
108
+ self.new start_date: start, duration: duration
109
+ end
110
+
111
+ def untill ending
112
+ ending = case ending.to_sym
113
+ when :tomorrow
114
+ Date.today + 1.day
115
+ when :next_week # requires active_support
116
+ date = Date.today.next_week
117
+ options[:start] ? date.beginning_of_week : date
118
+ when :next_month
119
+ date = Date.today.next_month
120
+ options[:start] ? date.at_beginning_of_month.next_month : date
121
+ else
122
+ ending
123
+ end
124
+ self.new start_date: Date.now, end_date: ending
125
+ end
126
+ alias_method :until, :untill
127
+
128
+ protected
129
+
130
+ def valid_date? date
131
+ date.any_kind_of?(Date, Time, DateTime)
96
132
  end
97
- self.new start_date: Date.now, end_date: ending
98
133
  end
99
134
 
100
135
  def start_time= time
@@ -122,21 +157,37 @@ class Timespan
122
157
  end
123
158
  alias_method :end_date=, :end_time=
124
159
 
125
- def until time
160
+ def asap?
161
+ @asap
162
+ end
163
+
164
+ def min
165
+ asap ? Time.now : start_time
166
+ end
167
+
168
+ def max
169
+ end_time
170
+ end
171
+
172
+ def untill time
126
173
  self.end_time = time
127
174
  self
128
175
  end
176
+ alias_method :until, :untill
129
177
 
130
178
  def convert_to_time time
179
+ time = Duration.new time if time.kind_of? Numeric
131
180
  case time
132
181
  when String
133
182
  Chronic.parse(time)
134
183
  when Date, DateTime
135
184
  time.to_time
185
+ when Duration
186
+ (Time.now + time).to_time
136
187
  when Time
137
188
  time
138
189
  else
139
- raise ArgumentError, "A valid time must be either a String, Date, Time or DateTime, was: #{time.inspect}"
190
+ raise ArgumentError, "A valid time must be either a String, Duration, Date, Time or DateTime, was: #{time.inspect} (#{time.class})"
140
191
  end
141
192
  end
142
193
 
@@ -153,18 +204,40 @@ class Timespan
153
204
  from = options[first_from(START_KEYS, options)]
154
205
  to = options[first_from(END_KEYS, options)]
155
206
  dur = options[first_from(DURATION_KEYS, options)]
207
+ asap = options[:asap]
208
+
209
+ if options[:at_least]
210
+ to = Timespan.max_date
211
+ from = Time.now + options[:at_least]
212
+ end
213
+
214
+ if options[:at_most]
215
+ to = Time.now + options[:at_most]
216
+ from = Time.now
217
+ end
218
+
219
+ if options[:between]
220
+ from = Time.now + options[:between].min
221
+ to = Time.now + options[:between].max
222
+ end
223
+
224
+ # puts "configure: to:#{to}, from:#{from}, dur:#{dur}, asap:#{asap}"
156
225
 
226
+ @asap = asap if asap
157
227
  self.duration = dur if dur
158
228
  self.start_time = from if from
159
229
  self.end_time = to if to
160
230
 
231
+ # puts "configured: start:#{self.start_time}, end:#{self.end_time}, duration:#{self.duration}, asap:#{self.asap?}"
232
+
161
233
  default_from_now!
162
234
  calculate_miss!
163
235
  rescue ArgumentError => e
164
236
  raise TimeParseError, e.message
165
- rescue Exception => e
166
- calculate_miss!
167
- validate!
237
+ # rescue Exception => e
238
+ # puts "Exception: #{e}"
239
+ # calculate_miss!
240
+ # validate!
168
241
  end
169
242
 
170
243
  def default_from_now!
@@ -0,0 +1,60 @@
1
+ require 'timespan/mongoid/spec_helper'
2
+
3
+ describe Range do
4
+ subject { timerange }
5
+
6
+ describe 'create DurationRange' do
7
+ let(:range) { (1..5) }
8
+ let (:timerange) { range.days }
9
+
10
+ specify { subject.should be_a DurationRange }
11
+
12
+ its(:min) { should be_a Fixnum }
13
+ its(:max) { should be_a Fixnum }
14
+
15
+ specify { subject.min.should == 1.day }
16
+ specify { subject.max.should == 5.days }
17
+ end
18
+ end
19
+
20
+ describe DurationRange do
21
+ subject { timerange }
22
+
23
+ let(:range) { (1..5) }
24
+
25
+ context 'day range' do
26
+ let (:timerange) { range.days }
27
+
28
+ its(:range) { should be_a Range }
29
+ its(:min) { should == 1.day }
30
+ its(:max) { should == 5.days }
31
+ its(:unit) { should == :day }
32
+
33
+ specify { subject.between?(4.days).should be_true }
34
+ end
35
+
36
+ context 'week range' do
37
+ let (:timerange) { range.weeks }
38
+
39
+ its(:range) { should be_a Range }
40
+ its(:min) { should == 1.week }
41
+ its(:max) { should == 5.weeks }
42
+ its(:unit) { should == :week }
43
+ end
44
+
45
+ context 'month range' do
46
+ let (:timerange) { range.months }
47
+
48
+ its(:min) { should == 1.month }
49
+ its(:max) { should == 5.months }
50
+ its(:unit) { should == :month }
51
+ end
52
+
53
+ context 'year range' do
54
+ let (:timerange) { range.years }
55
+
56
+ its(:min) { should == 1.year }
57
+ its(:max) { should == 5.years }
58
+ its(:unit) { should == :year }
59
+ end
60
+ end
@@ -0,0 +1,57 @@
1
+ require 'timespan/mongoid/spec_helper'
2
+
3
+ describe Range do
4
+ subject { timerange }
5
+
6
+ let(:range) { (1..5) }
7
+
8
+ describe 'create TimespanRange' do
9
+
10
+ let (:timerange) { range.months(:timespan) }
11
+
12
+ specify { subject.should be_a TimespanRange }
13
+ its(:range) { should be_a Timespan }
14
+ end
15
+ end
16
+
17
+ describe TimespanRange do
18
+ subject { timerange }
19
+
20
+ let(:range) { (1..5) }
21
+
22
+ context 'day range' do
23
+ let (:timerange) { range.days(:timespan) }
24
+
25
+ its(:range) { should be_a Timespan }
26
+ its(:min) { should == 1.day }
27
+ its(:max) { should == 5.days }
28
+ its(:unit) { should == :day }
29
+ end
30
+
31
+ context 'week range' do
32
+ let (:timerange) { range.weeks(:timespan) }
33
+
34
+ its(:range) { should be_a Timespan }
35
+ its(:min) { should == 1.week }
36
+ its(:max) { should == 5.weeks }
37
+ its(:unit) { should == :week }
38
+ end
39
+
40
+ context 'month range' do
41
+ let (:timerange) { range.months(:timespan) }
42
+
43
+ its(:range) { should be_a Timespan }
44
+ its(:min) { should == 1.month }
45
+ its(:max) { should == 5.months }
46
+ its(:unit) { should == :month }
47
+ end
48
+
49
+ context 'year range' do
50
+ let (:timerange) { range.years(:timespan) }
51
+
52
+ its(:range) { should be_a Timespan }
53
+ its(:min) { should == 1.year }
54
+ its(:max) { should == 5.years }
55
+ its(:unit) { should == :year }
56
+ end
57
+ end
@@ -0,0 +1,216 @@
1
+ require 'timespan/mongoid/spec_helper'
2
+
3
+ describe TimeSpan do
4
+ subject { account }
5
+
6
+ before :all do
7
+ # Mongoid.logger.level = Logger::DEBUG
8
+ # Moped.logger.level = Logger::DEBUG
9
+ # Mongoid.logger = Logger.new($stdout)
10
+ # Moped.logger = Logger.new($stdout)
11
+ end
12
+
13
+ def max_asap
14
+ 10.days.from_now.to_i
15
+ end
16
+
17
+ context '10 Accounts with period and duration-range' do
18
+
19
+ # TODO:
20
+ # Doesn't handle dynamic ASAP as attempted
21
+ # implemented via custom_specify
22
+
23
+ before do
24
+ Account.delete_all
25
+
26
+ @acc1 = Account.create name: '1', period: Timespan.from(:today, 5.days),
27
+ time_period: TimePeriod.new(flex: (2..5).minutes)
28
+
29
+ @acc2 = Account.create name: '2', period: Timespan.from(:today, 5.days),
30
+ time_period: TimePeriod.new(flex: (2..4).minutes)
31
+
32
+ @acc3 = Account.create name: '3', period: Timespan.from(:today, 5.days),
33
+ time_period: TimePeriod.new(flex: (0..4).minutes)
34
+
35
+ @acc4 = Account.create name: '4', period: Timespan.from(:today, 5.days),
36
+ time_period: TimePeriod.new(flex: (0..3).minutes)
37
+
38
+ @acc5 = Account.create name: '5', period: Timespan.from(:today, 5.days),
39
+ time_period: TimePeriod.new(flex: (0..2).minutes)
40
+
41
+ @acc6 = Account.create name: '6', period: Timespan.from(:next_month, 100.days),
42
+ time_period: TimePeriod.new(flex: (0..2).minutes)
43
+
44
+ @acc7 = Account.create name: '7', period: Timespan.from(:next_week, 100.days),
45
+ time_period: TimePeriod.new(flex: (0..2).minutes)
46
+
47
+ @acc8 = Account.create name: '8', period: Timespan.new(start_date: 9.days.from_now, duration: 100.days),
48
+ time_period: TimePeriod.new(flex: (5..10).minutes)
49
+
50
+ @acc9 = Account.create name: '9', period: Timespan.new(start_date: 40.days.from_now, duration: 100.days),
51
+ time_period: TimePeriod.new(flex: (0..3).minutes)
52
+
53
+ @acc8 = Account.create name: '10', period: Timespan.new(start_date: 1.week.from_now, duration: 100.days),
54
+ time_period: TimePeriod.new(flex: (3..6).minutes)
55
+ end
56
+
57
+ def yesterday
58
+ 1.day.ago.to_i
59
+ end
60
+
61
+ def where_hash
62
+ {:'period.from'.gte => yesterday, :'period.from'.lte => max_asap}
63
+ end
64
+
65
+ def exactly
66
+ [{:'time_period.flex.from' => period}, {:'time_period.flex.to' => period}]
67
+ end
68
+
69
+ def in_between
70
+ [{:'time_period.flex.from' => min_period}, {:'time_period.flex.to' => max_period}]
71
+ end
72
+
73
+ def at_least type = :min
74
+ {:'time_period.flex.to'.gte => send("#{type}_period") }
75
+ end
76
+
77
+ def at_most type = :max
78
+ {:'time_period.flex.to'.lte => send("#{type}_period") }
79
+ end
80
+
81
+ describe 'ASAP 2 minutes' do
82
+ let(:period) { 2.minutes.to_i }
83
+
84
+ let(:criteria) do
85
+ Account.where(where_hash).or(exactly)
86
+ end
87
+
88
+ # it 'should have a nice criteria' do
89
+ # criteria.selector['$or'].should == [{"time_period.flex.from"=>120}, {"time_period.flex.to"=>120}]
90
+ # criteria.selector["period.from"].should be_a Hash
91
+ # end
92
+
93
+ it 'should find #1, #2, #3, #4, #5' do
94
+ criteria.to_a.map(&:name).should include '1', '2', '5', '7'
95
+ end
96
+
97
+ let(:time_period) { criteria.first.time_period }
98
+
99
+ specify do
100
+ time_period.flex.should be_a DurationRange
101
+ time_period.flex.min.should == 2.minutes
102
+ time_period.flex.max.should == 5.minutes
103
+ end
104
+ end
105
+
106
+ describe 'ASAP 3 minutes' do
107
+ let(:period) { 3.minutes.to_i }
108
+
109
+ let(:criteria) do
110
+ Account.where(where_hash).or(exactly)
111
+ end
112
+
113
+ it 'should find #4, #10' do
114
+ # puts criteria.to_a.map(&:name)
115
+ criteria.to_a.map(&:name).should include '4', '10'
116
+ end
117
+ end
118
+
119
+ describe 'ASAP 4 minutes' do
120
+ let(:period) { 4.minutes.to_i }
121
+
122
+ let(:criteria) do
123
+ Account.where(where_hash).or(exactly)
124
+ end
125
+
126
+ it 'should find #2, #3' do
127
+ criteria.to_a.should include(@acc2, @acc3)
128
+ end
129
+ end
130
+
131
+ describe 'ASAP 1-4 minutes' do
132
+ let(:min_period) { 1.minute }
133
+ let(:max_period) { 4.minutes }
134
+
135
+ let(:criteria) do
136
+ Account.where(where_hash).or(in_between)
137
+ end
138
+
139
+ it 'should find #2, #3' do
140
+ # puts "ASAP 1-4 minutes"
141
+ # puts criteria.selector
142
+ # puts criteria.to_a.map(&:name)
143
+ criteria.to_a.map(&:name).should_not include('8', '9')
144
+ criteria.to_a.map(&:name).should include('2', '3')
145
+ end
146
+ end
147
+
148
+ describe 'ASAP 3-5 minutes' do
149
+ let(:min_period) { 3.minutes }
150
+ let(:max_period) { 5.minutes }
151
+
152
+ it 'should have a nice criteria' do
153
+ # puts "ASAP 3-5 minutes"
154
+ # puts criteria.selector
155
+ criteria.selector["time_period.flex.from"] = 180
156
+ criteria.selector["time_period.flex.to"] = 300
157
+ criteria.selector["period.from"].should be_a Hash
158
+ end
159
+
160
+ let(:criteria) do
161
+ Account.where(where_hash).or(in_between)
162
+ end
163
+
164
+ it 'should find #2, #3' do
165
+ # puts criteria.to_a.map(&:name)
166
+ criteria.to_a.map(&:name).should_not include('8', '9')
167
+ criteria.to_a.map(&:name).should include('10')
168
+ end
169
+ end
170
+
171
+ describe 'ASAP 5-7 minutes' do
172
+ let(:min_period) { 5.minutes }
173
+ let(:max_period) { 7.minutes }
174
+
175
+ let(:criteria) do
176
+ Account.where(where_hash).or(in_between)
177
+ end
178
+
179
+ it 'should find #2, #3' do
180
+ # puts criteria.to_a.map(&:name)
181
+ criteria.to_a.map(&:name).should_not include('6', '9', '10')
182
+ criteria.to_a.map(&:name).should include('8')
183
+ end
184
+ end
185
+
186
+ describe 'ASAP at least 4 minutes' do
187
+ let(:min_period) { 4.minutes }
188
+
189
+ let(:criteria) do
190
+ Account.where where_hash.merge(at_least)
191
+ end
192
+
193
+ it 'should find #2, #3' do
194
+ # puts criteria.selector
195
+ # puts criteria.to_a.map(&:name)
196
+ criteria.to_a.map(&:name).should_not include '9'
197
+ criteria.to_a.map(&:name).should include '1', '2', '3', '8', '10'
198
+ end
199
+ end
200
+
201
+ describe 'ASAP at most 3 minutes' do
202
+ let(:max_period) { 3.minutes }
203
+
204
+ let(:criteria) do
205
+ Account.where where_hash.merge(at_most)
206
+ end
207
+
208
+ it 'should find #2, #3' do
209
+ # puts criteria.selector
210
+ # puts criteria.to_a.map(&:name)
211
+ criteria.to_a.map(&:name).should_not include '8', '9', '10'
212
+ criteria.to_a.map(&:name).should include '4', '5', '7'
213
+ end
214
+ end
215
+ end
216
+ end
@@ -4,12 +4,15 @@ class Account
4
4
  include Mongoid::Document
5
5
  include Mongoid::Timespanned
6
6
 
7
+ field :name
7
8
  field :period, :type => ::Timespan, :between => true
8
9
 
9
10
  timespan_methods :period
10
11
 
11
12
  embeds_one :time_period
12
- timespan_container_delegates :time_period, :dates, :all #:start, :end
13
+
14
+ timespan_container_delegates :time_period, :dates, :all
15
+ timespan_container_delegates :time_period, :flex, :all
13
16
 
14
17
  def self.create_it! duration
15
18
  acc = self.new period: ::Timespan.new(duration: duration), time_period: ::TimePeriod.new
@@ -17,6 +20,12 @@ class Account
17
20
  acc
18
21
  end
19
22
 
23
+ # def to_s
24
+ # %Q{
25
+ # name: #{name}
26
+ # }
27
+ # end
28
+
20
29
  def self.between from, to
21
30
  Account.where(:'period.from'.gt => from.to_i, :'period.to'.lte => to.to_i)
22
31
  end
@@ -2,7 +2,8 @@ class TimePeriod
2
2
  include Mongoid::Document
3
3
  include Mongoid::Timespanned
4
4
 
5
- field :dates, :type => ::Timespan, :between => true
5
+ field :dates, :type => ::Timespan, :between => true
6
+ field :flex, :type => ::DurationRange
6
7
 
7
8
  embedded_in :account
8
9
 
data/timespan.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "timespan"
8
- s.version = "0.4.6"
8
+ s.version = "0.4.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kristian Mandrup"]
12
- s.date = "2012-09-24"
12
+ s.date = "2012-09-25"
13
13
  s.description = "Makes it easy to calculate time distance in different units"
14
14
  s.email = "kmandrup@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -28,6 +28,9 @@ Gem::Specification.new do |s|
28
28
  "config/locales/timespan/da.yml",
29
29
  "lib/timespan.rb",
30
30
  "lib/timespan/compare.rb",
31
+ "lib/timespan/core_ext.rb",
32
+ "lib/timespan/core_ext/hash.rb",
33
+ "lib/timespan/core_ext/range.rb",
31
34
  "lib/timespan/mongoid.rb",
32
35
  "lib/timespan/mongoid/mongoid_2x.rb",
33
36
  "lib/timespan/mongoid/mongoid_3x.rb",
@@ -38,8 +41,11 @@ Gem::Specification.new do |s|
38
41
  "lib/timespan/units.rb",
39
42
  "spec/spec_helper.rb",
40
43
  "spec/timespan/compare_spec.rb",
44
+ "spec/timespan/core_ext/duration_range_spec.rb",
45
+ "spec/timespan/core_ext/timespan_range_spec.rb",
41
46
  "spec/timespan/duration_macros_spec.rb",
42
47
  "spec/timespan/locales/duration_da.yml",
48
+ "spec/timespan/mongoid/advanced_search_spec.rb",
43
49
  "spec/timespan/mongoid/models/account_2x.rb",
44
50
  "spec/timespan/mongoid/models/account_3x.rb",
45
51
  "spec/timespan/mongoid/models/time_period.rb",
@@ -70,10 +76,12 @@ Gem::Specification.new do |s|
70
76
  s.add_runtime_dependency(%q<chronic_duration>, [">= 0"])
71
77
  s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0"])
72
78
  s.add_runtime_dependency(%q<spanner>, [">= 0"])
79
+ s.add_runtime_dependency(%q<sugar-high>, ["~> 0.7.2"])
73
80
  s.add_runtime_dependency(%q<xduration>, ["~> 2.2"])
74
81
  s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
75
82
  s.add_development_dependency(%q<rails>, ["~> 3.2.7"])
76
- s.add_development_dependency(%q<mongoid>, ["~> 3.0.0"])
83
+ s.add_development_dependency(%q<mongoid>, ["~> 3.0"])
84
+ s.add_development_dependency(%q<origin-selectable_ext>, ["~> 0.1.1"])
77
85
  s.add_development_dependency(%q<rdoc>, [">= 3.12"])
78
86
  s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
79
87
  s.add_development_dependency(%q<jeweler>, [">= 1.8.3"])
@@ -83,10 +91,12 @@ Gem::Specification.new do |s|
83
91
  s.add_dependency(%q<chronic_duration>, [">= 0"])
84
92
  s.add_dependency(%q<activesupport>, [">= 3.0.0"])
85
93
  s.add_dependency(%q<spanner>, [">= 0"])
94
+ s.add_dependency(%q<sugar-high>, ["~> 0.7.2"])
86
95
  s.add_dependency(%q<xduration>, ["~> 2.2"])
87
96
  s.add_dependency(%q<rspec>, [">= 2.8.0"])
88
97
  s.add_dependency(%q<rails>, ["~> 3.2.7"])
89
- s.add_dependency(%q<mongoid>, ["~> 3.0.0"])
98
+ s.add_dependency(%q<mongoid>, ["~> 3.0"])
99
+ s.add_dependency(%q<origin-selectable_ext>, ["~> 0.1.1"])
90
100
  s.add_dependency(%q<rdoc>, [">= 3.12"])
91
101
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
92
102
  s.add_dependency(%q<jeweler>, [">= 1.8.3"])
@@ -97,10 +107,12 @@ Gem::Specification.new do |s|
97
107
  s.add_dependency(%q<chronic_duration>, [">= 0"])
98
108
  s.add_dependency(%q<activesupport>, [">= 3.0.0"])
99
109
  s.add_dependency(%q<spanner>, [">= 0"])
110
+ s.add_dependency(%q<sugar-high>, ["~> 0.7.2"])
100
111
  s.add_dependency(%q<xduration>, ["~> 2.2"])
101
112
  s.add_dependency(%q<rspec>, [">= 2.8.0"])
102
113
  s.add_dependency(%q<rails>, ["~> 3.2.7"])
103
- s.add_dependency(%q<mongoid>, ["~> 3.0.0"])
114
+ s.add_dependency(%q<mongoid>, ["~> 3.0"])
115
+ s.add_dependency(%q<origin-selectable_ext>, ["~> 0.1.1"])
104
116
  s.add_dependency(%q<rdoc>, [">= 3.12"])
105
117
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
106
118
  s.add_dependency(%q<jeweler>, [">= 1.8.3"])
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timespan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.4.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-24 00:00:00.000000000 Z
12
+ date: 2012-09-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chronic
@@ -75,6 +75,22 @@ dependencies:
75
75
  - - ! '>='
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: sugar-high
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.7.2
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.7.2
78
94
  - !ruby/object:Gem::Dependency
79
95
  name: xduration
80
96
  requirement: !ruby/object:Gem::Requirement
@@ -130,7 +146,7 @@ dependencies:
130
146
  requirements:
131
147
  - - ~>
132
148
  - !ruby/object:Gem::Version
133
- version: 3.0.0
149
+ version: '3.0'
134
150
  type: :development
135
151
  prerelease: false
136
152
  version_requirements: !ruby/object:Gem::Requirement
@@ -138,7 +154,23 @@ dependencies:
138
154
  requirements:
139
155
  - - ~>
140
156
  - !ruby/object:Gem::Version
141
- version: 3.0.0
157
+ version: '3.0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: origin-selectable_ext
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ~>
164
+ - !ruby/object:Gem::Version
165
+ version: 0.1.1
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: 0.1.1
142
174
  - !ruby/object:Gem::Dependency
143
175
  name: rdoc
144
176
  requirement: !ruby/object:Gem::Requirement
@@ -222,6 +254,9 @@ files:
222
254
  - config/locales/timespan/da.yml
223
255
  - lib/timespan.rb
224
256
  - lib/timespan/compare.rb
257
+ - lib/timespan/core_ext.rb
258
+ - lib/timespan/core_ext/hash.rb
259
+ - lib/timespan/core_ext/range.rb
225
260
  - lib/timespan/mongoid.rb
226
261
  - lib/timespan/mongoid/mongoid_2x.rb
227
262
  - lib/timespan/mongoid/mongoid_3x.rb
@@ -232,8 +267,11 @@ files:
232
267
  - lib/timespan/units.rb
233
268
  - spec/spec_helper.rb
234
269
  - spec/timespan/compare_spec.rb
270
+ - spec/timespan/core_ext/duration_range_spec.rb
271
+ - spec/timespan/core_ext/timespan_range_spec.rb
235
272
  - spec/timespan/duration_macros_spec.rb
236
273
  - spec/timespan/locales/duration_da.yml
274
+ - spec/timespan/mongoid/advanced_search_spec.rb
237
275
  - spec/timespan/mongoid/models/account_2x.rb
238
276
  - spec/timespan/mongoid/models/account_3x.rb
239
277
  - spec/timespan/mongoid/models/time_period.rb
@@ -264,7 +302,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
264
302
  version: '0'
265
303
  segments:
266
304
  - 0
267
- hash: -1422999725425630205
305
+ hash: -4405468294374256560
268
306
  required_rubygems_version: !ruby/object:Gem::Requirement
269
307
  none: false
270
308
  requirements: