emery 0.0.2 → 0.0.3
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.
- checksums.yaml +4 -4
- data/lib/emery/dataclass.rb +63 -65
- data/lib/emery/enum.rb +73 -75
- data/lib/emery/jsoner.rb +162 -164
- data/lib/emery/tod.rb +202 -204
- data/lib/emery/type.rb +149 -151
- data/lib/emery.rb +1 -2
- data/test/dataclass_test.rb +80 -83
- data/test/enum_test.rb +29 -33
- data/test/jsoner_test.rb +189 -192
- data/test/tod_test.rb +23 -28
- data/test/type_test.rb +153 -155
- metadata +1 -1
data/lib/emery/tod.rb
CHANGED
@@ -25,238 +25,236 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
25
25
|
|
26
26
|
=end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
include Comparable
|
28
|
+
class TimeOfDay
|
29
|
+
include Comparable
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
def self.jsoner_deserialize(json_value)
|
32
|
+
TimeOfDay.parse(T.check(String, json_value))
|
33
|
+
end
|
34
|
+
def self.jsoner_serialize(value)
|
35
|
+
T.check(TimeOfDay, value).to_s
|
36
|
+
end
|
38
37
|
|
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
|
-
|
38
|
+
attr_reader :hour, :minute, :second, :second_of_day
|
39
|
+
alias_method :min, :minute
|
40
|
+
alias_method :sec, :second
|
41
|
+
alias_method :to_i, :second_of_day
|
42
|
+
|
43
|
+
PARSE_24H_REGEX = /
|
44
|
+
\A
|
45
|
+
([01]?\d|2[0-4])
|
46
|
+
:?
|
47
|
+
([0-5]\d)?
|
48
|
+
:?
|
49
|
+
([0-5]\d)?
|
50
|
+
\z
|
51
|
+
/x
|
52
|
+
|
53
|
+
PARSE_12H_REGEX = /
|
54
|
+
\A
|
55
|
+
(0?\d|1[0-2])
|
56
|
+
:?
|
57
|
+
([0-5]\d)?
|
58
|
+
:?
|
59
|
+
([0-5]\d)?
|
60
|
+
\s*
|
61
|
+
([ap])
|
62
|
+
\.?
|
63
|
+
\s*
|
64
|
+
m?
|
65
|
+
\.?
|
66
|
+
\z
|
67
|
+
/x
|
68
|
+
|
69
|
+
WORDS = {
|
70
|
+
"noon" => "12pm".freeze,
|
71
|
+
"midnight" => "12am".freeze
|
72
|
+
}
|
73
|
+
|
74
|
+
NUM_SECONDS_IN_DAY = 86400
|
75
|
+
NUM_SECONDS_IN_HOUR = 3600
|
76
|
+
NUM_SECONDS_IN_MINUTE = 60
|
77
|
+
|
78
|
+
FORMATS = {
|
79
|
+
short: "%-l:%M %P".freeze,
|
80
|
+
medium: "%-l:%M:%S %P".freeze,
|
81
|
+
time: "%H:%M".freeze
|
82
|
+
}
|
83
|
+
|
84
|
+
def initialize(h, m=0, s=0)
|
85
|
+
@hour = Integer(h)
|
86
|
+
@minute = Integer(m)
|
87
|
+
@second = Integer(s)
|
88
|
+
|
89
|
+
raise ArgumentError, "hour must be between 0 and 24" unless (0..24).include?(@hour)
|
90
|
+
if @hour == 24 && (@minute != 0 || @second != 0)
|
91
|
+
raise ArgumentError, "hour can only be 24 when minute and second are 0"
|
92
|
+
end
|
93
|
+
raise ArgumentError, "minute must be between 0 and 59" unless (0..59).include?(@minute)
|
94
|
+
raise ArgumentError, "second must be between 0 and 59" unless (0..59).include?(@second)
|
96
95
|
|
97
|
-
|
96
|
+
@second_of_day = @hour * 60 * 60 + @minute * 60 + @second
|
98
97
|
|
99
|
-
|
100
|
-
|
98
|
+
freeze # TimeOfDay instances are value objects
|
99
|
+
end
|
101
100
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
101
|
+
def <=>(other)
|
102
|
+
return unless other.respond_to?(:second_of_day)
|
103
|
+
@second_of_day <=> other.second_of_day
|
104
|
+
end
|
106
105
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
106
|
+
# Rounding to the given nearest number of seconds
|
107
|
+
def round(round_sec = 1)
|
108
|
+
down = self - (self.to_i % round_sec)
|
109
|
+
up = down + round_sec
|
111
110
|
|
112
|
-
|
113
|
-
|
111
|
+
difference_down = self - down
|
112
|
+
difference_up = up - self
|
114
113
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
114
|
+
if (difference_down < difference_up)
|
115
|
+
return down
|
116
|
+
else
|
117
|
+
return up
|
120
118
|
end
|
119
|
+
end
|
121
120
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
121
|
+
# Formats identically to Time#strftime
|
122
|
+
def strftime(format_string)
|
123
|
+
# Special case 2400 because strftime will load TimeOfDay into Time which
|
124
|
+
# will convert 24 to 0
|
125
|
+
format_string = format_string.gsub(/%H|%k/, '24') if @hour == 24
|
126
|
+
Time.local(2000,1,1, @hour, @minute, @second).strftime(format_string)
|
127
|
+
end
|
129
128
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
else
|
135
|
-
strftime(formatter)
|
136
|
-
end
|
129
|
+
def to_formatted_s(format = :default)
|
130
|
+
if formatter = FORMATS[format]
|
131
|
+
if formatter.respond_to?(:call)
|
132
|
+
formatter.call(self).to_s
|
137
133
|
else
|
138
|
-
strftime
|
134
|
+
strftime(formatter)
|
139
135
|
end
|
136
|
+
else
|
137
|
+
strftime "%H:%M:%S"
|
140
138
|
end
|
141
|
-
|
142
|
-
|
143
|
-
def value_for_database
|
144
|
-
to_s
|
145
|
-
end
|
139
|
+
end
|
140
|
+
alias_method :to_s, :to_formatted_s
|
146
141
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
TimeOfDay.from_second_of_day @second_of_day + num_seconds
|
151
|
-
end
|
142
|
+
def value_for_database
|
143
|
+
to_s
|
144
|
+
end
|
152
145
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
else
|
159
|
-
TimeOfDay.from_second_of_day @second_of_day - other
|
160
|
-
end
|
161
|
-
end
|
146
|
+
# Return a new TimeOfDay num_seconds greater than self. It will wrap around
|
147
|
+
# at midnight.
|
148
|
+
def +(num_seconds)
|
149
|
+
TimeOfDay.from_second_of_day @second_of_day + num_seconds
|
150
|
+
end
|
162
151
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
152
|
+
# Return a new TimeOfDay num_seconds less than self. It will wrap around
|
153
|
+
# at midnight.
|
154
|
+
def -(other)
|
155
|
+
if other.instance_of?(TimeOfDay)
|
156
|
+
TimeOfDay.from_second_of_day @second_of_day - other.second_of_day
|
157
|
+
else
|
158
|
+
TimeOfDay.from_second_of_day @second_of_day - other
|
167
159
|
end
|
160
|
+
end
|
168
161
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
return new 24 if second_of_day == NUM_SECONDS_IN_DAY
|
175
|
-
remaining_seconds = second_of_day % NUM_SECONDS_IN_DAY
|
176
|
-
hour = remaining_seconds / NUM_SECONDS_IN_HOUR
|
177
|
-
remaining_seconds -= hour * NUM_SECONDS_IN_HOUR
|
178
|
-
minute = remaining_seconds / NUM_SECONDS_IN_MINUTE
|
179
|
-
remaining_seconds -= minute * NUM_SECONDS_IN_MINUTE
|
180
|
-
new hour, minute, remaining_seconds
|
181
|
-
end
|
182
|
-
class << self
|
183
|
-
alias :from_i :from_second_of_day
|
184
|
-
end
|
162
|
+
# Returns a Time instance on date using self as the time of day
|
163
|
+
# Optional time_zone will build time in that zone
|
164
|
+
def on(date, time_zone=Tod::TimeOfDay.time_zone)
|
165
|
+
time_zone.local date.year, date.month, date.day, @hour, @minute, @second
|
166
|
+
end
|
185
167
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
# If no block is given an ArgumentError is raised if try_parse returns nil.
|
203
|
-
def self.parse(tod_string)
|
204
|
-
try_parse(tod_string) || (block_given? ? yield(tod_string) : raise(ArgumentError, "Invalid time of day string"))
|
205
|
-
end
|
168
|
+
# Build a new TimeOfDay instance from second_of_day
|
169
|
+
#
|
170
|
+
# TimeOfDay.from_second_of_day(3600) == TimeOfDay.new(1) # => true
|
171
|
+
def self.from_second_of_day(second_of_day)
|
172
|
+
second_of_day = Integer(second_of_day)
|
173
|
+
return new 24 if second_of_day == NUM_SECONDS_IN_DAY
|
174
|
+
remaining_seconds = second_of_day % NUM_SECONDS_IN_DAY
|
175
|
+
hour = remaining_seconds / NUM_SECONDS_IN_HOUR
|
176
|
+
remaining_seconds -= hour * NUM_SECONDS_IN_HOUR
|
177
|
+
minute = remaining_seconds / NUM_SECONDS_IN_MINUTE
|
178
|
+
remaining_seconds -= minute * NUM_SECONDS_IN_MINUTE
|
179
|
+
new hour, minute, remaining_seconds
|
180
|
+
end
|
181
|
+
class << self
|
182
|
+
alias :from_i :from_second_of_day
|
183
|
+
end
|
206
184
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
185
|
+
# Build a TimeOfDay instance from string
|
186
|
+
#
|
187
|
+
# Strings only need to contain an hour. Minutes, seconds, AM or PM, and colons
|
188
|
+
# are all optional.
|
189
|
+
# TimeOfDay.parse "8" # => 08:00:00
|
190
|
+
# TimeOfDay.parse "8am" # => 08:00:00
|
191
|
+
# TimeOfDay.parse "8pm" # => 20:00:00
|
192
|
+
# TimeOfDay.parse "8p" # => 20:00:00
|
193
|
+
# TimeOfDay.parse "9:30" # => 09:30:00
|
194
|
+
# TimeOfDay.parse "15:30" # => 15:30:00
|
195
|
+
# TimeOfDay.parse "3:30pm" # => 15:30:00
|
196
|
+
# TimeOfDay.parse "1230" # => 12:30:00
|
197
|
+
# TimeOfDay.parse "3:25:58" # => 03:25:58
|
198
|
+
# TimeOfDay.parse "515p" # => 17:15:00
|
199
|
+
# TimeOfDay.parse "151253" # => 15:12:53
|
200
|
+
# You can give a block, that is called with the input if the string is not parsable.
|
201
|
+
# If no block is given an ArgumentError is raised if try_parse returns nil.
|
202
|
+
def self.parse(tod_string)
|
203
|
+
try_parse(tod_string) || (block_given? ? yield(tod_string) : raise(ArgumentError, "Invalid time of day string"))
|
204
|
+
end
|
223
205
|
|
224
|
-
|
225
|
-
|
226
|
-
|
206
|
+
# Same as parse(), but return nil if not parsable (instead of raising an error)
|
207
|
+
# TimeOfDay.try_parse "8am" # => 08:00:00
|
208
|
+
# TimeOfDay.try_parse "" # => nil
|
209
|
+
# TimeOfDay.try_parse "abc" # => nil
|
210
|
+
def self.try_parse(tod_string)
|
211
|
+
tod_string = tod_string.to_s
|
212
|
+
tod_string = tod_string.strip
|
213
|
+
tod_string = tod_string.downcase
|
214
|
+
tod_string = WORDS[tod_string] || tod_string
|
215
|
+
if PARSE_24H_REGEX =~ tod_string || PARSE_12H_REGEX =~ tod_string
|
216
|
+
hour, minute, second, a_or_p = $1.to_i, $2.to_i, $3.to_i, $4
|
217
|
+
if hour == 12 && a_or_p == "a"
|
218
|
+
hour = 0
|
219
|
+
elsif hour < 12 && a_or_p == "p"
|
220
|
+
hour += 12
|
227
221
|
end
|
228
|
-
end
|
229
222
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
def self.parsable?(tod_string)
|
234
|
-
!!try_parse(tod_string)
|
223
|
+
new hour, minute, second
|
224
|
+
else
|
225
|
+
nil
|
235
226
|
end
|
227
|
+
end
|
236
228
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
229
|
+
# Determine if a string is parsable into a TimeOfDay instance
|
230
|
+
# TimeOfDay.parsable? "8am" # => true
|
231
|
+
# TimeOfDay.parsable? "abc" # => false
|
232
|
+
def self.parsable?(tod_string)
|
233
|
+
!!try_parse(tod_string)
|
234
|
+
end
|
241
235
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
# get hour, minute and second and construct new TimeOfDay object
|
247
|
-
::Tod::TimeOfDay.new(time_of_day[4], time_of_day[5], time_of_day[6])
|
248
|
-
else
|
249
|
-
# return nil, if input is not parsable
|
250
|
-
Tod::TimeOfDay(time_of_day){}
|
251
|
-
end
|
252
|
-
time_of_day.to_s if time_of_day
|
253
|
-
end
|
236
|
+
# If ActiveSupport TimeZone is available and set use current time zone else return Time
|
237
|
+
def self.time_zone
|
238
|
+
(Time.respond_to?(:zone) && Time.zone) || Time
|
239
|
+
end
|
254
240
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
241
|
+
def self.dump(time_of_day)
|
242
|
+
time_of_day =
|
243
|
+
if time_of_day.is_a? Hash
|
244
|
+
# rails multiparam attribute
|
245
|
+
# get hour, minute and second and construct new TimeOfDay object
|
246
|
+
::Tod::TimeOfDay.new(time_of_day[4], time_of_day[5], time_of_day[6])
|
247
|
+
else
|
248
|
+
# return nil, if input is not parsable
|
249
|
+
Tod::TimeOfDay(time_of_day){}
|
250
|
+
end
|
251
|
+
time_of_day.to_s if time_of_day
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.load(time)
|
255
|
+
if time && !time.to_s.empty?
|
256
|
+
return ::Tod::TimeOfDay.new(24) if time.respond_to?(:day) && time.day == 2 && time.hour == 0 && time.min == 0 && time.sec == 0
|
257
|
+
::Tod::TimeOfDay(time)
|
260
258
|
end
|
261
259
|
end
|
262
260
|
end
|