/
duration.rb
132 lines (116 loc) · 4.33 KB
/
duration.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# -*- encoding: utf-8 -*-
# Duration objects are simple mechanisms that allow you to operate on durations
# of time. They allow you to know how much time has passed since a certain
# point in time, or they can tell you how much time something is (when given as
# seconds) in different units of time measurement. Durations would particularly
# be useful for those scripts or applications that allow you to know the uptime
# of themselves or perhaps provide a countdown until a certain event.
class Duration
include Comparable
# Unit names
UNITS = [:seconds, :minutes, :hours, :days, :weeks]
# Unit labels
UNIT_LABELS = {:second => 'second',
:seconds => 'seconds',
:minute => 'minute',
:minutes => 'minutes',
:hour => 'hour',
:hours => 'hours',
:day => 'day',
:days => 'days',
:week => 'week',
:weeks => 'weeks'}
# Unit multiples
MULTIPLES = {:seconds => 1,
:minutes => 60,
:hours => 3600,
:days => 86400,
:weeks => 604800,
:second => 1,
:minute => 60,
:hour => 3600,
:day => 86400,
:week => 604800}
attr_reader :total, :seconds, :minutes, :hours, :days, :weeks
# Initialize a duration. 'args' can be a hash or anything else. If a hash is
# passed, it will be scanned for a key=>value pair of time units such as those
# listed in the Duration::UNITS array or Duration::MULTIPLES hash.
#
# If anything else except a hash is passed, #to_i is invoked on that object
# and expects that it return the number of seconds desired for the duration.
def initialize(args = 0)
# Two types of arguments are accepted. If it isn't a hash, it's converted
# to an integer.
if args.kind_of?(Hash)
@seconds = 0
MULTIPLES.each do |unit, multiple|
unit = unit.to_sym
@seconds += args[unit].to_i * multiple if args.key?(unit)
end
else
@seconds = args.to_i
end
# Calculate duration
calculate!
end
# Compare this duration to another (or objects that respond to #to_i)
def <=>(other)
@total <=> other.to_i
end
# Format a duration into a human-readable string.
#
# %w => weeks
# %d => days
# %h => hours
# %m => minutes
# %s => seconds
# %t => total seconds
# %H => zero-padded hours
# %M => zero-padded minutes
# %S => zero-padded seconds
# %~s => locale-dependent "seconds" terminology
# %~m => locale-dependent "minutes" terminology
# %~h => locale-dependent "hours" terminology
# %~d => locale-dependent "days" terminology
# %~w => locale-dependent "weeks" terminology
#
def format(format_str)
identifiers = {
'w' => @weeks,
'd' => @days,
'h' => @hours,
'm' => @minutes,
's' => @seconds,
't' => @total,
'H' => @hours.to_s.rjust(2, '0'),
'M' => @minutes.to_s.rjust(2, '0'),
'S' => @seconds.to_s.rjust(2, '0'),
'~s' => @seconds == 1 ? UNIT_LABELS[:second] : UNIT_LABELS[:seconds],
'~m' => @minutes == 1 ? UNIT_LABELS[:minute] : UNIT_LABELS[:minutes],
'~h' => @hours == 1 ? UNIT_LABELS[:hour] : UNIT_LABELS[:hours],
'~d' => @days == 1 ? UNIT_LABELS[:day] : UNIT_LABELS[:days],
'~w' => @weeks == 1 ? UNIT_LABELS[:week] : UNIT_LABELS[:weeks]
}
format_str.gsub(/%?%(w|d|h|m|s|t|H|M|S|~(?:s|m|h|d|w))/) do |match|
match['%%'] ? match : identifiers[match[1..-1]]
end.gsub('%%', '%')
end
alias_method :to_i, :total
alias_method :strftime, :format
private
# Calculates the duration from seconds and figures out what the actual
# durations are in specific units. This method is called internally, and
# does not need to be called by user code.
def calculate!
multiples = [MULTIPLES[:weeks], MULTIPLES[:days], MULTIPLES[:hours], MULTIPLES[:minutes], MULTIPLES[:seconds]]
units = []
@total = @seconds.to_f.round
multiples.inject(@total) do |total, multiple|
# Divide into largest unit
units << total / multiple
total % multiple # The remainder will be divided as the next largest
end
# Gather the divided units
@weeks, @days, @hours, @minutes, @seconds = units
end
end