Skip to content

Commit 6aeeb22

Browse files
authored
Adds precision option for datetime fields, reverts new default functionality from v4.1.1 and makes it opt-in (#71)
1 parent f0b1a7f commit 6aeeb22

File tree

5 files changed

+152
-11
lines changed

5 files changed

+152
-11
lines changed

CHANGELOG.MD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## Subroutine 4.1.4
2+
3+
Fields using the time/timestamp/datetime caster will now default back to the old behavior, and use a `precision:` option to opt-in to the new behavior introduced in `v4.1.1`.
4+
5+
`precision: :seconds` will retain the old behavior of always parsing to a new Time object
6+
with floored sub-second precision, but applied more forcefully than before as it would have parsed whatever you passed to it. (This is the default, now.)
7+
8+
`precision: :high` will now use the new functionality of re-using Time objects when they
9+
are passed in, or if not parsing exactly the provided string as to a Time object.
10+
111
## Subroutine 4.1.1
212

313
Fields using the time/timestamp/datetime caster will now return exactly the passed in value

lib/subroutine/type_caster.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
require 'time'
55
require 'bigdecimal'
66
require 'securerandom'
7+
require 'active_support/core_ext/date_time/acts_like'
8+
require 'active_support/core_ext/date_time/calculations'
79
require 'active_support/core_ext/object/acts_like'
810
require 'active_support/core_ext/object/blank'
911
require 'active_support/core_ext/object/try'
1012
require 'active_support/core_ext/array/wrap'
1113
require 'active_support/core_ext/time/acts_like'
14+
require 'active_support/core_ext/time/calculations'
1215

1316
module Subroutine
1417
module TypeCaster
@@ -117,13 +120,23 @@ def self.cast(value, options = {})
117120
::Date.parse(String(value))
118121
end
119122

120-
::Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value, _options = {}|
123+
::Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value, options = {}|
121124
next nil unless value.present?
122125

123-
if value.try(:acts_like?, :time)
124-
value
125-
else
126-
::Time.parse(String(value))
126+
if options[:precision] == :high
127+
if value.try(:acts_like?, :time)
128+
value.to_time
129+
else
130+
::Time.parse(String(value))
131+
end
132+
else # precision == :seconds
133+
time = if value.try(:acts_like?, :time)
134+
value.to_time
135+
else
136+
::Time.parse(String(value))
137+
end
138+
139+
time.change(usec: 0)
127140
end
128141
end
129142

lib/subroutine/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Subroutine
44

55
MAJOR = 4
66
MINOR = 1
7-
PATCH = 3
7+
PATCH = 4
88
PRE = nil
99

1010
VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")

test/subroutine/type_caster_test.rb

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def test_date_inputs
269269
assert_nil op.date_input
270270
end
271271

272-
def test_time_inputs
272+
def test_time_inputs__with_seconds_precision
273273
op.time_input = nil
274274
assert_nil op.time_input
275275

@@ -284,22 +284,139 @@ def test_time_inputs
284284
assert_equal 0, op.time_input.min
285285
assert_equal 0, op.time_input.sec
286286

287+
op.time_input = ::DateTime.new(2022, 12, 22)
288+
assert_equal ::Time, op.time_input.class
289+
refute_equal ::DateTime, op.time_input.class
290+
291+
assert_equal 0, op.time_input.utc_offset
292+
assert_equal 2022, op.time_input.year
293+
assert_equal 12, op.time_input.month
294+
assert_equal 22, op.time_input.day
295+
assert_equal 0, op.time_input.hour
296+
assert_equal 0, op.time_input.min
297+
assert_equal 0, op.time_input.sec
298+
287299
op.time_input = '2023-05-05T10:00:30.123456Z'
288300
assert_equal ::Time, op.time_input.class
289301
refute_equal ::DateTime, op.time_input.class
290302

303+
assert_equal 0, op.time_input.utc_offset
304+
assert_equal 2023, op.time_input.year
305+
assert_equal 5, op.time_input.month
306+
assert_equal 5, op.time_input.day
307+
assert_equal 10, op.time_input.hour
308+
assert_equal 0, op.time_input.min
309+
assert_equal 30, op.time_input.sec
310+
assert_equal 0, op.time_input.usec
311+
312+
op.time_input = '2023-05-05T10:00:30Z'
313+
assert_equal ::Time, op.time_input.class
314+
assert_equal 0, op.time_input.utc_offset
291315
assert_equal 2023, op.time_input.year
292316
assert_equal 5, op.time_input.month
293317
assert_equal 5, op.time_input.day
294318
assert_equal 10, op.time_input.hour
295319
assert_equal 0, op.time_input.min
296320
assert_equal 30, op.time_input.sec
297-
assert_equal 123456, op.time_input.usec
321+
assert_equal 0, op.time_input.usec
298322

299-
time = Time.at(1678741605.123456)
323+
op.time_input = '2024-11-11T16:42:23.246+0100'
324+
assert_equal ::Time, op.time_input.class
325+
assert_equal 3600, op.time_input.utc_offset
326+
assert_equal 2024, op.time_input.year
327+
assert_equal 11, op.time_input.month
328+
assert_equal 11, op.time_input.day
329+
assert_equal 16, op.time_input.hour
330+
assert_equal 42, op.time_input.min
331+
assert_equal 23, op.time_input.sec
332+
assert_equal 0, op.time_input.usec
333+
334+
time = Time.at(1678741605.123456).utc
300335
op.time_input = time
301-
assert_equal time, op.time_input
302-
assert_equal time.object_id, op.time_input.object_id
336+
refute_equal time, op.time_input
337+
refute_equal time.object_id, op.time_input.object_id
338+
assert_equal 2023, op.time_input.year
339+
assert_equal 3, op.time_input.month
340+
assert_equal 13, op.time_input.day
341+
assert_equal 21, op.time_input.hour
342+
assert_equal 6, op.time_input.min
343+
assert_equal 45, op.time_input.sec
344+
assert_equal 0, op.time_input.usec
345+
end
346+
347+
def test_time_inputs__with_high_precision
348+
op.precise_time_input = nil
349+
assert_nil op.precise_time_input
350+
351+
op.precise_time_input = '2022-12-22'
352+
assert_equal ::Time, op.precise_time_input.class
353+
refute_equal ::DateTime, op.precise_time_input.class
354+
355+
assert_equal 2022, op.precise_time_input.year
356+
assert_equal 12, op.precise_time_input.month
357+
assert_equal 22, op.precise_time_input.day
358+
assert_equal 0, op.precise_time_input.hour
359+
assert_equal 0, op.precise_time_input.min
360+
assert_equal 0, op.precise_time_input.sec
361+
362+
op.precise_time_input = ::DateTime.new(2022, 12, 22)
363+
assert_equal ::Time, op.precise_time_input.class
364+
refute_equal ::DateTime, op.precise_time_input.class
365+
366+
assert_equal 0, op.precise_time_input.utc_offset
367+
assert_equal 2022, op.precise_time_input.year
368+
assert_equal 12, op.precise_time_input.month
369+
assert_equal 22, op.precise_time_input.day
370+
assert_equal 0, op.precise_time_input.hour
371+
assert_equal 0, op.precise_time_input.min
372+
assert_equal 0, op.precise_time_input.sec
373+
374+
op.precise_time_input = '2023-05-05T10:00:30.123456Z'
375+
assert_equal ::Time, op.precise_time_input.class
376+
refute_equal ::DateTime, op.precise_time_input.class
377+
378+
assert_equal 0, op.precise_time_input.utc_offset
379+
assert_equal 2023, op.precise_time_input.year
380+
assert_equal 5, op.precise_time_input.month
381+
assert_equal 5, op.precise_time_input.day
382+
assert_equal 10, op.precise_time_input.hour
383+
assert_equal 0, op.precise_time_input.min
384+
assert_equal 30, op.precise_time_input.sec
385+
assert_equal 123456, op.precise_time_input.usec
386+
387+
op.precise_time_input = '2023-05-05T10:00:30Z'
388+
assert_equal ::Time, op.precise_time_input.class
389+
assert_equal 0, op.precise_time_input.utc_offset
390+
assert_equal 2023, op.precise_time_input.year
391+
assert_equal 5, op.precise_time_input.month
392+
assert_equal 5, op.precise_time_input.day
393+
assert_equal 10, op.precise_time_input.hour
394+
assert_equal 0, op.precise_time_input.min
395+
assert_equal 30, op.precise_time_input.sec
396+
assert_equal 0, op.precise_time_input.usec
397+
398+
op.precise_time_input = '2024-11-11T16:42:23.246+0100'
399+
assert_equal ::Time, op.precise_time_input.class
400+
assert_equal 3600, op.precise_time_input.utc_offset
401+
assert_equal 2024, op.precise_time_input.year
402+
assert_equal 11, op.precise_time_input.month
403+
assert_equal 11, op.precise_time_input.day
404+
assert_equal 16, op.precise_time_input.hour
405+
assert_equal 42, op.precise_time_input.min
406+
assert_equal 23, op.precise_time_input.sec
407+
assert_equal 246000, op.precise_time_input.usec
408+
409+
time = Time.at(1678741605.123456).utc
410+
op.precise_time_input = time
411+
assert_equal time, op.precise_time_input
412+
assert_equal time.object_id, op.precise_time_input.object_id
413+
assert_equal 2023, op.precise_time_input.year
414+
assert_equal 3, op.precise_time_input.month
415+
assert_equal 13, op.precise_time_input.day
416+
assert_equal 21, op.precise_time_input.hour
417+
assert_equal 6, op.precise_time_input.min
418+
assert_equal 45, op.precise_time_input.sec
419+
assert_equal 123456, op.precise_time_input.usec
303420
end
304421

305422
def test_iso_date_inputs

test/support/ops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class TypeCastOp < ::Subroutine::Op
173173
boolean :boolean_input
174174
date :date_input
175175
time :time_input, default: -> { Time.now }
176+
time :precise_time_input, precision: :high
176177
iso_date :iso_date_input
177178
iso_time :iso_time_input
178179
object :object_input

0 commit comments

Comments
 (0)