xduration 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ Gemfile.lock
2
+ .DS_Store
3
+ .idea
4
+ .bundle
5
+ *~
6
+ #*
7
+ *.gem
8
+ .yardoc/*
9
+ coverage/*
10
+ doc/*
11
+ pkg/*
12
+ bluecloth/*
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - ruby-head
9
+ - jruby-head
10
+ - ree
data/CHANGELOG ADDED
@@ -0,0 +1,8 @@
1
+ ## 0.2.2
2
+
3
+ * Renamed to 'xduration'
4
+ * Added months (30 days) and years (365.25 days)
5
+ * Added month and year I18n support
6
+ * Added Rails engine in order to support auto-load of duration locale files
7
+ * Added numeric macros
8
+ * Added TimeUnit wrappers such as: Hours, Weeks etc.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in xduration.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jose Peleteiro
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ xduration
2
+ =============
3
+
4
+ Duration is an immutable type that represents some amount of time with accuracy in seconds.
5
+
6
+ A lot of the code and inspirations is borrowed from [duration](http://rubyforge.org/projects/duration)
7
+ lib, which is a **mutable** Duration type with lot more features but lack of tests.
8
+
9
+
10
+ Features
11
+ --------
12
+
13
+ * Representation of time in weeks, days, hours, minutes and seconds.
14
+ * Construtor can receive the amount of time in seconds or a Hash with unit and amount of time.
15
+ * Format method to display the time with i18n support.
16
+ * Mongoid serialization support. Use `require 'duration/mongoid'`.
17
+ * Tested on mri 1.8.7, ree 1.8.7, mri 1.9.2, jruby and rubinius. Kudos to rvm!
18
+
19
+ Extras
20
+ ---------
21
+
22
+ ### Macros
23
+
24
+ `require 'duration/macros'`
25
+
26
+ ```ruby
27
+ 1.day + 4.hours
28
+ 10.am
29
+ ```
30
+
31
+ `require 'duration/time_units'`
32
+
33
+ ```ruby
34
+ weeks = Weeks.new(6)
35
+ years = Years.new(6)
36
+ ```
37
+
38
+
39
+ Show me the code
40
+ ----------------
41
+
42
+ ### constructor
43
+
44
+ Duration.new(100) => #<Duration: minutes=1, seconds=40, total=100>
45
+ Duration.new(:hours => 5, :minutes => 70) => #<Duration: hours=6, minutes=10, total=22200>
46
+
47
+ ### format
48
+
49
+ Duration.new(:weeks => 3, :days => 1).format("%w %~w and %d %~d") => "3 weeks and 1 day"
50
+ Duration.new(:weeks => 1, :days => 20).format("%w %~w and %d %~d") => "3 weeks and 6 days"
51
+
52
+ ### iso 8601 [more info](http://en.wikipedia.org/wiki/ISO_8601#Durations)
53
+
54
+ Duration.new(:weeks => 1, :days => 20).iso8601 => "P3W6DT0H0M0S"
55
+
56
+ ### Mongoid support
57
+ The current version of this gem supports Mongoid >= [2.1.0](https://github.com/mongoid/mongoid).
58
+
59
+ require 'duration/mongoid'
60
+
61
+ class MyModel
62
+ include Mongoid::Document
63
+ field :duration, type => Duration
64
+ end
65
+
66
+
67
+ Note on Patches/Pull Requests
68
+ -----------------------------
69
+
70
+ * Fork the project.
71
+ * Make your feature addition or bug fix.
72
+ * Add tests for it. This is important so I don't break it in a
73
+ future version unintentionally.
74
+ * Commit, do not mess with rakefile, version, or history.
75
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
76
+ * Send me a pull request. Bonus points for topic branches.
77
+
78
+
79
+ License
80
+ ---------
81
+ Copyright (c) 2010 Jose Peleteiro
82
+
83
+ Permission is hereby granted, free of charge, to any person obtaining
84
+ a copy of this software and associated documentation files (the
85
+ "Software"), to deal in the Software without restriction, including
86
+ without limitation the rights to use, copy, modify, merge, publish,
87
+ distribute, sublicense, and/or sell copies of the Software, and to
88
+ permit persons to whom the Software is furnished to do so, subject to
89
+ the following conditions:
90
+
91
+ The above copyright notice and this permission notice shall be
92
+ included in all copies or substantial portions of the Software.
93
+
94
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
95
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
96
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
97
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
98
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
99
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
100
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :default => :test
5
+
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'lib' << 'test'
9
+ test.pattern = 'test/**/test_*.rb'
10
+ test.verbose = true
11
+ end
12
+
13
+ require 'yard'
14
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,16 @@
1
+ da:
2
+ ruby_duration:
3
+ second: sekond
4
+ seconds: sekonder
5
+ minute: minut
6
+ minutes: minutter
7
+ hour: time
8
+ hours: timer
9
+ day: dag
10
+ days: dage
11
+ week: uge
12
+ weeks: uges
13
+ month: måned
14
+ months: måneder
15
+ year: år
16
+ years: år
@@ -0,0 +1,16 @@
1
+ pt:
2
+ ruby_duration:
3
+ second: segundo
4
+ seconds: segundos
5
+ minute: minuto
6
+ minutes: minutos
7
+ hour: hora
8
+ hours: horas
9
+ day: dia
10
+ days: dias
11
+ week: semana
12
+ weeks: semanas
13
+ month: mes
14
+ months: meses
15
+ year: ano
16
+ years: anos
@@ -0,0 +1,11 @@
1
+ def Fixnum
2
+ def am
3
+ raise "AM, number must be 0-12" if self < 0 || self > 12
4
+ Time.parse("#{self}:00:00")
5
+ end
6
+
7
+ def pm
8
+ raise "PM, number must be 0-12" if self < 0 || self > 12
9
+ Time.parse("#{self+12}:00:00")
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require 'duration/fixnum'
2
+ require 'duration/numeric'
@@ -0,0 +1,36 @@
1
+ require "#{File.dirname(__FILE__)}/../duration"
2
+ require "mongoid/fields"
3
+
4
+ # Mongoid serialization support for Duration type.
5
+ module Mongoid
6
+ module Fields
7
+ class Duration
8
+ include Mongoid::Fields::Serializable
9
+
10
+ # Deserialize a Duration given the amount of seconds stored by Mongodb
11
+ #
12
+ # @param [Integer, nil] duration in seconds
13
+ # @return [Duration] deserialized Duration
14
+ def deserialize(seconds)
15
+ return if !seconds
16
+ ::Duration.new(seconds)
17
+ end
18
+
19
+ # Serialize a Duration or a Hash (with duration units) or a amount of seconds to
20
+ # a BSON serializable type.
21
+ #
22
+ # @param [Duration, Hash, Integer] value
23
+ # @return [Integer] duration in seconds
24
+ def serialize(value)
25
+ return if value.blank?
26
+ if value.is_a?(Hash)
27
+ value.delete_if{|k, v| v.blank? || !::Duration::UNITS.include?(k.to_sym)}
28
+ return if value.blank?
29
+ ::Duration.new(value).to_i
30
+ elsif value.respond_to?(:to_i)
31
+ value.to_i
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,60 @@
1
+ require 'duration/time_units'
2
+
3
+ class Numeric
4
+ def duration
5
+ Duration.new self
6
+ end
7
+
8
+ def dyears
9
+ self == 1 ? Year.new : Years.new(self)
10
+ end
11
+ def dyear
12
+ self.dyears
13
+ end
14
+
15
+ def dmonths
16
+ self == 1 ? Month.new : Months.new(self)
17
+ end
18
+ def dmonth
19
+ self.dmonths
20
+ end
21
+
22
+ def dweeks
23
+ self == 1 ? Week.new : Weeks.new(self)
24
+ end
25
+ def dweek
26
+ self.dweeks
27
+ end
28
+
29
+ def ddays
30
+ self == 1 ? Day.new : Days.new(self)
31
+ end
32
+ def dday
33
+ self.ddays
34
+ end
35
+
36
+ def dhours
37
+ self == 1 ? Hour.new : Hours.new(self)
38
+ end
39
+ def dhour
40
+ self.dhours
41
+ end
42
+
43
+ def dminutes
44
+ self == 1 ? Minute.new : Minutes.new(self)
45
+ end
46
+ def dminute
47
+ self.dminutes
48
+ end
49
+
50
+ def dseconds
51
+ self == 1 ? Second.new : Seconds.new(self)
52
+ end
53
+ def dsecond
54
+ self.dseconds
55
+ end
56
+
57
+ def is_multiple_of?(num)
58
+ self % num == 0
59
+ end
60
+ end
@@ -0,0 +1,9 @@
1
+ module RubyDuration
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ initializer 'duration setup' do
5
+ I18n.load_path << Dir[Rails.root.join('config', 'locales', 'duration', '*.yml')]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ class Days < Duration
2
+ def initialize number = 1
3
+ super(:days => number)
4
+ end
5
+ end
6
+
7
+ class Day < Days
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
12
+
@@ -0,0 +1,11 @@
1
+ class Decades < Duration
2
+ def initialize number = 1
3
+ super(:decaded => number)
4
+ end
5
+ end
6
+
7
+ class Decade < Decades
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class Hours < Duration
2
+ def initialize number = 1
3
+ super(:hours => number)
4
+ end
5
+ end
6
+
7
+ class Hour < Hours
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
12
+
@@ -0,0 +1,12 @@
1
+ class Minutes < Duration
2
+ def initialize number = 1
3
+ super(:minutes => number)
4
+ end
5
+ end
6
+
7
+ class Minute < Minutes
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
12
+
@@ -0,0 +1,12 @@
1
+ class Months < Duration
2
+ def initialize number = 1
3
+ super(:months => number)
4
+ end
5
+ end
6
+
7
+ class Month < Months
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
12
+
@@ -0,0 +1,11 @@
1
+ class Seconds < Duration
2
+ def initialize number = 1
3
+ super(:seconds => number)
4
+ end
5
+ end
6
+
7
+ class Second < Seconds
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class Weeks < Duration
2
+ def initialize number = 1
3
+ super(:weeks => number)
4
+ end
5
+ end
6
+
7
+ class Week < Weeks
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class Years < Duration
2
+ def initialize number = 1
3
+ super(:years => number)
4
+ end
5
+ end
6
+
7
+ class Year < Years
8
+ def initialize
9
+ super(1)
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ %w{second minute hour day week month year decade}.each do |unit|
2
+ require "duration/time_units/#{unit}"
3
+ end
@@ -0,0 +1,3 @@
1
+ class Duration
2
+ VERSION = "2.2.1"
3
+ end
data/lib/duration.rb ADDED
@@ -0,0 +1,240 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'i18n'
3
+ require 'active_support/core_ext'
4
+
5
+ if defined?(Rails) && Rails::VERSION::STRING >= '3.1'
6
+ require 'duration/rails/engine'
7
+ end
8
+
9
+ # Duration objects are simple mechanisms that allow you to operate on durations
10
+ # of time. They allow you to know how much time has passed since a certain
11
+ # point in time, or they can tell you how much time something is (when given as
12
+ # seconds) in different units of time measurement. Durations would particularly
13
+ # be useful for those scripts or applications that allow you to know the uptime
14
+ # of themselves or perhaps provide a countdown until a certain event.
15
+ class Duration
16
+ include Comparable
17
+
18
+ UNITS = [:seconds, :minutes, :hours, :days, :weeks, :months, :years]
19
+
20
+ MULTIPLES = {:seconds => 1,
21
+ :minutes => 60,
22
+ :hours => 3600,
23
+ :days => 86400,
24
+ :weeks => 604800,
25
+ :months => 2592000, # 30 days
26
+ :years => 31557600, # 365.25 days
27
+ :second => 1,
28
+ :minute => 60,
29
+ :hour => 3600,
30
+ :day => 86400,
31
+ :week => 604800,
32
+ :month => 2592000,
33
+ :year => 31557600}
34
+
35
+ attr_reader :years, :months, :weeks, :days, :hours, :minutes, :seconds, :total
36
+
37
+ # Initialize a duration. 'args' can be a hash or anything else. If a hash is
38
+ # passed, it will be scanned for a key=>value pair of time units such as those
39
+ # listed in the Duration::UNITS array or Duration::MULTIPLES hash.
40
+ #
41
+ # If anything else except a hash is passed, #to_i is invoked on that object
42
+ # and expects that it return the number of seconds desired for the duration.
43
+ def initialize(args = 0)
44
+ if args.kind_of?(Hash)
45
+ @seconds = 0
46
+ MULTIPLES.each do |unit, multiple|
47
+ unit = unit.to_sym
48
+ @seconds += args[unit].to_i * multiple if args.key?(unit)
49
+ end
50
+ else
51
+ @seconds = args.to_i
52
+ end
53
+
54
+ calculate!
55
+ end
56
+
57
+ # Compare this duration to another (or objects that respond to #to_i)
58
+ def <=>(other)
59
+ return false unless other.is_a?(Duration)
60
+ @total <=> other.to_i
61
+ end
62
+
63
+ def +(other)
64
+ Duration.new(@total + other.to_i)
65
+ end
66
+
67
+ def -(other)
68
+ Duration.new(@total - other.to_i)
69
+ end
70
+
71
+ def *(other)
72
+ Duration.new(@total * other.to_i)
73
+ end
74
+
75
+ def /(other)
76
+ Duration.new(@total / other.to_i)
77
+ end
78
+
79
+ def %(other)
80
+ Duration.new(@total % other.to_i)
81
+ end
82
+
83
+ %w(minutes hours days).each do |meth|
84
+ define_method("total_#{meth}") { @total / MULTIPLES[meth.to_sym] }
85
+ end
86
+
87
+ # Formats a duration in ISO8601.
88
+ # @see http://en.wikipedia.org/wiki/ISO_8601#Durations
89
+ def iso8601
90
+ output = 'P'
91
+
92
+ output << "#{weeks}W" if weeks > 0
93
+ output << "#{days}D" if days > 0
94
+ if seconds > 0 || minutes > 0 || hours > 0
95
+ output << 'T'
96
+ output << "#{hours}H" if hours > 0
97
+ output << "#{minutes}M" if minutes > 0
98
+ output << "#{seconds}S" if seconds > 0
99
+ end
100
+
101
+ output
102
+ end
103
+
104
+ # @return true if total is 0
105
+ def blank?
106
+ @total == 0
107
+ end
108
+
109
+ # @return true if total different than 0
110
+ def present?
111
+ !blank?
112
+ end
113
+
114
+ # Format a duration into a human-readable string.
115
+ #
116
+ # %w => weeks
117
+ # %d => days
118
+ # %h => hours
119
+ # %m => minutes
120
+ # %s => seconds
121
+ # %td => total days
122
+ # %th => total hours
123
+ # %tm => total minutes
124
+ # %ts => total seconds
125
+ # %t => total seconds
126
+ # %H => zero-padded hours
127
+ # %M => zero-padded minutes
128
+ # %S => zero-padded seconds
129
+ # %~s => locale-dependent "seconds" terminology
130
+ # %~m => locale-dependent "minutes" terminology
131
+ # %~h => locale-dependent "hours" terminology
132
+ # %~d => locale-dependent "days" terminology
133
+ # %~w => locale-dependent "weeks" terminology
134
+ # %tdu => total days with locale-dependent unit
135
+ # %thu => total hours with locale-dependent unit
136
+ # %tmu => total minutes with locale-dependent unit
137
+ # %tsu => total seconds with locale-dependent unit
138
+ #
139
+ # You can also use the I18n support.
140
+ # The %~s, %~m, %~h, %~d and %~w can be translated with I18n.
141
+ # If you are using Ruby on Rails, the support is ready out of the box, so just change your locale file. Otherwise you can try:
142
+ #
143
+ # I18n.load_path << "path/to/your/locale"
144
+ # I18n.locale = :your_locale
145
+ #
146
+ # And you must use the following structure (example) for your locale file:
147
+ # pt:
148
+ # ruby_duration:
149
+ # second: segundo
150
+ # seconds: segundos
151
+ # minute: minuto
152
+ # minutes: minutos
153
+ # hour: hora
154
+ # hours: horas
155
+ # day: dia
156
+ # days: dias
157
+ # week: semana
158
+ # weeks: semanas
159
+ #
160
+ def format(format_str)
161
+ identifiers = {
162
+ 'y' => @years,
163
+ 'o' => @months,
164
+ 'w' => @weeks,
165
+ 'd' => @days,
166
+ 'h' => @hours,
167
+ 'm' => @minutes,
168
+ 's' => @seconds,
169
+ 'ty' => Proc.new { total_years },
170
+ 'to' => Proc.new { total_months },
171
+ 'tw' => Proc.new { total_weeks },
172
+ 'td' => Proc.new { total_days },
173
+ 'th' => Proc.new { total_hours },
174
+ 'tm' => Proc.new { total_minutes },
175
+ 'ts' => @total,
176
+ 't' => @total,
177
+ 'H' => @hours.to_s.rjust(2, '0'),
178
+ 'M' => @minutes.to_s.rjust(2, '0'),
179
+ 'S' => @seconds.to_s.rjust(2, '0'),
180
+ '~s' => i18n_for(:second),
181
+ '~m' => i18n_for(:minute),
182
+ '~h' => i18n_for(:hour),
183
+ '~d' => i18n_for(:day),
184
+ '~w' => i18n_for(:week),
185
+ '~o' => i18n_for(:month),
186
+ '~y' => i18n_for(:year),
187
+ 'tyr'=> Proc.new { "#{total_years} #{i18n_for(:total_year)}"},
188
+ 'tmo'=> Proc.new { "#{total_months} #{i18n_for(:total_month)}"},
189
+ 'twk'=> Proc.new { "#{total_weeks} #{i18n_for(:total_week)}"},
190
+ 'tdu'=> Proc.new { "#{total_days} #{i18n_for(:total_day)}"},
191
+ 'thu'=> Proc.new { "#{total_hours} #{i18n_for(:total_hour)}"},
192
+ 'tmu'=> Proc.new { "#{total_minutes} #{i18n_for(:total_minute)}"},
193
+ 'tsu'=> Proc.new { "#{total} #{i18n_for(:total)}"}
194
+ }
195
+
196
+ format_str.gsub(/%?%(y|o|w|d|h|m|s|t([ydhms]u?)?|H|M|S|~(?:s|m|h|d|w|o|y))/) do |match|
197
+ match['%%'] ? match : (identifiers[match[1..-1]].class == Proc ? identifiers[match[1..-1]].call : identifiers[match[1..-1]])
198
+ end.gsub('%%', '%')
199
+ end
200
+
201
+ alias_method :to_i, :total
202
+ alias_method :strftime, :format
203
+
204
+ private
205
+
206
+ # Calculates the duration from seconds and figures out what the actual
207
+ # durations are in specific units. This method is called internally, and
208
+ # does not need to be called by user code.
209
+ def calculate!
210
+ multiples = [MULTIPLES[:years], MULTIPLES[:months], MULTIPLES[:weeks], MULTIPLES[:days], MULTIPLES[:hours], MULTIPLES[:minutes], MULTIPLES[:seconds]]
211
+ units = []
212
+ @total = @seconds.to_f.round
213
+ multiples.inject(@total) do |total, multiple|
214
+ # Divide into largest unit
215
+ units << total / multiple
216
+ total % multiple # The remainder will be divided as the next largest
217
+ end
218
+
219
+ # Gather the divided units
220
+ @years, @months, @weeks, @days, @hours, @minutes, @seconds = units
221
+ end
222
+
223
+ def i18n_for(singular)
224
+ if singular == :total
225
+ fn_name = :total
226
+ singular = :second
227
+ plural = 'seconds'
228
+ elsif singular.to_s.start_with?('total_')
229
+ fn_name = "#{singular}s"
230
+ singular = singular.to_s['total_'.length..-1]
231
+ plural = "#{singular}s"
232
+ else
233
+ plural = "#{singular}s"
234
+ fn_name = plural
235
+ end
236
+ label = send(fn_name) == 1 ? singular : plural
237
+
238
+ I18n.t(label, :scope => :ruby_duration, :default => label.to_s)
239
+ end
240
+ end