Skip to content

Commit

Permalink
Adds precision option for datetime fields, reverts new default functi…
Browse files Browse the repository at this point in the history
…onality from v4.1.1 and makes it opt-in (#71)
  • Loading branch information
fusion2004 authored Nov 11, 2024
1 parent f0b1a7f commit 6aeeb22
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 11 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## Subroutine 4.1.4

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

`precision: :seconds` will retain the old behavior of always parsing to a new Time object
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.)

`precision: :high` will now use the new functionality of re-using Time objects when they
are passed in, or if not parsing exactly the provided string as to a Time object.

## Subroutine 4.1.1

Fields using the time/timestamp/datetime caster will now return exactly the passed in value
Expand Down
23 changes: 18 additions & 5 deletions lib/subroutine/type_caster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
require 'time'
require 'bigdecimal'
require 'securerandom'
require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'

module Subroutine
module TypeCaster
Expand Down Expand Up @@ -117,13 +120,23 @@ def self.cast(value, options = {})
::Date.parse(String(value))
end

::Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value, _options = {}|
::Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value, options = {}|
next nil unless value.present?

if value.try(:acts_like?, :time)
value
else
::Time.parse(String(value))
if options[:precision] == :high
if value.try(:acts_like?, :time)
value.to_time
else
::Time.parse(String(value))
end
else # precision == :seconds
time = if value.try(:acts_like?, :time)
value.to_time
else
::Time.parse(String(value))
end

time.change(usec: 0)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/subroutine/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Subroutine

MAJOR = 4
MINOR = 1
PATCH = 3
PATCH = 4
PRE = nil

VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
Expand Down
127 changes: 122 additions & 5 deletions test/subroutine/type_caster_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def test_date_inputs
assert_nil op.date_input
end

def test_time_inputs
def test_time_inputs__with_seconds_precision
op.time_input = nil
assert_nil op.time_input

Expand All @@ -284,22 +284,139 @@ def test_time_inputs
assert_equal 0, op.time_input.min
assert_equal 0, op.time_input.sec

op.time_input = ::DateTime.new(2022, 12, 22)
assert_equal ::Time, op.time_input.class
refute_equal ::DateTime, op.time_input.class

assert_equal 0, op.time_input.utc_offset
assert_equal 2022, op.time_input.year
assert_equal 12, op.time_input.month
assert_equal 22, op.time_input.day
assert_equal 0, op.time_input.hour
assert_equal 0, op.time_input.min
assert_equal 0, op.time_input.sec

op.time_input = '2023-05-05T10:00:30.123456Z'
assert_equal ::Time, op.time_input.class
refute_equal ::DateTime, op.time_input.class

assert_equal 0, op.time_input.utc_offset
assert_equal 2023, op.time_input.year
assert_equal 5, op.time_input.month
assert_equal 5, op.time_input.day
assert_equal 10, op.time_input.hour
assert_equal 0, op.time_input.min
assert_equal 30, op.time_input.sec
assert_equal 0, op.time_input.usec

op.time_input = '2023-05-05T10:00:30Z'
assert_equal ::Time, op.time_input.class
assert_equal 0, op.time_input.utc_offset
assert_equal 2023, op.time_input.year
assert_equal 5, op.time_input.month
assert_equal 5, op.time_input.day
assert_equal 10, op.time_input.hour
assert_equal 0, op.time_input.min
assert_equal 30, op.time_input.sec
assert_equal 123456, op.time_input.usec
assert_equal 0, op.time_input.usec

time = Time.at(1678741605.123456)
op.time_input = '2024-11-11T16:42:23.246+0100'
assert_equal ::Time, op.time_input.class
assert_equal 3600, op.time_input.utc_offset
assert_equal 2024, op.time_input.year
assert_equal 11, op.time_input.month
assert_equal 11, op.time_input.day
assert_equal 16, op.time_input.hour
assert_equal 42, op.time_input.min
assert_equal 23, op.time_input.sec
assert_equal 0, op.time_input.usec

