xduration 2.2.1

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.
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