@@ -8,77 +8,146 @@ module RubyUnits
88 # is in years, decades, or centuries. This leads to less precise values, but ones that match the
99 # calendar better.
1010 module Time
11- # Class methods for [Time] objects
11+ # Class methods for [:: Time] objects
1212 module ClassMethods
1313 # Convert a duration to a [::Time] object by considering the duration to be
14- # the number of seconds since the epoch
14+ # the number of seconds since the epoch.
1515 #
16- # @param [Array<RubyUnits::Unit, Numeric, Symbol, Hash>] args
17- # @return [::Time]
18- def at ( *args , **kwargs )
19- case args . first
20- when RubyUnits ::Unit
21- options = args . last . is_a? ( Hash ) ? args . pop : kwargs
22- secondary_unit = args [ 2 ] || "microsecond"
23- case args [ 1 ]
24- when Numeric
25- super ( ( args . first + RubyUnits ::Unit . new ( args [ 1 ] , secondary_unit . to_s ) ) . convert_to ( "second" ) . scalar , **options )
26- else
27- super ( args . first . convert_to ( "second" ) . scalar , **options )
28- end
29- else
30- super
31- end
16+ # @example Create time from a Unit duration
17+ # Time.at(Unit.new("5 min")) #=> Time object 300 seconds after epoch
18+ #
19+ # @example Create time from Unit with offset
20+ # Time.at(Unit.new("1 h"), 500, :millisecond) #=> Time 1 hour + 500 ms after epoch
21+ #
22+ # @param [Array<RubyUnits::Unit, Numeric, Symbol, Hash>] args Arguments passed to Time.at
23+ # @param [Hash] kwargs Keyword arguments (e.g., in: timezone)
24+ # @return [::Time] A Time object representing the duration since epoch
25+ def at ( *args , **)
26+ first_arg = args . first
27+ return super unless first_arg . is_a? ( RubyUnits ::Unit )
28+
29+ time_in_seconds = calculate_time_in_seconds ( first_arg , args [ 1 ] , args [ 2 ] )
30+ remaining_args = build_remaining_args ( args )
31+
32+ super ( time_in_seconds , *remaining_args , **)
3233 end
3334
34- # @example
35- # Time.in '5 min'
36- # @param duration [#to_unit]
37- # @return [::Time]
35+ # Calculate a future time by adding a duration to the current time.
36+ # This is a convenience method equivalent to Time.now + duration.
37+ #
38+ # @example Get time 5 minutes from now
39+ # Time.in('5 min') #=> Time object 5 minutes in the future
40+ #
41+ # @example Using various duration formats
42+ # Time.in('2 hours') #=> 2 hours from now
43+ # Time.in(Unit.new('30 sec')) #=> 30 seconds from now
44+ #
45+ # @param duration [String, RubyUnits::Unit, #to_unit] A duration that can be converted to a Unit
46+ # @return [::Time] A Time object representing the current time plus the duration
47+ # :reek:UtilityFunction - This is a class method convenience wrapper, state independence is by design
3848 def in ( duration )
3949 ::Time . now + duration . to_unit
4050 end
51+
52+ private
53+
54+ # Calculate the time in seconds from a Unit and optional offset
55+ # @param base_unit [RubyUnits::Unit] The base time unit
56+ # @param offset_value [Numeric, nil] Optional offset value
57+ # @param offset_unit [String, Symbol, nil] Unit for the offset (default: "microsecond")
58+ # @return [Numeric] Time in seconds
59+ # :reek:UtilityFunction - Private helper method, state independence is acceptable
60+ # :reek:ControlParameter - Default parameter handling is appropriate here
61+ def calculate_time_in_seconds ( base_unit , offset_value , offset_unit )
62+ return base_unit . convert_to ( "second" ) . scalar unless offset_value . is_a? ( Numeric )
63+
64+ unit_str = offset_unit &.to_s || "microsecond"
65+ ( base_unit + RubyUnits ::Unit . new ( offset_value , unit_str ) ) . convert_to ( "second" ) . scalar
66+ end
67+
68+ # Build remaining arguments to pass to super, skipping the first 3 processed args
69+ # @param args [Array] Original arguments array
70+ # @return [Array] Remaining arguments after the first three
71+ # :reek:UtilityFunction - Private helper method, state independence is acceptable
72+ def build_remaining_args ( args )
73+ args [ 3 ..] || [ ]
74+ end
4175 end
4276
43- # Convert a [::Time] object to a [RubyUnits::Unit] object. The time is
44- # considered to be a duration with the number of seconds since the epoch.
77+ # Convert a [::Time] object to a [RubyUnits::Unit] object representing
78+ # the duration in seconds since the Unix epoch (January 1, 1970 00:00:00 UTC).
79+ #
80+ # @example Convert time to unit
81+ # Time.now.to_unit #=> Unit representing seconds since epoch
82+ #
83+ # @example Convert time to specific unit
84+ # Time.now.to_unit('hour') #=> Unit in hours since epoch
4585 #
46- # @param other [String, RubyUnits::Unit]
47- # @return [RubyUnits::Unit]
86+ # @param other [String, RubyUnits::Unit, nil] Optional target unit for conversion
87+ # @return [RubyUnits::Unit] A Unit object representing the time as a duration
4888 def to_unit ( other = nil )
49- other ? RubyUnits ::Unit . new ( self ) . convert_to ( other ) : RubyUnits ::Unit . new ( self )
89+ unit = RubyUnits ::Unit . new ( self )
90+ other ? unit . convert_to ( other ) : unit
5091 end
5192
52- # @param other [::Time, RubyUnits::Unit]
53- # @return [RubyUnits::Unit, ::Time]
93+ # Add a duration to a time. For large units (years, decades, centuries),
94+ # the duration is first converted to days and rounded to handle calendar complexities.
95+ # If the result would be out of range for Time, falls back to DateTime.
96+ #
97+ # @example Add hours to time
98+ # Time.now + Unit.new('2 hours') #=> Time 2 hours in future
99+ #
100+ # @example Add years (rounded to days)
101+ # Time.now + Unit.new('1 year') #=> Time ~365 days in future
102+ #
103+ # @param other [::Time, RubyUnits::Unit, Numeric] Value to add
104+ # @return [::Time, DateTime] The resulting time, or DateTime if out of Time range
54105 def +( other )
55- case other
56- when RubyUnits ::Unit
57- other = other . convert_to ( "d" ) . round . convert_to ( "s" ) if %w[ y decade century ] . include? other . units
58- begin
59- super ( other . convert_to ( "s" ) . scalar )
60- rescue RangeError
61- to_datetime + other
62- end
63- else
64- super
65- end
106+ return super unless other . is_a? ( RubyUnits ::Unit )
107+
108+ duration_in_seconds = convert_to_seconds ( other )
109+ super ( duration_in_seconds )
110+ rescue RangeError
111+ to_datetime + other
66112 end
67113
68- # @param other [::Time, RubyUnits::Unit]
69- # @return [RubyUnits::Unit, ::Time]
114+ # Subtract a duration from a time. For large units (years, decades, centuries),
115+ # the duration is first converted to days and rounded to handle calendar complexities.
116+ # If the result would be out of range for Time, falls back to DateTime.
117+ #
118+ # @example Subtract hours from time
119+ # Time.now - Unit.new('2 hours') #=> Time 2 hours in past
120+ #
121+ # @example Subtract years (rounded to days)
122+ # Time.now - Unit.new('1 year') #=> Time ~365 days in past
123+ #
124+ # @param other [::Time, RubyUnits::Unit, Numeric] Value to subtract
125+ # @return [::Time, DateTime, Numeric] The resulting time (DateTime if out of range),
126+ # or numeric difference in seconds if subtracting another Time
70127 def -( other )
71- case other
72- when RubyUnits ::Unit
73- other = other . convert_to ( "d" ) . round . convert_to ( "s" ) if %w[ y decade century ] . include? other . units
74- begin
75- super ( other . convert_to ( "s" ) . scalar )
76- rescue RangeError
77- public_send ( :to_datetime ) - other
78- end
79- else
80- super
81- end
128+ return super unless other . is_a? ( RubyUnits ::Unit )
129+
130+ duration_in_seconds = convert_to_seconds ( other )
131+ super ( duration_in_seconds )
132+ rescue RangeError
133+ public_send ( :to_datetime ) - other
134+ end
135+
136+ private
137+
138+ # Convert a unit to seconds, rounding large time units (years, decades, centuries) to days first.
139+ # This handles calendar complexities where years don't have a fixed number of seconds.
140+ #
141+ # @param unit [RubyUnits::Unit] The duration unit to convert
142+ # @return [Numeric] The duration in seconds
143+ # :reek:UtilityFunction - Private helper method, state independence is acceptable
144+ def convert_to_seconds ( unit )
145+ normalized_unit = if %w[ y decade century ] . include? ( unit . units )
146+ unit . convert_to ( "d" ) . round . convert_to ( "s" )
147+ else
148+ unit . convert_to ( "s" )
149+ end
150+ normalized_unit . scalar
82151 end
83152 end
84153end
0 commit comments