days_and_times 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +7 -0
- data/License.txt +20 -0
- data/Manifest.txt +13 -0
- data/README.txt +72 -0
- data/Rakefile +9 -0
- data/lib/days_and_times.rb +8 -0
- data/lib/days_and_times/duration.rb +367 -0
- data/lib/days_and_times/numeric.rb +48 -0
- data/lib/days_and_times/object.rb +19 -0
- data/lib/days_and_times/time.rb +137 -0
- data/spec/days_and_times/numeric_spec.rb +9 -0
- data/spec/days_and_times_spec.rb +80 -0
- data/spec/spec_helper.rb +1 -0
- metadata +102 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
Binary file
|
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 BehindLogic
|
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/Manifest.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
lib/days_and_times.rb
|
7
|
+
lib/days_and_times/duration.rb
|
8
|
+
lib/days_and_times/numeric.rb
|
9
|
+
lib/days_and_times/object.rb
|
10
|
+
lib/days_and_times/time.rb
|
11
|
+
spec/days_and_times/numeric_spec.rb
|
12
|
+
spec/days_and_times_spec.rb
|
13
|
+
spec/spec_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= days_and_times
|
2
|
+
|
3
|
+
* Homepage: http://dcparker.github.com/days_and_times
|
4
|
+
* Code: http://github.com/dcparker/days_and_times
|
5
|
+
* Download: http://gemcutter.com/gems/days_and_times
|
6
|
+
|
7
|
+
== DESCRIPTION:
|
8
|
+
|
9
|
+
Natural language method chaining for Time, Durations and the like.
|
10
|
+
|
11
|
+
== FEATURES:
|
12
|
+
|
13
|
+
* Singular and plural of the concrete time units (second, minute, hour, day, week) are added to the Numeric object.
|
14
|
+
* Use the Duration class to do lots of cool things.
|
15
|
+
* Perform mathematical operations between numbers and durations, durations and durations, and durations and numbers, and the 'unit' will be respected as expected in the algebraic rules.
|
16
|
+
* Iterate over Duration objects, by the time-unit the duration was instantiated with, or a unit of choice.
|
17
|
+
* Durations can be 'anchored' to a begin or end time.
|
18
|
+
|
19
|
+
== PROBLEMS:
|
20
|
+
|
21
|
+
* Months are variable and do not lend themselves to concrete mathematics, so as of now they are not implemented.
|
22
|
+
|
23
|
+
== SYNOPSIS:
|
24
|
+
|
25
|
+
1.day #=> A duration of 1 day
|
26
|
+
7.days #=> A duration of 7 days
|
27
|
+
1.week #=> A duration of 1 week
|
28
|
+
1.week - 2.days #=> A duration of 5 days
|
29
|
+
1.week.from(Now()) #=> The time of 1 week from this moment
|
30
|
+
1.week.from(Today()) #=> The time of 1 week from the beginning of today
|
31
|
+
3.minutes.ago.until(7.minutes.from(Now())) #=> duration 3 minutes ago to 7 minutes from now
|
32
|
+
3.minutes.ago.until(7.minutes.from(Now())) - 2.minutes #=> duration 3 minutes ago to 5 minutes from now
|
33
|
+
4.weeks.from(2.days.from(Now())).until(8.weeks.from(Yesterday())) #=> A duration, starting in 4 weeks and 2 days, and ending 8 weeks from yesterday
|
34
|
+
1.week - 1.second #=> A duration of 6 days, 23 hours, 59 minutes, and 59 seconds
|
35
|
+
4.weeks / 2 #=> A duration of 2 weeks
|
36
|
+
4.weeks / 2.weeks #=> The integer 2
|
37
|
+
8.weeks.each {|week| ...} #=> Runs code for each week contained in the duration (of 8 weeks)
|
38
|
+
8.weeks.starting(Now()).each {|week| ...} #=> Runs code for each week in the duration, but each week is also anchored to a starting time, in sequence through the duration.
|
39
|
+
1.week.each {|week| ...} #=> Automatically chooses week as its iterator
|
40
|
+
7.days.each {|day| ...} #=> Automatically chooses day as its iterator
|
41
|
+
1.week.each_day {|day| ...} #=> Forcing the week to iterate through days
|
42
|
+
1.week.each(10.hours) {|ten_hour_segment| ...} #=> Using a custom iterator of 10 hours. There would be 17 of them, but notice that the last iteration will only be 8 hours.
|
43
|
+
# ... and more!
|
44
|
+
|
45
|
+
== INSTALL:
|
46
|
+
|
47
|
+
gem install days_and_times -s http://gemcutter.com
|
48
|
+
|
49
|
+
== LICENSE:
|
50
|
+
|
51
|
+
(The MIT License)
|
52
|
+
|
53
|
+
Copyright (c) 2008 BehindLogic
|
54
|
+
|
55
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
56
|
+
a copy of this software and associated documentation files (the
|
57
|
+
'Software'), to deal in the Software without restriction, including
|
58
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
59
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
60
|
+
permit persons to whom the Software is furnished to do so, subject to
|
61
|
+
the following conditions:
|
62
|
+
|
63
|
+
The above copyright notice and this permission notice shall be
|
64
|
+
included in all copies or substantial portions of the Software.
|
65
|
+
|
66
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
67
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
68
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
69
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
70
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
71
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
72
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,367 @@
|
|
1
|
+
require 'time'
|
2
|
+
class Duration
|
3
|
+
# Length is the length of the time span, in seconds
|
4
|
+
# Unit is a length of time (in seconds) to use in collection methods
|
5
|
+
# StartTime is an optional attribute that can 'anchor' a duration
|
6
|
+
# to a specific real time.
|
7
|
+
attr_accessor :length, :unit, :start_time
|
8
|
+
def initialize(count=0,unit=1,start_time=nil,auto_klass={})
|
9
|
+
if unit.is_a?(Time) || unit.is_a?(DateTime)
|
10
|
+
start_time = unit
|
11
|
+
unit = 1
|
12
|
+
end
|
13
|
+
options = {:count => count || 0, :unit => unit || 1, :start_time => start_time}.merge(count.is_a?(Hash) ? count : {})
|
14
|
+
|
15
|
+
@unit = options[:unit]
|
16
|
+
@length = (@unit * options[:count].to_f).round
|
17
|
+
@start_time = options[:start_time]
|
18
|
+
end
|
19
|
+
def self.new(*args)
|
20
|
+
a = super
|
21
|
+
if self.name == 'Duration' && (args.last.is_a?(Hash) ? args.last[:auto_class] == true : true)
|
22
|
+
a.send(:auto_class)
|
23
|
+
else
|
24
|
+
a
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def self.length
|
28
|
+
1
|
29
|
+
end
|
30
|
+
|
31
|
+
# * * * * * * * * * * * * * * * *
|
32
|
+
# A Duration is a LENGTH of Time
|
33
|
+
# -This is measured in seconds,
|
34
|
+
# but set in terms of the unit
|
35
|
+
# currently being used.
|
36
|
+
def length=(value)
|
37
|
+
if value.respond_to?(:to_f)
|
38
|
+
@length = (self.unit * value.to_f).round
|
39
|
+
else
|
40
|
+
raise TypeError, "Can't set a Duration's length to a #{value.class.name} object."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def length
|
44
|
+
length = @length.to_f / self.unit
|
45
|
+
length.to_i == length ? length.to_i : length
|
46
|
+
end
|
47
|
+
def abs_length=(value)
|
48
|
+
if value.respond_to?(:to_i)
|
49
|
+
@length = value.to_i
|
50
|
+
else
|
51
|
+
raise TypeError, "Can't set a Duration's length to a #{value.class.name} object."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def abs_length
|
55
|
+
@length
|
56
|
+
end
|
57
|
+
def to_i
|
58
|
+
self.abs_length.to_i
|
59
|
+
end
|
60
|
+
def to_f
|
61
|
+
self.abs_length.to_f
|
62
|
+
end
|
63
|
+
def to_s
|
64
|
+
"#{self.length} #{self.class.name}"
|
65
|
+
end
|
66
|
+
def coerce(*args)
|
67
|
+
to_f.coerce(*args)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ===(other)
|
71
|
+
self.to_f == other.to_f
|
72
|
+
end
|
73
|
+
def inspect
|
74
|
+
"#<#{self.class.name}:#{self.object_id} (length=#{self.length.inspect}) #{self.instance_variables.reject {|d| d=='@length' || self.instance_variable_get(d).nil?}.collect {|iv| "#{iv}=#{self.instance_variable_get(iv).inspect}"}.join(' ')}>"
|
75
|
+
end
|
76
|
+
# * * * * * * * * * * * * * * * *
|
77
|
+
|
78
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
79
|
+
# A Duration's calculations utilize a UNIT
|
80
|
+
# -This is stored as the number of
|
81
|
+
# seconds equal to the unit's value.
|
82
|
+
def unit=(value)
|
83
|
+
if value.respond_to?(:to_i)
|
84
|
+
@unit = value.to_i
|
85
|
+
else
|
86
|
+
raise TypeError, "Can't set a Duration's unit to a #{value.class.name} object."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
def -(value)
|
90
|
+
if value.respond_to?(:to_i)
|
91
|
+
auto_class(Duration.new(@length - value.to_i))
|
92
|
+
else
|
93
|
+
raise TypeError, "Can't convert #{value.class.name} to an integer."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
def +(value)
|
97
|
+
if value.respond_to?(:to_i)
|
98
|
+
auto_class(Duration.new(@length + value.to_i))
|
99
|
+
else
|
100
|
+
raise TypeError, "Can't convert #{value.class.name} to an integer."
|
101
|
+
end
|
102
|
+
end
|
103
|
+
def *(value)
|
104
|
+
if value.is_a?(Duration)
|
105
|
+
@length * value.length * value.unit
|
106
|
+
elsif value.respond_to?(:to_i)
|
107
|
+
auto_class(Duration.new(@length * value))
|
108
|
+
else
|
109
|
+
raise TypeError, "Can't convert #{value.class.name} to an integer."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
def /(value)
|
113
|
+
if value.is_a?(Duration)
|
114
|
+
@length / (value.length * value.unit)
|
115
|
+
elsif value.respond_to?(:to_i)
|
116
|
+
auto_class(Duration.new(@length / value))
|
117
|
+
else
|
118
|
+
raise TypeError, "Can't convert #{value.class.name} to an integer."
|
119
|
+
end
|
120
|
+
end
|
121
|
+
def in_weeks
|
122
|
+
self.unit = Week.length
|
123
|
+
auto_class(self)
|
124
|
+
end
|
125
|
+
def in_days
|
126
|
+
self.unit = Day.length
|
127
|
+
auto_class(self)
|
128
|
+
end
|
129
|
+
def in_hours
|
130
|
+
self.unit = Hour.length
|
131
|
+
auto_class(self)
|
132
|
+
end
|
133
|
+
def in_minutes
|
134
|
+
self.unit = Minute.length
|
135
|
+
auto_class(self)
|
136
|
+
end
|
137
|
+
def in_seconds
|
138
|
+
self.unit = Second.length
|
139
|
+
auto_class(self)
|
140
|
+
end
|
141
|
+
def weeks
|
142
|
+
@length.to_f / Week.length
|
143
|
+
end
|
144
|
+
def days
|
145
|
+
@length.to_f / Day.length
|
146
|
+
end
|
147
|
+
def hours
|
148
|
+
@length.to_f / Hour.length
|
149
|
+
end
|
150
|
+
def minutes
|
151
|
+
@length.to_f / Minute.length
|
152
|
+
end
|
153
|
+
def seconds
|
154
|
+
@length.to_f / Second.length
|
155
|
+
end
|
156
|
+
# * * * * * * * * * * * * * * * * * * * * *
|
157
|
+
|
158
|
+
# * * * * * * * * * * * * * * * * * * * * * * *
|
159
|
+
# A Duration can be 'anchored' to a START_TIME
|
160
|
+
# -This start_time is a Time object
|
161
|
+
def start_time=(value)
|
162
|
+
if value.is_a?(Time) || value.is_a?(DateTime)
|
163
|
+
@start_time = value.to_time
|
164
|
+
else
|
165
|
+
raise TypeError, "A Duration's start_time must be a Time or DateTime object."
|
166
|
+
end
|
167
|
+
end
|
168
|
+
def end_time=(value)
|
169
|
+
if value.is_a?(Time) || value.is_a?(DateTime)
|
170
|
+
@start_time = value.to_time - self #Subtracts this duration from the end_time to get the start_time
|
171
|
+
else
|
172
|
+
raise TypeError, "A Duration's end_time must be a Time or DateTime object."
|
173
|
+
end
|
174
|
+
end
|
175
|
+
def end_time
|
176
|
+
@start_time + self
|
177
|
+
end
|
178
|
+
def anchored?
|
179
|
+
!self.start_time.nil?
|
180
|
+
end
|
181
|
+
# * * * * * * * * * * * * * * * * * * * * * * *
|
182
|
+
|
183
|
+
# * * * * * * * * * * * * * * * * * * * * * * * *
|
184
|
+
# Calculations using Duration as an intermediate
|
185
|
+
def from(time)
|
186
|
+
time + @length
|
187
|
+
end
|
188
|
+
def before(time)
|
189
|
+
time - @length
|
190
|
+
end
|
191
|
+
def from_now
|
192
|
+
self.from(Time.now)
|
193
|
+
end
|
194
|
+
def ago
|
195
|
+
self.before(Time.now)
|
196
|
+
end
|
197
|
+
def starting(time)
|
198
|
+
self.start_time = time
|
199
|
+
self
|
200
|
+
end
|
201
|
+
def ending(time)
|
202
|
+
self.end_time = time
|
203
|
+
self
|
204
|
+
end
|
205
|
+
# * * * * * * * * * * * * * * * * * * * * * * * *
|
206
|
+
|
207
|
+
# * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
208
|
+
# A Duration can be treated as a 'collection' of units
|
209
|
+
def each_week(&block)
|
210
|
+
self.each(Week.length,&block)
|
211
|
+
end
|
212
|
+
def each_day(&block)
|
213
|
+
self.each(Day.length,&block)
|
214
|
+
end
|
215
|
+
def each_hour(&block)
|
216
|
+
self.each(Hour.length,&block)
|
217
|
+
end
|
218
|
+
def each_minute(&block)
|
219
|
+
self.each(Minute.length,&block)
|
220
|
+
end
|
221
|
+
def each_second(&block)
|
222
|
+
self.each(Second.length,&block)
|
223
|
+
end
|
224
|
+
def collect(use_unit=self.class.length,&block)
|
225
|
+
ary = []
|
226
|
+
self.each(use_unit) do |x|
|
227
|
+
ary << (block_given? ? yield(x) : x)
|
228
|
+
end
|
229
|
+
ary
|
230
|
+
end
|
231
|
+
def each(use_unit=self.class.length)
|
232
|
+
remainder = @length.to_f % use_unit
|
233
|
+
ret = []
|
234
|
+
if self.start_time.nil?
|
235
|
+
(@length.to_f / use_unit).to_i.times do |i|
|
236
|
+
ret << Duration.new(1, use_unit)
|
237
|
+
yield(ret[-1])
|
238
|
+
end
|
239
|
+
else
|
240
|
+
(@length.to_f / use_unit).to_i.times do |i|
|
241
|
+
ret << Duration.new(1, use_unit, (self.start_time + (use_unit * i)))
|
242
|
+
yield(ret[-1])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
if remainder > 0
|
246
|
+
ret << (self.start_time.nil? ? Duration.new(remainder, 1) : Duration.new(remainder, 1, (self.start_time + @length - remainder)))
|
247
|
+
yield(ret[-1])
|
248
|
+
end
|
249
|
+
ret
|
250
|
+
end
|
251
|
+
# * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
252
|
+
|
253
|
+
# * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
254
|
+
# Through some ingenious metacoding (see 'def bind_object_method') below,
|
255
|
+
# it is possible to create a new method on a Duration object
|
256
|
+
# to a method on another object, in order to gather information
|
257
|
+
# based on the duration mentioned.
|
258
|
+
def create_find_within_method_for(other, method_name, other_method_name)
|
259
|
+
self.bind_object_method(other, method_name, other_method_name, [[], ['self.start_time', 'self.end_time']])
|
260
|
+
end
|
261
|
+
def self.create_find_within_method_for(other, method_name, other_method_name)
|
262
|
+
self.bind_class_object_method(other, method_name, other_method_name, [[], ['self.start_time', 'self.end_time']])
|
263
|
+
end
|
264
|
+
# * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
265
|
+
|
266
|
+
def method_missing(method_name, *args)
|
267
|
+
# Delegate any missing methods to the start_time Time object, if we have a start_time and the method exists there.
|
268
|
+
return self.start_time.send(method_name, *args) if self.anchored? && self.start_time.respond_to?(method_name)
|
269
|
+
super
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
def auto_class(obj=self)
|
274
|
+
new_obj = case obj.unit
|
275
|
+
when 1
|
276
|
+
obj.class.name == 'Seconds' ? obj : (obj.length == 1 ? Second.new(obj.start_time) : Seconds.new(obj.length,obj.start_time))
|
277
|
+
when 60
|
278
|
+
obj.class.name == 'Minutes' ? obj : (obj.length == 1 ? Minute.new(obj.start_time) : Minutes.new(obj.length,obj.start_time))
|
279
|
+
when 3600
|
280
|
+
obj.class.name == 'Hours' ? obj : (obj.length == 1 ? Hour.new(obj.start_time) : Hours.new(obj.length,obj.start_time))
|
281
|
+
when 86400
|
282
|
+
obj.class.name == 'Days' ? obj : (obj.length == 1 ? Day.new(obj.start_time) : Days.new(obj.length,obj.start_time))
|
283
|
+
when 604800
|
284
|
+
obj.class.name == 'Weeks' ? obj : (obj.length == 1 ? Week.new(obj.start_time) : Weeks.new(obj.length,obj.start_time))
|
285
|
+
else
|
286
|
+
obj.class.name == 'Duration' ? obj : Duration.new(obj.length,obj.unit,obj.start_time,{:auto_class => false})
|
287
|
+
end
|
288
|
+
# Now, auto-transform class if possible:
|
289
|
+
case
|
290
|
+
when !['Weeks', 'Week'].include?(new_obj.class.name) && new_obj.to_i.remainder(Week.length) == 0
|
291
|
+
new_obj.to_i == Week.length ? Week.new(new_obj.start_time) : Weeks.new(new_obj.to_f / Week.length,new_obj.start_time)
|
292
|
+
when !['Weeks', 'Week', 'Days', 'Day'].include?(new_obj.class.name) && new_obj.to_i.remainder(Day.length) == 0
|
293
|
+
new_obj.to_i == Day.length ? Day.new(new_obj.start_time) : Days.new(new_obj.to_f / Day.length,new_obj.start_time)
|
294
|
+
when !['Weeks', 'Week', 'Days', 'Day', 'Hours', 'Hour'].include?(new_obj.class.name) && new_obj.to_i.remainder(Hour.length) == 0
|
295
|
+
new_obj.to_i == Hour.length ? Hour.new(new_obj.start_time) : Hours.new(new_obj.to_f / Hour.length,new_obj.start_time)
|
296
|
+
when !['Weeks', 'Week', 'Days', 'Day', 'Hours', 'Hour', 'Minutes', 'Minute'].include?(new_obj.class.name) && new_obj.to_f.remainder(Minute.length) == 0
|
297
|
+
new_obj.to_i == Minute.length ? Minute.new(new_obj.start_time) : Minutes.new(new_obj.to_f / Minute.length,new_obj.start_time)
|
298
|
+
else
|
299
|
+
new_obj
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
class Weeks < Duration
|
304
|
+
def initialize(count=1,start_time=nil)
|
305
|
+
super(count,Week.length,start_time)
|
306
|
+
end
|
307
|
+
def self.length
|
308
|
+
604800
|
309
|
+
end
|
310
|
+
end
|
311
|
+
class Week < Weeks
|
312
|
+
def initialize(start_time=nil)
|
313
|
+
super(1,start_time)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
class Days < Duration
|
317
|
+
def initialize(count=1,start_time=nil)
|
318
|
+
super(count,Day.length,start_time)
|
319
|
+
end
|
320
|
+
def self.length
|
321
|
+
86400
|
322
|
+
end
|
323
|
+
end
|
324
|
+
class Day < Days
|
325
|
+
def initialize(start_time=nil)
|
326
|
+
super(1,start_time)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
class Hours < Duration
|
330
|
+
def initialize(count=1,start_time=nil)
|
331
|
+
super(count,Hour.length,start_time)
|
332
|
+
end
|
333
|
+
def self.length
|
334
|
+
3600
|
335
|
+
end
|
336
|
+
end
|
337
|
+
class Hour < Hours
|
338
|
+
def initialize(start_time=nil)
|
339
|
+
super(1,start_time)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
class Minutes < Duration
|
343
|
+
def initialize(count=1,start_time=nil)
|
344
|
+
super(count,Minute.length,start_time)
|
345
|
+
end
|
346
|
+
def self.length
|
347
|
+
60
|
348
|
+
end
|
349
|
+
end
|
350
|
+
class Minute < Minutes
|
351
|
+
def initialize(start_time=nil)
|
352
|
+
super(1,start_time)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
class Seconds < Duration
|
356
|
+
def initialize(count=1,start_time=nil)
|
357
|
+
super(count,Second.length,start_time)
|
358
|
+
end
|
359
|
+
def self.length
|
360
|
+
1
|
361
|
+
end
|
362
|
+
end
|
363
|
+
class Second < Seconds
|
364
|
+
def initialize(start_time=nil)
|
365
|
+
super(1,start_time)
|
366
|
+
end
|
367
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'days_and_times/duration'
|
2
|
+
class Numeric
|
3
|
+
def weeks
|
4
|
+
self == 1 ? Week.new : Weeks.new(self)
|
5
|
+
end
|
6
|
+
def week
|
7
|
+
self.weeks
|
8
|
+
end
|
9
|
+
|
10
|
+
def days
|
11
|
+
self == 1 ? Day.new : Days.new(self)
|
12
|
+
end
|
13
|
+
def day
|
14
|
+
self.days
|
15
|
+
end
|
16
|
+
|
17
|
+
def hours
|
18
|
+
self == 1 ? Hour.new : Hours.new(self)
|
19
|
+
end
|
20
|
+
def hour
|
21
|
+
self.hours
|
22
|
+
end
|
23
|
+
|
24
|
+
def minutes
|
25
|
+
self == 1 ? Minute.new : Minutes.new(self)
|
26
|
+
end
|
27
|
+
def minute
|
28
|
+
self.minutes
|
29
|
+
end
|
30
|
+
|
31
|
+
def seconds
|
32
|
+
self == 1 ? Second.new : Seconds.new(self)
|
33
|
+
end
|
34
|
+
def second
|
35
|
+
self.seconds
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_multiple_of?(num)
|
39
|
+
self % num == 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def am
|
43
|
+
Time.parse("#{self}:00:00")
|
44
|
+
end
|
45
|
+
def pm
|
46
|
+
Time.parse("#{self+12}:00:00")
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Object
|
2
|
+
def bind_class_object_method(other, self_method_name, other_method_name, args=[[],[]])
|
3
|
+
# Since I can't pass the 'other' object into eval as a string, I have to
|
4
|
+
# set a class instance variable and copy the contents to a class variable
|
5
|
+
# so that the generated method will play nicely with subclasses.
|
6
|
+
self.instance_variable_set("@#{self_method_name.to_s}_OBJ", other)
|
7
|
+
self.send :eval, "@@#{self_method_name.to_s}_OBJ = @#{self_method_name.to_s}_OBJ
|
8
|
+
def #{self_method_name.to_s}(#{args[0].join(', ')})
|
9
|
+
@@#{self_method_name.to_s}_OBJ.#{other_method_name.to_s}(#{args[1].join(', ')})
|
10
|
+
end"
|
11
|
+
self
|
12
|
+
end
|
13
|
+
def bind_object_method(other, self_method_name, other_method_name, args=[[],[]])
|
14
|
+
self.instance_variable_set("@#{self_method_name}_OBJ", other)
|
15
|
+
eval "def self.#{self_method_name}(#{args[0].join(', ')})
|
16
|
+
@#{self_method_name}_OBJ.#{other_method_name}(#{args[1].join(', ')})
|
17
|
+
end"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'time'
|
2
|
+
class Time
|
3
|
+
def to_time
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
|
8
|
+
# (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
|
9
|
+
# minute is passed, then sec and usec is set to 0.
|
10
|
+
def change(options)
|
11
|
+
::Time.send(
|
12
|
+
self.utc? ? :utc_time : :local_time,
|
13
|
+
options[:year] || self.year,
|
14
|
+
options[:month] || self.month,
|
15
|
+
options[:day] || options[:mday] || self.day, # mday is deprecated
|
16
|
+
options[:hour] || self.hour,
|
17
|
+
options[:min] || (options[:hour] ? 0 : self.min),
|
18
|
+
options[:sec] || ((options[:hour] || options[:min]) ? 0 : self.sec),
|
19
|
+
options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : self.usec)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def day_name
|
24
|
+
self.strftime("%A")
|
25
|
+
end
|
26
|
+
def month_name
|
27
|
+
self.strftime("%B")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Seconds since midnight: Time.now.seconds_since_midnight
|
31
|
+
def seconds_since_midnight
|
32
|
+
self.to_i - self.change(:hour => 0).to_i + (self.usec/1.0e+6)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a new Time representing the start of the day (0:00)
|
36
|
+
def beginning_of_day
|
37
|
+
(self - self.seconds_since_midnight).change(:usec => 0)
|
38
|
+
end
|
39
|
+
alias :midnight :beginning_of_day
|
40
|
+
alias :at_midnight :beginning_of_day
|
41
|
+
alias :at_beginning_of_day :beginning_of_day
|
42
|
+
|
43
|
+
# Returns a new Time if requested year can be accomodated by Ruby's Time class
|
44
|
+
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
|
45
|
+
# otherwise returns a DateTime
|
46
|
+
def self.time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
|
47
|
+
::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
|
48
|
+
rescue
|
49
|
+
offset = if utc_or_local.to_sym == :utc then 0 else ::DateTime.now.offset end
|
50
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset, 0)
|
51
|
+
end
|
52
|
+
|
53
|
+
# wraps class method time_with_datetime_fallback with utc_or_local == :utc
|
54
|
+
def self.utc_time(*args)
|
55
|
+
time_with_datetime_fallback(:utc, *args)
|
56
|
+
end
|
57
|
+
|
58
|
+
# wraps class method time_with_datetime_fallback with utc_or_local == :local
|
59
|
+
def self.local_time(*args)
|
60
|
+
time_with_datetime_fallback(:local, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.tomorrow
|
64
|
+
Time.now.beginning_of_day + 1.day
|
65
|
+
end
|
66
|
+
def tomorrow
|
67
|
+
self.beginning_of_day + 1.day
|
68
|
+
end
|
69
|
+
def self.yesterday
|
70
|
+
Time.now.beginning_of_day - 1.day
|
71
|
+
end
|
72
|
+
def yesterday
|
73
|
+
self.beginning_of_day - 1.day
|
74
|
+
end
|
75
|
+
def self.today
|
76
|
+
Time.now.beginning_of_day
|
77
|
+
end
|
78
|
+
def self.next_month
|
79
|
+
today.change(:day => 1, :month => today.month + 1)
|
80
|
+
end
|
81
|
+
|
82
|
+
def until(end_time)
|
83
|
+
Duration.new(end_time - self, self)
|
84
|
+
end
|
85
|
+
def through(duration)
|
86
|
+
self.until(duration)
|
87
|
+
end
|
88
|
+
def for(duration)
|
89
|
+
raise TypeError, "must be a Duration object." unless duration.is_a?(Duration)
|
90
|
+
duration.start_time = self
|
91
|
+
duration
|
92
|
+
end
|
93
|
+
def is_today?
|
94
|
+
self.beginning_of_day == Time.today
|
95
|
+
end
|
96
|
+
def strfsql
|
97
|
+
self.strftime("%Y-#{self.strftime("%m").to_i.to_s}-#{self.strftime("%d").to_i.to_s}")
|
98
|
+
end
|
99
|
+
def self.from_tzid(tzid) #We aren't handling the Time Zone part here...
|
100
|
+
if tzid =~ /(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z/ # yyyymmddThhmmss
|
101
|
+
Time.xmlschema("#{$1}-#{$2}-#{$3}T#{$4}:#{$5}:#{$6}")
|
102
|
+
else
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
def humanize_time
|
107
|
+
self.strftime("%M").to_i > 0 ? self.strftime("#{self.strftime("%I").to_i.to_s}:%M%p").downcase : self.strftime("#{self.strftime("%I").to_i.to_s}%p").downcase
|
108
|
+
end
|
109
|
+
def humanize_date(length_profile='medium') #There may be decent reason to change how this works entirely...
|
110
|
+
case length_profile
|
111
|
+
when 'abbr' || 'abbreviated'
|
112
|
+
self.strftime("%m/%d/%y")
|
113
|
+
when 'short'
|
114
|
+
self.strftime("%b #{self.strftime("%d").to_i.to_s}")
|
115
|
+
when 'medium'
|
116
|
+
self.strftime("%B #{self.strftime("%d").to_i.to_s}")
|
117
|
+
when 'long'
|
118
|
+
self.strftime("%B #{self.strftime("%d").to_i.to_s}, %Y")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
def humanize_date_time
|
122
|
+
self.humanize_date + ' ' + self.humanize_time
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def Today
|
127
|
+
Time.today
|
128
|
+
end
|
129
|
+
def Yesterday
|
130
|
+
Time.yesterday
|
131
|
+
end
|
132
|
+
def Tomorrow
|
133
|
+
Time.tomorrow
|
134
|
+
end
|
135
|
+
def Now
|
136
|
+
Time.now
|
137
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Numeric do
|
4
|
+
it "should generate am and pm times" do
|
5
|
+
3.am.to_s.should eql(Time.parse("3:00:00").to_s)
|
6
|
+
3.pm.to_s.should eql(Time.parse("15:00:00").to_s)
|
7
|
+
19.am.to_s.should eql(Time.parse("19:00:00").to_s)
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Duration do
|
4
|
+
it "should be backward-compatible with previous english time statements" do
|
5
|
+
10.minutes.from_now.inspect.should eql((Time.now + 600).inspect)
|
6
|
+
3.days.ago.inspect.should eql((Time.now - 3*24*60*60).inspect)
|
7
|
+
# More english statements need to be written!!
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should store a duration correctly in minutes if told to do so, even when created in seconds" do
|
11
|
+
132.seconds.in_minutes.should be_is_a(Minutes)
|
12
|
+
132.seconds.in_minutes.unit.should eql(60)
|
13
|
+
132.seconds.in_minutes.length.to_s.should eql('2.2')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should add a duration properly to a Time object" do
|
17
|
+
(Time.now+1.day).inspect.should eql((Time.now+86400).inspect)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should properly represent time in string, depending on the unit" do
|
21
|
+
23.seconds.to_s.should eql("23 Seconds")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should properly perform mathmatical operations, yet considering the unit" do
|
25
|
+
(2.days / 2 === 1.day).should eql(true)
|
26
|
+
(2.days / 2).in_days.length.should eql(1.day.length)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should automatically map to Day, Minute, Hour, etc if the length matches" do
|
30
|
+
(1.week - 6.days).class.name.should eql('Day')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should render durations properly" do
|
34
|
+
1.day.should === Duration.new(1, 86400) #=> A duration of 1 day
|
35
|
+
7.days.should === Duration.new(7, 86400) #=> A duration of 7 days
|
36
|
+
1.week.should === Duration.new(1, 604800) #=> A duration of 1 week
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should perform mathematics on unanchored durations" do
|
40
|
+
(1.week - 2.days).should === Duration.new(5, 86400) #=> A duration of 5 days
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should perform mathematics on an anchored duration" do
|
44
|
+
(3.minutes.ago.until(7.minutes.from(Now())) - 2.minutes).should === Duration.new(8, 60) #=> duration 3 minutes ago to 5 minutes from now
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should generate durations from numeric and time arguments" do
|
48
|
+
1.week.from(Now()).to_s.should eql((Time.now + 604800).to_s) #=> The time of 1 week from this moment
|
49
|
+
1.week.from(Today()).should eql((Time.now + 604800).beginning_of_day) #=> The time of 1 week from the beginning of today
|
50
|
+
3.minutes.ago.until(7.minutes.from(Now())).should === Duration.new(10, 60) #=> duration 3 minutes ago to 7 minutes from now
|
51
|
+
4.weeks.from(2.days.from(Now())).until(8.weeks.from(Yesterday())) #=> A duration, starting in 4 weeks and 2 days, and ending 8 weeks from yesterday
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should perform cross-unit calculations" do
|
55
|
+
1.week - 1.second #=> A duration of 6 days, 23 hours, 59 minutes, and 59 seconds
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should perform algebraic calculations respecting the unit" do
|
59
|
+
4.weeks / 2 #=> A duration of 2 weeks
|
60
|
+
4.weeks / 2.weeks #=> The integer 2
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should perform iterations respecting the default unit and custom units" do
|
64
|
+
8.weeks.each {|week|
|
65
|
+
week.should === Duration.new(1, 604800)
|
66
|
+
} #=> Runs code for each week contained in the duration (of 8 weeks)
|
67
|
+
8.weeks.starting(Now()).each {|week| } #=> Runs code for each week in the duration, but each week is also anchored to a starting time, in sequence through the duration.
|
68
|
+
1.week.each {|week| } #=> Automatically chooses week as its iterator
|
69
|
+
7.days.each {|day| } #=> Automatically chooses day as its iterator
|
70
|
+
1.week.each_day {|day| } #=> Forcing the week to iterate through days
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should perform iterations with a remainder that also runs" do
|
74
|
+
count = 0
|
75
|
+
1.week.each(10.hours.to_f) {|ten_hour_segment|
|
76
|
+
count += 1
|
77
|
+
ten_hour_segment.should === Duration.new(10, 3600) unless count == 17
|
78
|
+
}[-1].should === Duration.new(8, 3600) #=> Using a custom iterator of 10 hours. There would be 17 of them, but notice that the last iteration will only be 8 hours.
|
79
|
+
end
|
80
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/days_and_times'
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: days_and_times
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Parker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMQ0wCwYDVQQDDARnZW1z
|
14
|
+
MRswGQYKCZImiZPyLGQBGRYLYmVoaW5kbG9naWMxEzARBgoJkiaJk/IsZAEZFgNj
|
15
|
+
b20wHhcNMDkxMTE5MDQ0NzIzWhcNMTAxMTE5MDQ0NzIzWjBBMQ0wCwYDVQQDDARn
|
16
|
+
ZW1zMRswGQYKCZImiZPyLGQBGRYLYmVoaW5kbG9naWMxEzARBgoJkiaJk/IsZAEZ
|
17
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDs+2PvSoBC5mCz
|
18
|
+
Nm95RXKOb/u+CmPA70DoG9cF0zipCie003Vm0DYL3Rtobcr32eMvHxkWoJ6xoz0I
|
19
|
+
h74yNKtHjTTCxj86HWBPsE6xVMxVCftClndjCyKsiMiqvvp1wDNO0FFK+6LijmL3
|
20
|
+
2Xkp4brWq1JO92y9vYct34R7o2X//+nwZs+sss+EYhNdvdUJfWy7tA5dghGdLvRn
|
21
|
+
UhJJSAtTefkBCwO7bufLEt+n7wIRbiJJ5dDCwE3NIX4wUSrNeYwXGXA/Ybki+BUl
|
22
|
+
3KJF9IC0XR9fY9DGF0FXBKrkfDlZRrnQOem2aIxeuln0KQLJXXJuDTQPHO+mK3EG
|
23
|
+
UPhR7IAHAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
24
|
+
BBQn32ZStKmqwFqs2vuglYzkvDzBZjANBgkqhkiG9w0BAQUFAAOCAQEAe3Z+iMmy
|
25
|
+
IX9ChQW4hNNb1HOpgCMc2RL+vUwku9WE95dZ+BE4A6mOYTj5JXdYf4R4Z2vavr+d
|
26
|
+
nwJWtXPeIBWxireb8gUU0DwqodYTpsmkj5LD1zIaZ59rXlwDA9O0V4fwE1iRG5MD
|
27
|
+
mB7m8fT8WNOeg4AfjH4aSiHI1+HX1RQkc7KFdLotKnCevzYU6Jza5VUbXyJ+yCEH
|
28
|
+
DFARN3mkfGI+18MRDEi39nK2O/bBd6Wf0cYPEKsGQjNNAIBtv9belepSMd1KKfQ2
|
29
|
+
L7j8CnNDCrsHDe7/251D85wSvTH4Q/41NE5ahdCkkHwzDJeyhXpmNuUSswdn7woz
|
30
|
+
teST6sOe8lUhZQ==
|
31
|
+
-----END CERTIFICATE-----
|
32
|
+
|
33
|
+
date: 2009-11-20 00:00:00 -05:00
|
34
|
+
default_executable:
|
35
|
+
dependencies:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: hoe
|
38
|
+
type: :development
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 2.3.3
|
45
|
+
version:
|
46
|
+
description: Natural language method chaining for Time, Durations and the like.
|
47
|
+
email:
|
48
|
+
- gems@behindlogic.com
|
49
|
+
executables: []
|
50
|
+
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
extra_rdoc_files:
|
54
|
+
- History.txt
|
55
|
+
- License.txt
|
56
|
+
- Manifest.txt
|
57
|
+
- README.txt
|
58
|
+
files:
|
59
|
+
- History.txt
|
60
|
+
- License.txt
|
61
|
+
- Manifest.txt
|
62
|
+
- README.txt
|
63
|
+
- Rakefile
|
64
|
+
- lib/days_and_times.rb
|
65
|
+
- lib/days_and_times/duration.rb
|
66
|
+
- lib/days_and_times/numeric.rb
|
67
|
+
- lib/days_and_times/object.rb
|
68
|
+
- lib/days_and_times/time.rb
|
69
|
+
- spec/days_and_times/numeric_spec.rb
|
70
|
+
- spec/days_and_times_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: http://dcparker.github.com/days_and_times
|
74
|
+
licenses: []
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options:
|
78
|
+
- --main
|
79
|
+
- README.txt
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: "0"
|
87
|
+
version:
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project: days_and_times
|
97
|
+
rubygems_version: 1.3.5
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Natural language method chaining for Time, Durations and the like.
|
101
|
+
test_files: []
|
102
|
+
|
metadata.gz.sig
ADDED
Binary file
|