dcparker-days_and_times 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-08-06
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -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.
@@ -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
@@ -0,0 +1,68 @@
1
+ = days_and_times
2
+
3
+ * http://github.com/dcparker/days_and_times
4
+
5
+ == DESCRIPTION:
6
+
7
+ Natural language method chaining for Time, Durations and the like.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Acts on singular and plural of the following methods: second, minute, hour, day, week.
12
+ * Months are variable and do not lend themselves to concrete mathmatics, so they are not implemented.
13
+ * The Duration class holds many treasures and enables us in many ways.
14
+ * 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.
15
+ * Iterators are implemented on Duration objects, respecting the unit of time the duration was instantiated with, or using a customized iterator unit.
16
+ * Durations can be 'anchored' to a starting time for convenience in many usages.
17
+
18
+ == SYNOPSIS:
19
+
20
+ 1.day #=> A duration of 1 day
21
+ 7.days #=> A duration of 7 days
22
+ 1.week #=> A duration of 1 week
23
+ 1.week - 2.days #=> A duration of 5 days
24
+ 1.week.from(Now()) #=> The time of 1 week from this moment
25
+ 1.week.from(Today()) #=> The time of 1 week from the beginning of today
26
+ 3.minutes.ago.until(7.minutes.from(Now())) #=> duration 3 minutes ago to 7 minutes from now
27
+ 3.minutes.ago.until(7.minutes.from(Now())) - 2.minutes #=> duration 3 minutes ago to 5 minutes from now
28
+ 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
29
+ 1.week - 1.second #=> A duration of 6 days, 23 hours, 59 minutes, and 59 seconds
30
+ 4.weeks / 2 #=> A duration of 2 weeks
31
+ 4.weeks / 2.weeks #=> The integer 2
32
+ 8.weeks.each {|week| ...} #=> Runs code for each week contained in the duration (of 8 weeks)
33
+ 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.
34
+ 1.week.each {|week| ...} #=> Automatically chooses week as its iterator
35
+ 7.days.each {|day| ...} #=> Automatically chooses day as its iterator
36
+ 1.week.each_day {|day| ...} #=> Forcing the week to iterate through days
37
+ 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.
38
+ # ... and more!
39
+
40
+ == INSTALL:
41
+
42
+ gem sources -a http://gems.github.com
43
+ sudo gem install dcparker-days_and_times
44
+
45
+ == LICENSE:
46
+
47
+ (The MIT License)
48
+
49
+ Copyright (c) 2008 BehindLogic
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of this software and associated documentation files (the
53
+ 'Software'), to deal in the Software without restriction, including
54
+ without limitation the rights to use, copy, modify, merge, publish,
55
+ distribute, sublicense, and/or sell copies of the Software, and to
56
+ permit persons to whom the Software is furnished to do so, subject to
57
+ the following conditions:
58
+
59
+ The above copyright notice and this permission notice shall be
60
+ included in all copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
63
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
64
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
65
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
66
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
67
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
68
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/days_and_times.rb'
4
+
5
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
6
+
7
+ Hoe.new('days_and_times', DaysAndTimes.VERSION) do |p|
8
+ p.author = 'Daniel Parker'
9
+ p.email = 'gems@behindlogic.com'
10
+ p.summary = "Natural language method chaining for Time, Durations and the like."
11
+ p.description = "Natural language method chaining for Time, Durations and the like."
12
+ p.url = 'http://github.com/dcparker/days_and_times'
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ end
15
+
16
+ desc "Generate gemspec"
17
+ task :gemspec do |x|
18
+ # Check the manifest before generating the gemspec
19
+ manifest = %x[rake check_manifest]
20
+ manifest.gsub!(/\(in [^\)]+\)\n/, "")
21
+
22
+ unless manifest.empty?
23
+ print "\n", "#"*68, "\n"
24
+ print <<-EOS
25
+ Manifest.txt is not up-to-date. Please review the changes below.
26
+ If the changes are correct, run 'rake check_manifest | patch'
27
+ and then run this command again.
28
+ EOS
29
+ print "#"*68, "\n\n"
30
+ puts manifest
31
+ else
32
+ gemspec = `rake debug_gem`
33
+ gemspec.gsub!(/\(in [^\)]+\)\n/, "")
34
+ File.open("days_and_times.gemspec", 'w') {|f| f.write(gemspec) }
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module DaysAndTimes
5
+ def self.VERSION
6
+ '1.0.0'
7
+ end
8
+ end
9
+
10
+ require 'days_and_times/duration'
11
+ require 'days_and_times/numeric'
12
+ require 'days_and_times/time'
13
+ require 'days_and_times/object'
@@ -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
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../lib/days_and_times'
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dcparker-days_and_times
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Parker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-06 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.7.0
23
+ version:
24
+ description: Natural language method chaining for Time, Durations and the like.
25
+ email: gems@behindlogic.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - History.txt
32
+ - License.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ files:
36
+ - History.txt
37
+ - License.txt
38
+ - Manifest.txt
39
+ - README.txt
40
+ - Rakefile
41
+ - lib/days_and_times.rb
42
+ - lib/days_and_times/duration.rb
43
+ - lib/days_and_times/numeric.rb
44
+ - lib/days_and_times/object.rb
45
+ - lib/days_and_times/time.rb
46
+ - spec/days_and_times/numeric_spec.rb
47
+ - spec/days_and_times_spec.rb
48
+ - spec/spec_helper.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/dcparker/days_and_times
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --main
54
+ - README.txt
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project: days_and_times
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: Natural language method chaining for Time, Durations and the like.
76
+ test_files: []
77
+