duration 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2007 Matthew Harris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,152 @@
1
+ = Project
2
+ == Duration
3
+ == http://rubyforge.org/projects/duration
4
+
5
+ = Author
6
+ Matthew Harris <matt@matthewharris.org>
7
+ http://matthewharris.org
8
+
9
+ = Synopsis
10
+ Duration objects are simple mechanisms that allow you to operate on durations
11
+ of time.
12
+
13
+ They allow you to know how much time has passed since a certain
14
+ point in time, or they can tell you how much time something is (when given as
15
+ seconds) in different units of time measurement.
16
+
17
+ Durations would particularly be useful for those scripts or applications that
18
+ allow you to know the uptime of themselves or perhaps provide a countdown until
19
+ a certain event.
20
+
21
+ As of version 0.1.0, the duration library has been completely rewritten with
22
+ better knowledge.
23
+
24
+ = Features
25
+ Duration is packed with many features and has plans for many more.
26
+
27
+ === Current features
28
+ * Duck typing objects to work with Duration
29
+ * Localization support of formatted strings (feel free to submit your own!)
30
+ * Capability to compare durations to any object (duck typing)
31
+ * Arithmetic operations from Duration instances are legal (duck typing)
32
+ * Convenience methods for creating durations from Time objects
33
+
34
+ === Soon to come features
35
+ * Collection of holiday-related methods (creating durations since/until a holiday)
36
+ * A larger collection of locales
37
+ * Definitely have to put more Rubyisms in there too!
38
+ * ...and much more! (can't think of any yet)
39
+
40
+ = Credits
41
+ I want to thank everyone who downloaded duration, tried it, and sent me e-mails
42
+ of appreciation, feedback, bug reports and suggestions.
43
+
44
+ Your gratitude has motivated me to rework this almost-dead library and continue
45
+ to maintain it.
46
+
47
+ Thanks!
48
+
49
+ = Usage
50
+ Using the duration library is fairly simple and straight-forward. The base
51
+ class most of you will be using is the Duration class.
52
+
53
+ === Initialization
54
+ There are two ways to initialize the Duration class. One way is to pass an
55
+ object capable of responding to the #to_i method. That object is expected to
56
+ return an integer or float that represents the number of seconds for the
57
+ duration object.
58
+
59
+ The second way to initialize the Duration class is to pass a key=>value paired
60
+ Hash object. The hash's keys will be scanned for units of measurement defiend
61
+ in Duration::UNITS or (the keys of) Duration::MULTIPLES.
62
+
63
+ Here's an example of using the formerly mentioned solution:
64
+ duration = Duration.new(Time.now)
65
+ => #<Duration: 1981 weeks, 3 hours, 50 minutes and 44 seconds>
66
+
67
+ As you can see, it gave us the duration since the UNIX epoch. This works fine
68
+ because Time objects respond to the #to_i method. And Time#to_i returns a
69
+ UNIX timestamp (which is the number of seconds that have passed since the UNIX
70
+ epoch, January 1, 1970).
71
+
72
+ Before I move on to demonstrating the second solution, I'd like to note that
73
+ there are Time#to_duration, Time#duration and Duration.since_epoch for
74
+ accomplishing similar tasks.
75
+
76
+ Alright, the second solution is to pass in a Hash object, as previously
77
+ mentioned.
78
+
79
+ For example:
80
+ duration = Duration.new(:hours => 12, :minutes => 58, :days => 14)
81
+ => #<Duration: 2 weeks, 12 hours and 58 minutes>
82
+
83
+ So, it works. But also notice that I gave it `:days => 14', but it spat out
84
+ `2 weeks'. Yes, Duration knows what it's doing.
85
+
86
+ There are more keys you can pass. Check all the keys available in the
87
+ Duration::MULTIPLES hash to see. All those keys are valid to pass. Any
88
+ duplicated multiples will merely be added to each other, so be careful if that's
89
+ not the desired behavior.
90
+
91
+ === Formatting
92
+ The Duration class provides Duration#format (alias: Duration#strftime) for
93
+ formatting the time units into a string.
94
+
95
+ For more information on the identifiers it supports, check out Duration#format.
96
+
97
+ Here's an example of how to format the known minutes in the duration:
98
+ duration = Duration.since_epoch
99
+ duration.format('%~m passed: %m')
100
+ => "minutes passed: 4"
101
+
102
+ Now, this may look a bit tricky at first, but it's somewhat similar to the
103
+ standard strftime() function you see in many lanuages, with the exception of the
104
+ weird "%~m" type identifiers. These identifiers are the correct terminology for
105
+ the corresponding time unit.
106
+
107
+ The identifiers are locale-sensitive and can be easily changed to another
108
+ language. Localization is done internally from the duration library, and does
109
+ not use the system's locales.
110
+
111
+ If you would like to write a locale for your language (if it doesn't exist),
112
+ you can simply read the source code of the existing locales lying within the
113
+ "duration/localizations" directory. They are straight-forward 10-15 line
114
+ modules and are not complex at all.
115
+
116
+ === Arithmetic
117
+ Duration objects support arithmetic against any object that response to the
118
+ #to_i method. This is pretty self-explanatory. What happens when you divide
119
+ a duration by 2?
120
+
121
+ duration = Duration.since_epoch
122
+ => #<Duration: 1981 weeks, 4 hours, 12 minutes and 54 seconds>
123
+
124
+ duration / 2
125
+ => #<Duration: 990 weeks, 3 days, 14 hours, 6 minutes and 27 seconds>
126
+
127
+ Pretty simple, right?
128
+
129
+ === Magical Methods
130
+ Finally, Duration has a few magical methods that are handled through the
131
+ #method_missing override. Duration instances support all method calls to such
132
+ methods as `round_<unit>_to_<unit>' and `<unit>_to_<unit>'. These methods
133
+ basically convert their <unit> to another <unit> and spit back the numbers.
134
+
135
+ The `round_<unit>_to_<unit>' method rounds the converted unit.
136
+
137
+ Duration.since_epoch.weeks_to_days
138
+ => 13867.0
139
+
140
+ Duration.since_epoch.round_weeks_to_days
141
+ => 13867
142
+
143
+ Duration.since_epoch.weeks_to_days.round
144
+ => 13867
145
+
146
+ = Feedback
147
+ Well, I hope you learned a lot from the above explanations. Time for you to get
148
+ creative on your own now!
149
+
150
+ If you have any feedback, bug reports, suggestions or locale submissions, just
151
+ e-mail me at matt@matthewharris.org or shugotenshi@gmail.com and I will be glad
152
+ to answer you back.
@@ -1,515 +1,204 @@
1
- # = Author
2
- #
3
- # Matthew Harris (mailto:shugotenshi@gmail.com)
4
- #
5
- # = Project
6
- #
7
- # http://www.rubyforge.org/projects/duration
8
- #
9
- # = Synopsis
10
- #
11
- # Duration is a simple class that provides ways of easily manipulating durations
12
- # (timespans) and formatting them as well.
13
- #
14
- # = Usage
15
- #
16
- # require 'duration'
17
- # => true
18
- # d = Duration.new(60 * 60 * 24 * 10 + 120 + 30)
19
- # => #<Duration: 1 week, 3 days, 2 minutes and 30 seconds>
20
- # d.to_s
21
- # => "1 week, 3 days, 2 minutes and 30 seconds"
22
- # [d.weeks, d.days]
23
- # => [1, 3]
24
- # d.days = 7; d
25
- # => #<Duration: 2 weeks, 2 minutes and 30 seconds>
26
- # d.strftime('%w w, %d d, %h h, %m m, %s s')
27
- # => "2 w, 0 d, 0 h, 2 m, 30 s"
28
- #
1
+ require 'duration/version'
2
+ require 'duration/numeric'
3
+ require 'duration/time'
4
+ require 'duration/localizations'
5
+ Duration::Localizations.load_all
6
+
7
+ # Duration objects are simple mechanisms that allow you to operate on durations
8
+ # of time. They allow you to know how much time has passed since a certain
9
+ # point in time, or they can tell you how much time something is (when given as
10
+ # seconds) in different units of time measurement. Durations would particularly
11
+ # be useful for those scripts or applications that allow you to know the uptime
12
+ # of themselves or perhaps provide a countdown until a certain event.
29
13
  class Duration
30
- include Comparable
31
- include Enumerable
32
-
33
- attr_reader :total, :weeks, :days, :hours, :minutes
34
-
35
- WEEK = 60 * 60 * 24 * 7
36
- DAY = 60 * 60 * 24
37
- HOUR = 60 * 60
38
- MINUTE = 60
39
- SECOND = 1
40
-
41
- # Initialize Duration class.
42
- #
43
- # *Example*
44
- #
45
- # d = Duration.new(60 * 60 * 24 * 10 + 120 + 30)
46
- # => #<Duration: 1 week, 3 days, 2 minutes and 30 seconds>
47
- # d = Duration.new(:weeks => 1, :days => 3, :minutes => 2, :seconds => 30)
48
- # => #<Duration: 1 week, 3 days, 2 minutes and 30 seconds>
49
- #
50
- def initialize(seconds_or_attr = 0)
51
- if seconds_or_attr.kind_of? Hash
52
- # Part->time map table.
53
- h = {:weeks => WEEK, :days => DAY, :hours => HOUR, :minutes => MINUTE, :seconds => SECOND}
54
-
55
- # Loop through each valid part, ignore all others.
56
- seconds = seconds_or_attr.inject(0) do |sec, args|
57
- # Grab the part of the duration (week, day, whatever) and the number of seconds for it.
58
- part, time = args
59
-
60
- # Map each part to their number of seconds and the given value.
61
- # {:weeks => 2} maps to h[:weeks] -- so... weeks = WEEK * 2
62
- if h.key?(prt = part.to_s.to_sym) then sec + time * h[prt] else 0 end
63
- end
64
- else
65
- seconds = seconds_or_attr
66
- end
67
-
68
- @total, array = seconds.to_f.round, []
69
- @seconds = [WEEK, DAY, HOUR, MINUTE].inject(@total) do |left, part|
70
- array << left / part; left % part
71
- end
72
-
73
- @weeks, @days, @hours, @minutes = array
74
- end
75
-
76
- # Format duration.
77
- #
78
- # *Identifiers*
79
- #
80
- # %w -- Number of weeks
81
- # %d -- Number of days
82
- # %h -- Number of hours
83
- # %m -- Number of minutes
84
- # %s -- Number of seconds
85
- # %t -- Total number of seconds
86
- # %x -- Duration#to_s
87
- # %% -- Literal `%' character
88
- #
89
- # *Example*
90
- #
91
- # d = Duration.new(:weeks => 10, :days => 7)
92
- # => #<Duration: 11 weeks>
93
- # d.strftime("It's been %w weeks!")
94
- # => "It's been 11 weeks!"
95
- #
96
- def strftime(fmt)
97
- h =\
98
- {'w' => @weeks ,
99
- 'd' => @days ,
100
- 'h' => @hours ,
101
- 'm' => @minutes,
102
- 's' => @seconds,
103
- 't' => @total ,
104
- 'x' => to_s}
105
-
106
- fmt.gsub(/%?%(w|d|h|m|s|t|x)/) do |match|
107
- match.size == 3 ? match : h[match[1..1]]
108
- end.gsub('%%', '%')
109
- end
110
-
111
- # Get the number of seconds of a given part, or simply just get the number of
112
- # seconds.
113
- #
114
- # *Example*
115
- #
116
- # d = Duration.new(:weeks => 1, :days => 1, :hours => 1, :seconds => 30)
117
- # => #<Duration: 1 week, 1 day, 1 hour and 30 seconds>
118
- # d.seconds(:weeks)
119
- # => 604800
120
- # d.seconds(:days)
121
- # => 86400
122
- # d.seconds(:hours)
123
- # => 3600
124
- # d.seconds
125
- # => 30
126
- #
127
- def seconds(part = nil)
128
- # Table mapping
129
- h = {:weeks => WEEK, :days => DAY, :hours => HOUR, :minutes => MINUTE}
130
-
131
- if [:weeks, :days, :hours, :minutes].include? part
132
- __send__(part) * h[part]
133
- else
134
- @seconds
135
- end
136
- end
137
-
138
- # For iterating through the duration set of weeks, days, hours, minutes, and
139
- # seconds.
140
- #
141
- # *Example*
142
- #
143
- # Duration.new(:weeks => 1, :seconds => 30).each do |part, time|
144
- # puts "part: #{part}, time: #{time}"
145
- # end
146
- #
147
- # _Output_
148
- #
149
- # part: weeks, time: 1
150
- # part: days, time: 0
151
- # part: hours, time: 0
152
- # part: minutes, time: 0
153
- # part: seconds, time: 30
154
- #
155
- def each
156
- [['weeks' , @weeks ],
157
- ['days' , @days ],
158
- ['hours' , @hours ],
159
- ['minutes' , @minutes],
160
- ['seconds' , @seconds]].each do |part, time|
161
- # Yield to block
162
- yield part, time
14
+ include Comparable
15
+ include Enumerable
16
+
17
+ # Unit multiples
18
+ MULTIPLES = {
19
+ :seconds => 1,
20
+ :minutes => 60,
21
+ :hours => 3600,
22
+ :days => 86400,
23
+ :weeks => 604800,
24
+ :second => 1,
25
+ :minute => 60,
26
+ :hour => 3600,
27
+ :day => 86400,
28
+ :week => 604800
29
+ }
30
+
31
+ # Unit names
32
+ UNITS = [:seconds, :minutes, :hours, :days, :weeks]
33
+
34
+ attr_reader :total, :seconds, :minutes, :hours, :days, :weeks
35
+
36
+ # Change the locale Duration will use when converting itself to a string
37
+ def Duration.change_locale(locale)
38
+ @@locale = Localizations.locales[locale.to_sym] or raise LocaleError, "undefined locale '#{locale}'"
39
+ end
40
+
41
+ # Load default locale
42
+ change_locale(Localizations::DEFAULT_LOCALE)
43
+
44
+ # Constructs a Duration instance that represents the duration since the UNIX
45
+ # epoch (1970-01-01T00:00:00Z)
46
+ def Duration.since_epoch
47
+ new(Time.now)
48
+ end
49
+
50
+ # Initialize a duration. `args' can be a hash or anything else. If a hash is
51
+ # passed, it will be scanned for a key=>value pair of time units such as those
52
+ # listed in the Duration::UNITS array or Duration::MULTIPLES hash.
53
+ #
54
+ # If anything else except a hash is passed, #to_i is invoked on that object
55
+ # and expects that it return the number of seconds desired for the duration.
56
+ def initialize(args = 0)
57
+ # Two types of arguments are accepted. If it isn't a hash, it's converted
58
+ # to an integer.
59
+ if args.kind_of?(Hash)
60
+ @seconds = 0
61
+ MULTIPLES.each do |unit, multiple|
62
+ unit = unit.to_sym
63
+ @seconds += args[unit] * multiple if args.key?(unit)
64
+ end
65
+ else
66
+ @seconds = args.to_i
67
+ end
68
+
69
+ # Calculate duration
70
+ calculate!
71
+ end
72
+
73
+ # Calculates the duration from seconds and figures out what the actual
74
+ # durations are in specific units. This method is called internally, and
75
+ # does not need to be called by user code.
76
+ def calculate!
77
+ multiples = [MULTIPLES[:weeks], MULTIPLES[:days], MULTIPLES[:hours], MULTIPLES[:minutes], MULTIPLES[:seconds]]
78
+ units = []
79
+ @total = @seconds.to_f.round
80
+ multiples.inject(@total) do |total, multiple|
81
+ # Divide into largest unit
82
+ units << total / multiple
83
+ total % multiple # The remainder will be divided as the next largest
163
84
  end
164
- end
165
-
166
- # Calls `<=>' on Duration#total.
167
- #
168
- # *Example*
169
- #
170
- # 5.days == 24.hours * 5
171
- # => true
172
- #
85
+
86
+ # Gather the divided units
87
+ @weeks, @days, @hours, @minutes, @seconds = units
88
+ end
89
+
90
+ # Compare this duration to another (or objects that respond to #to_i)
173
91
  def <=>(other)
174
92
  @total <=> other.to_i
175
93
  end
176
-
177
- # Set the number of weeks.
178
- #
179
- # *Example*
180
- #
181
- # d = Duration.new(0)
182
- # => #<Duration: ...>
183
- # d.weeks = 2; d
184
- # => #<Duration: 2 weeks>
185
- #
186
- def weeks=(n)
187
- initialize(:weeks => n, :seconds => @total - seconds(:weeks))
188
- end
189
-
190
- # Set the number of days.
191
- #
192
- # *Example*
193
- #
194
- # d = Duration.new(0)
195
- # => #<Duration: ...>
196
- # d.days = 5; d
197
- # => #<Duration: 5 days>
198
- #
199
- def days=(n)
200
- initialize(:days => n, :seconds => @total - seconds(:days))
201
- end
202
-
203
- # Set the number of hours.
204
- #
205
- # *Example*
206
- #
207
- # d = Duration.new(0)
208
- # => #<Duration: ...>
209
- # d.hours = 5; d
210
- # => #<Duration: 5 hours>
211
- #
212
- def hours=(n)
213
- initialize(:hours => n, :seconds => @total - seconds(:hours))
214
- end
215
-
216
- # Set the number of minutes.
217
- #
218
- # *Example*
219
- #
220
- # d = Duration.new(0)
221
- # => #<Duration: ...>
222
- # d.minutes = 30; d
223
- # => #<Duration: 30 minutes>
224
- #
225
- def minutes=(n)
226
- initialize(:minutes => n, :seconds => @total - seconds(:minutes))
227
- end
228
-
229
- # Set the number of minutes.
230
- #
231
- # *Example*
232
- #
233
- # d = Duration.new(0)
234
- # => #<Duration: ...>
235
- # d.seconds = 30; d
236
- # => #<Duration: 30 seconds>
237
- #
238
- def seconds=(n)
239
- initialize(:seconds => (@total + n) - @seconds)
240
- end
241
-
242
- # Friendly, human-readable string representation of the duration.
243
- #
244
- # *Example*
245
- #
246
- # d = Duration.new(:seconds => 140)
247
- # => #<Duration: 2 minutes and 20 seconds>
248
- # d.to_s
249
- # => "2 minutes and 20 seconds"
250
- #
251
- def to_s
252
- str = ''
253
-
254
- each do |part, time|
255
- # Skip any zero times.
256
- next if time.zero?
257
-
258
- # Concatenate the part of the time and the time itself.
259
- str << "#{time} #{time == 1 ? part[0..-2] : part}, "
260
- end
261
-
262
- str.chomp(', ').sub(/(.+), (.+)/, '\1 and \2')
263
- end
264
-
265
- # Inspection string--Similar to #to_s except that it has the class name.
266
- #
267
- # *Example*
268
- #
269
- # Duration.new(:seconds => 140)
270
- # => #<Duration: 2 minutes and 20 seconds>
271
- #
94
+
95
+ # Convenient iterator for going through each duration unit from lowest to
96
+ # highest. (Goes from seconds...weeks)
97
+ def each
98
+ UNITS.each { |unit| yield unit, __send__(unit) }
99
+ end
100
+
101
+ # Format a duration into a human-readable string.
102
+ #
103
+ # %w => weeks
104
+ # %d => days
105
+ # %h => hours
106
+ # %m => minutes
107
+ # %s => seconds
108
+ # %t => total seconds
109
+ # %H => zero-padded hours
110
+ # %M => zero-padded minutes
111
+ # %S => zero-padded seconds
112
+ # %~s => locale-dependent "seconds" terminology
113
+ # %~m => locale-dependent "minutes" terminology
114
+ # %~h => locale-dependent "hours" terminology
115
+ # %~d => locale-dependent "days" terminology
116
+ # %~w => locale-dependent "weeks" terminology
117
+ #
118
+ def format(format_str)
119
+ identifiers = {
120
+ 'w' => @weeks,
121
+ 'd' => @days,
122
+ 'h' => @hours,
123
+ 'm' => @minutes,
124
+ 's' => @seconds,
125
+ 't' => @total,
126
+ 'H' => @hours.to_s.rjust(2, '0'),
127
+ 'M' => @minutes.to_s.rjust(2, '0'),
128
+ 'S' => @seconds.to_s.rjust(2, '0'),
129
+ '~s' => @seconds == 1 ? @@locale.singulars[0] : @@locale.plurals[0],
130
+ '~m' => @minutes == 1 ? @@locale.singulars[1] : @@locale.plurals[1],
131
+ '~h' => @hours == 1 ? @@locale.singulars[2] : @@locale.plurals[2],
132
+ '~d' => @days == 1 ? @@locale.singulars[3] : @@locale.plurals[3],
133
+ '~w' => @weeks == 1 ? @@locale.singulars[4] : @@locale.plurals[4]
134
+ }
135
+
136
+ format_str.gsub(/%?%(w|d|h|m|s|t|H|M|S|~(?:s|m|h|d|w))/) do |match|
137
+ match['%%'] ? match : identifiers[match[1..-1]]
138
+ end.gsub('%%', '%')
139
+ end
140
+
141
+ def method_missing(m, *args, &block)
142
+ units = UNITS.join('|')
143
+ match = /(round_)?(#{units})_to_(#{units})$/.match(m.to_s)
144
+ if match
145
+ seconds = ((__send__(match[2].to_sym) * MULTIPLES[match[2].to_sym]) / MULTIPLES[match[3].to_sym].to_f)
146
+ match[1] ? seconds.round : seconds
147
+ else
148
+ super
149
+ end
150
+ end
151
+
152
+ # String representation of the Duration object.
153
+ def to_s
154
+ @@locale.format.call(self)
155
+ end
156
+
157
+ # Inspect Duration object.
272
158
  def inspect
273
159
  "#<#{self.class}: #{(s = to_s).empty? ? '...' : s}>"
274
160
  end
275
-
276
- # Add to Duration.
277
- #
278
- # *Example*
279
- #
280
- # d = Duration.new(30)
281
- # => #<Duration: 30 seconds>
282
- # d + 30
283
- # => #<Duration: 1 minute>
284
- #
161
+
285
162
  def +(other)
286
- self.class.new(@total + other.to_i)
163
+ Duration.new(@total + other.to_i)
287
164
  end
288
-
289
- # Subtract from Duration.
290
- #
291
- # *Example*
292
- #
293
- # d = Duration.new(30)
294
- # => #<Duration: 30 seconds>
295
- # d - 15
296
- # => #<Duration: 15 seconds>
297
- #
165
+
298
166
  def -(other)
299
- self.class.new(@total - other.to_i)
167
+ Duration.new(@total - other.to_i)
300
168
  end
301
-
302
- # Multiply two Durations.
303
- #
304
- # *Example*
305
- #
306
- # d = Duration.new(30)
307
- # => #<Duration: 30 seconds>
308
- # d * 2
309
- # => #<Duration: 1 minute>
310
- #
169
+
311
170
  def *(other)
312
- self.class.new(@total * other.to_i)
171
+ Duration.new(@total * other.to_i)
313
172
  end
314
-
315
- # Divide two Durations.
316
- #
317
- # *Example*
318
- #
319
- # d = Duration.new(30)
320
- # => #<Duration: 30 seconds>
321
- # d / 2
322
- # => #<Duration: 15 seconds>
323
- #
173
+
324
174
  def /(other)
325
- self.class.new(@total / other.to_i)
326
- end
327
-
328
- alias to_i total
329
- end
330
-
331
- # BigDuration is a variant of Duration that supports years and months. Support
332
- # for months is not accurate, as a month is assumed to be 30 days so use at your
333
- # own risk.
334
- #
335
- class BigDuration < Duration
336
- attr_reader :years, :months
337
-
338
- YEAR = 60 * 60 * 24 * 30 * 12
339
- MONTH = 60 * 60 * 24 * 30
340
-
341
- # Similar to Duration.new except that BigDuration.new supports `:years' and
342
- # `:months' and will also handle years and months correctly when breaking down
343
- # the seconds.
344
- #
345
- def initialize(seconds_or_attr = 0)
346
- if seconds_or_attr.kind_of? Hash
347
- # Part->time map table.
348
- h =\
349
- {:years => YEAR ,
350
- :months => MONTH ,
351
- :weeks => WEEK ,
352
- :days => DAY ,
353
- :hours => HOUR ,
354
- :minutes => MINUTE,
355
- :seconds => SECOND}
356
-
357
- # Loop through each valid part, ignore all others.
358
- seconds = seconds_or_attr.inject(0) do |sec, args|
359
- # Grab the part of the duration (week, day, whatever) and the number of seconds for it.
360
- part, time = args
361
-
362
- # Map each part to their number of seconds and the given value.
363
- # {:weeks => 2} maps to h[:weeks] -- so... weeks = WEEK * 2
364
- if h.key?(prt = part.to_s.to_sym) then sec + time * h[prt] else 0 end
365
- end
366
- else
367
- seconds = seconds_or_attr
368
- end
369
-
370
- @total, array = seconds.to_f.round, []
371
- @seconds = [YEAR, MONTH, WEEK, DAY, HOUR, MINUTE].inject(@total) do |left, part|
372
- array << left / part; left % part
373
- end
374
-
375
- @years, @months, @weeks, @days, @hours, @minutes = array
376
- end
377
-
378
- # BigDuration variant of Duration#strftime.
379
- #
380
- # *Identifiers: BigDuration*
381
- #
382
- # %y -- Number of years
383
- # %m -- Number of months
384
- #
385
- def strftime(fmt)
386
- h = {'y' => @years, 'M' => @months}
387
- super(fmt.gsub(/%?%(y|M)/) { |match| match.size == 3 ? match : h[match[1..1]] })
388
- end
389
-
390
- # Similar to Duration#each except includes years and months in the interation.
391
- #
392
- def each
393
- [['years' , @years ],
394
- ['months' , @months ],
395
- ['weeks' , @weeks ],
396
- ['days' , @days ],
397
- ['hours' , @hours ],
398
- ['minutes' , @minutes],
399
- ['seconds' , @seconds]].each do |part, time|
400
- # Yield to block
401
- yield part, time
402
- end
403
- end
404
-
405
- # Derived from Duration#seconds, but supports `:years' and `:months' as well.
406
- #
407
- def seconds(part = nil)
408
- h = {:years => YEAR, :months => MONTH}
409
- if [:years, :months].include? part
410
- __send__(part) * h[part]
411
- else
412
- super(part)
413
- end
414
- end
415
-
416
- # Set the number of years in the BigDuration.
417
- #
418
- def years=(n)
419
- initialize(:years => n, :seconds => @total - seconds(:years))
420
- end
421
-
422
- # Set the number of months in the BigDuration.
423
- #
424
- def months=(n)
425
- initialize(:months => n, :seconds => @total - seconds(:months))
426
- end
427
- end
428
-
429
- # The following important additions are made to Numeric:
430
- #
431
- # Numeric#weeks -- Create a Duration object with given weeks
432
- # Numeric#days -- Create a Duration object with given days
433
- # Numeric#hours -- Create a Duration object with given hours
434
- # Numeric#minutes -- Create a Duration object with given minutes
435
- # Numeric#seconds -- Create a Duration object with given seconds
436
- #
437
- # BigDuration support:
438
- #
439
- # Numeric#years -- Create a BigDuration object with given years
440
- # Numeric#months -- Create a BigDuration object with given months
441
- #
442
- # BigDuration objects can be created from regular weeks, days, hours, etc by
443
- # providing `:big' as an argument to the above Numeric methods.
444
- #
445
- class Numeric
446
- alias __numeric_old_method_missing method_missing
447
-
448
- # Create a Duration object using self where self could represent weeks, days,
449
- # hours, minutes, and seconds.
450
- #
451
- # *Example*
452
- #
453
- # 10.duration(:weeks)
454
- # => #<Duration: 10 weeks>
455
- # 10.duration
456
- # => #<Duration: 10 seconds>
457
- #
458
- def duration(part = nil, klass = Duration)
459
- if [:years, :months, :weeks, :days, :hours, :minutes, :seconds].include? part
460
- klass.new(part => self)
461
- else
462
- klass.new(self)
463
- end
464
- end
465
-
466
- # Intercept calls to .weeks, .days, .hours, .minutes and .seconds because
467
- # Rails defines its own methods, so I'd like to prevent any redefining of
468
- # Rails' methods. If these methods don't get captured, then alternatively
469
- # Numeric#duration can be used.
470
- #
471
- # BigDuration methods include .years and .months, also BigDuration objects
472
- # can be created from any time such as weeks or minutes and even seconds.
473
- #
474
- # *Example: BigDuration*
475
- #
476
- # 5.years
477
- # => #<BigDuration: 5 years>
478
- # 10.minutes(:big)
479
- # => #<BigDuration: 10 minutes>
480
- #
481
- # *Example*
482
- #
483
- # 140.seconds
484
- # => #<Duration: 2 minutes and 20 seconds>
485
- #
486
- def method_missing(method, *args)
487
- if [:weeks, :days, :hours, :minutes, :seconds].include? method
488
- if args.size > 0 && args[0] == :big
489
- duration(method, BigDuration)
490
- else
491
- duration(method)
492
- end
493
- elsif [:years, :months].include? method
494
- duration(method, BigDuration)
495
- else
496
- __numeric_old_method_missing(method, *args)
497
- end
498
- end
499
- end
500
-
501
- # Time#duration has been added to convert the UNIX timestamp into a Duration.
502
- # See Time#duration for an example.
503
- #
504
- class Time
505
- # Create a Duration object from the UNIX timestamp.
506
- #
507
- # *Example*
508
- #
509
- # Time.now.duration
510
- # => #<Duration: 1898 weeks, 6 days, 1 hour, 12 minutes and 1 second>
511
- #
512
- def duration(type = nil)
513
- if type == :big then BigDuration.new(to_i) else Duration.new(to_i) end
514
- end
175
+ Duration.new(@total / other.to_i)
176
+ end
177
+
178
+ def %(other)
179
+ Duration.new(@total % other.to_i)
180
+ end
181
+
182
+ def seconds=(seconds)
183
+ initialize :seconds => (@total + seconds) - @seconds
184
+ end
185
+
186
+ def minutes=(minutes)
187
+ initialize :seconds => @total - minutes_to_seconds, :minutes => minutes
188
+ end
189
+
190
+ def hours=(hours)
191
+ initialize :seconds => @total - hours_to_seconds, :hours => hours
192
+ end
193
+
194
+ def days=(days)
195
+ initialize :seconds => @total - days_to_seconds, :days => days
196
+ end
197
+
198
+ def weeks=(weeks)
199
+ initialize :seconds => @total - weeks_to_seconds, :weeks => weeks
200
+ end
201
+
202
+ alias_method :to_i, :total
203
+ alias_method :strftime, :format
515
204
  end
@@ -0,0 +1,3 @@
1
+ class Duration
2
+ # Holiday durations shall go here.
3
+ end
@@ -0,0 +1,3 @@
1
+ class Duration
2
+ Locale = Struct.new(:name, :plurals, :singulars, :format)
3
+ end
@@ -0,0 +1,42 @@
1
+ require 'duration/locale'
2
+ require 'duration/localizations/english'
3
+ require 'duration/localizations/korean'
4
+
5
+ class Duration
6
+ # Contains localizations for the time formatters. Standard locales cannot be
7
+ # used because they don't define time units.
8
+ module Localizations
9
+ # Default locale
10
+ DEFAULT_LOCALE = :english
11
+ @@locales = {}
12
+
13
+ # Load all locales. This is invoked automatically upon loading Duration.
14
+ def Localizations.load_all
15
+ locales = []
16
+ constants.each do |constant|
17
+ mod = const_get(constant)
18
+ next unless mod.kind_of?(Module) and mod.const_defined?('LOCALE')
19
+
20
+ locale = mod.const_get('LOCALE').to_sym # Locale name
21
+ plurals = mod.const_defined?('PLURALS') ? mod.const_get('PLURALS') : DEFAULT_LOCALE # Unit plurals
22
+ singulars = mod.const_defined?('SINGULARS') ? mod.const_get('SINGULARS') : DEFAULT_LOCALE # Unit singulars
23
+
24
+ if mod.const_defined? 'FORMAT'
25
+ format = mod.const_get 'FORMAT'
26
+ format = format.kind_of?(Proc) ? format : proc { |duration| duration.format(format.to_s) }
27
+ end
28
+
29
+ # Add valid locale to the collection.
30
+ @@locales[locale] = Locale.new(locale, plurals, singulars, format)
31
+ end
32
+ end
33
+
34
+ # Collection of locales
35
+ def Localizations.locales
36
+ @@locales
37
+ end
38
+ end
39
+
40
+ class LocaleError < StandardError
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ class Duration
2
+ module Localizations
3
+ # English localization
4
+ module English
5
+ LOCALE = :english
6
+ PLURALS = %w(seconds minutes hours days weeks)
7
+ SINGULARS = %w(second minute hour day week)
8
+ FORMAT = proc do |duration|
9
+ str = duration.format('%w %~w, %d %~d, %h %~h, %m %~m, %s %~s')
10
+ str.sub(/^0 [a-z]+,?/i, '').gsub(/ 0 [a-z]+,?/i, '').chomp(',').sub(/, (\d+ [a-z]+)$/i, ' and \1').strip
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ class Duration
2
+ module Localizations
3
+ # Korean localization
4
+ module Korean
5
+ LOCALE = :korean
6
+ PLURALS = %w(초 분 시간 일 주)
7
+ SINGULARS = %w(초 분 시간 일 주)
8
+ FORMAT = proc do |duration|
9
+ str = duration.format('%w%~w, %d%~d, %h%~h, %m%~m, %s%~s')
10
+ str.sub(/^0[^\d+,]+,?/i, '').gsub(/ 0[^\d+,]+,?/i, '').chomp(',').strip
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ # Additional methods added to numeric objects allow the developer to construct
2
+ # a calculated number of seconds through human-readable methods.
3
+ #
4
+ # 60.seconds #=> 60
5
+ # 60.minutes #=> 3600
6
+ # ... and so on
7
+ #
8
+ # Singular methods are also defined.
9
+ #
10
+ # 1.minute #=> 60
11
+ # 1.hour #=> 3600
12
+ # ... and so on
13
+ class Numeric
14
+ # Number of seconds (equivalent to Numeric#to_i)
15
+ def seconds
16
+ to_i
17
+ end unless Numeric.instance_methods.include? 'seconds'
18
+
19
+ # Number of seconds using the number as the base of minutes.
20
+ def minutes
21
+ to_i * 60
22
+ end unless Numeric.instance_methods.include? 'minutes'
23
+
24
+ # Number of seconds using the number as the base of hours.
25
+ def hours
26
+ to_i * 3600
27
+ end unless Numeric.instance_methods.include? 'hours'
28
+
29
+ # Number of seconds using the number as the base of days.
30
+ def days
31
+ to_i * 86400
32
+ end unless Numeric.instance_methods.include? 'days'
33
+
34
+ # Number of seconds using the number as the base of weeks.
35
+ def week
36
+ to_i * 604800
37
+ end unless Numeric.instance_methods.include? 'weeks'
38
+
39
+ # Number of seconds (singular form).
40
+ def second
41
+ to_i
42
+ end unless Numeric.instance_methods.include? 'second'
43
+
44
+ # Number of seconds using the number as the base of minutes (singular form).
45
+ def minute
46
+ to_i * 60
47
+ end unless Numeric.instance_methods.include? 'minute'
48
+
49
+ # Number of seconds using the number as the base of hours (singular form).
50
+ def hour
51
+ to_i * 3600
52
+ end unless Numeric.instance_methods.include? 'hour'
53
+
54
+ # Number of seconds using the number as the base of days (singular form).
55
+ def day
56
+ to_i * 86400
57
+ end unless Numeric.instance_methods.include? 'day'
58
+
59
+ # Number of seconds using the number as the base of weeks (singular form).
60
+ def week
61
+ to_i * 604800
62
+ end unless Numeric.instance_methods.include? 'week'
63
+ end
@@ -0,0 +1,7 @@
1
+ class Time
2
+ def to_duration
3
+ Duration.new(self)
4
+ end
5
+
6
+ alias_method :duration, :to_duration
7
+ end
@@ -0,0 +1,9 @@
1
+ class Duration
2
+ # Duration library version number
3
+ VERSION = '0.1.0'
4
+
5
+ # Duration library version number
6
+ def Duration.version
7
+ VERSION
8
+ end
9
+ end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: duration
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.4
7
- date: 2006-05-24 00:00:00 +09:00
8
- summary: Duration is a package for manipulating time spans.
6
+ version: 0.1.0
7
+ date: 2007-12-20 00:00:00 +09:00
8
+ summary: Duration/timespan manipulation library
9
9
  require_paths:
10
10
  - lib
11
- email: shugotenshi@gmail.com
11
+ email: matt@matthewharris.org
12
12
  homepage: http://duration.rubyforge.org
13
13
  rubyforge_project: duration
14
- description: Duration is a simple class that provides ways of easily manipulating durations (timespans) and formatting them as well.
15
- autorequire:
14
+ description: Duration is a library for manipulating timespans. It can give you readable output for a timespan as well as manipulate the timespan itself. With this it is possible to make "countdowns" or "time passed since" type objects.
15
+ autorequire: duration
16
16
  default_executable:
17
17
  bindir: bin
18
18
  has_rdoc: true
@@ -25,16 +25,32 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ post_install_message:
28
29
  authors:
29
30
  - Matthew Harris
30
31
  files:
32
+ - lib/duration/holidays.rb
33
+ - lib/duration/locale.rb
34
+ - lib/duration/localizations/english.rb
35
+ - lib/duration/localizations/korean.rb
36
+ - lib/duration/localizations.rb
37
+ - lib/duration/numeric.rb
38
+ - lib/duration/time.rb
39
+ - lib/duration/version.rb
31
40
  - lib/duration.rb
41
+ - README
42
+ - LICENSE
32
43
  test_files: []
33
44
 
34
- rdoc_options: []
35
-
36
- extra_rdoc_files: []
37
-
45
+ rdoc_options:
46
+ - --title
47
+ - Duration -- The Timespan Library
48
+ - --main
49
+ - README
50
+ - --line-numbers
51
+ extra_rdoc_files:
52
+ - README
53
+ - LICENSE
38
54
  executables: []
39
55
 
40
56
  extensions: []