time = Time.at(1678741605.123456).utc
op.time_input = time
assert_equal time, op.time_input
assert_equal time.object_id, op.time_input.object_id
refute_equal time, op.time_input
refute_equal time.object_id, op.time_input.object_id
assert_equal 2023, op.time_input.year
assert_equal 3, op.time_input.month
assert_equal 13, op.time_input.day
assert_equal 21, op.time_input.hour
assert_equal 6, op.time_input.min
assert_equal 45, op.time_input.sec
assert_equal 0, op.time_input.usec
end

def test_time_inputs__with_high_precision
op.precise_time_input = nil
assert_nil op.precise_time_input

op.precise_time_input = '2022-12-22'
assert_equal ::Time, op.precise_time_input.class
refute_equal ::DateTime, op.precise_time_input.class

assert_equal 2022, op.precise_time_input.year
assert_equal 12, op.precise_time_input.month
assert_equal 22, op.precise_time_input.day
assert_equal 0, op.precise_time_input.hour
assert_equal 0, op.precise_time_input.min
assert_equal 0, op.precise_time_input.sec

op.precise_time_input = ::DateTime.new(2022, 12, 22)
assert_equal ::Time, op.precise_time_input.class
refute_equal ::DateTime, op.precise_time_input.class

assert_equal 0, op.precise_time_input.utc_offset
assert_equal 2022, op.precise_time_input.year
assert_equal 12, op.precise_time_input.month
assert_equal 22, op.precise_time_input.day
assert_equal 0, op.precise_time_input.hour
assert_equal 0, op.precise_time_input.min
assert_equal 0, op.precise_time_input.sec

op.precise_time_input = '2023-05-05T10:00:30.123456Z'
assert_equal ::Time, op.precise_time_input.class
refute_equal ::DateTime, op.precise_time_input.class

assert_equal 0, op.precise_time_input.utc_offset
assert_equal 2023, op.precise_time_input.year
assert_equal 5, op.precise_time_input.month
assert_equal 5, op.precise_time_input.day
assert_equal 10, op.precise_time_input.hour
assert_equal 0, op.precise_time_input.min
assert_equal 30, op.precise_time_input.sec
assert_equal 123456, op.precise_time_input.usec

op.precise_time_input = '2023-05-05T10:00:30Z'
assert_equal ::Time, op.precise_time_input.class
assert_equal 0, op.precise_time_input.utc_offset
assert_equal 2023, op.precise_time_input.year
assert_equal 5, op.precise_time_input.month
assert_equal 5, op.precise_time_input.day
assert_equal 10, op.precise_time_input.hour
assert_equal 0, op.precise_time_input.min
assert_equal 30, op.precise_time_input.sec
assert_equal 0, op.precise_time_input.usec

op.precise_time_input = '2024-11-11T16:42:23.246+0100'
assert_equal ::Time, op.precise_time_input.class
assert_equal 3600, op.precise_time_input.utc_offset
assert_equal 2024, op.precise_time_input.year
assert_equal 11, op.precise_time_input.month
assert_equal 11, op.precise_time_input.day
assert_equal 16, op.precise_time_input.hour
assert_equal 42, op.precise_time_input.min
assert_equal 23, op.precise_time_input.sec
assert_equal 246000, op.precise_time_input.usec

time = Time.at(1678741605.123456).utc
op.precise_time_input = time
assert_equal time, op.precise_time_input
assert_equal time.object_id, op.precise_time_input.object_id
assert_equal 2023, op.precise_time_input.year
assert_equal 3, op.precise_time_input.month
assert_equal 13, op.precise_time_input.day
assert_equal 21, op.precise_time_input.hour
assert_equal 6, op.precise_time_input.min
assert_equal 45, op.precise_time_input.sec
assert_equal 123456, op.precise_time_input.usec
end

def test_iso_date_inputs
Expand Down
1 change: 1 addition & 0 deletions test/support/ops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class TypeCastOp < ::Subroutine::Op
boolean :boolean_input
date :date_input
time :time_input, default: -> { Time.now }
time :precise_time_input, precision: :high
iso_date :iso_date_input
iso_time :iso_time_input
object :object_input
Expand Down

0 comments on commit 6aeeb22

Please sign in to comment.