class TimeCalc::Value
Wrapper (one can say “monad”) around date/time value, allowing to perform several TimeCalc
operations in a chain.
@example
TimeCalc.wrap(Time.parse('2019-06-01 14:50')).+(1, :year).-(1, :month).round(:week).unwrap # => 2020-05-04 00:00:00 +0300
Constants
- CLASS_NAME
@private Because AS::TimeWithZone so frigging smart that it returns “Time” from redefined class name.
- TIMEY
@private
Attributes
@private
Public Class Methods
@note
Prefer {TimeCalc.wrap} to create a Value.
@param time_or_date [Time, Date, DateTime]
# File lib/time_calc/value.rb, line 50 def initialize(time_or_date) @internal = time_or_date end
@private
# File lib/time_calc/value.rb, line 28 def self.wrap(value) case value when Time, Date, DateTime # NB: ActiveSupport::TimeWithZone will also pass to this branch if # active_support/core_ext/time is required. But it is doubtfully it is not -- TWZ will be # mostly unusable :) new(value) when Value value when TIMEY wrap(value.to_time) else fail ArgumentError, "Unsupported value: #{value}" end end
Public Instance Methods
Add `<span units>` to wrapped value.
@param span [Integer] @param unit [Symbol] @return [Value]
# File lib/time_calc/value.rb, line 147 def +(span, unit) unit = Units.(unit) case unit when :sec, :min, :hour, :day plus_seconds(span, unit) when :week self.+(span * 7, :day) when :month plus_months(span) when :year merge(year: year + span) end end
@overload -(span, unit)
Subtracts `span units` from wrapped value. @param span [Integer] @param unit [Symbol] @return [Value]
@overload -(date_or_time)
Produces {Diff}, allowing to calculate structured difference between two points in time. @param date_or_time [Date, Time, DateTime] @return [Diff]
Subtracts `span units` from wrapped value.
# File lib/time_calc/value.rb, line 171 def -(span_or_other, unit = nil) unit.nil? ? Diff.new(self, span_or_other) : self.+(-span_or_other, unit) end
@return [1, 0, -1]
# File lib/time_calc/value.rb, line 65 def <=>(other) return unless other.is_a?(self.class) Types.compare(internal, other.internal) end
Ceils (rounds up) underlying date/time to nearest `unit`.
@example
TimeCalc.from(Time.parse('2018-06-23 12:30')).ceil(:month) # => #<TimeCalc::Value(2018-07-01 00:00:00 +0300)>
@param unit [Symbol] @return [Value]
# File lib/time_calc/value.rb, line 124 def ceil(unit) floor(unit).then { |res| res == self ? res : res.+(1, unit) } end
@private
# File lib/time_calc/value.rb, line 229 def convert(klass) return dup if internal.class == klass Value.new(Types.convert(internal, klass)) end
# File lib/time_calc/value.rb, line 75 def dst? return unless internal.respond_to?(:dst?) internal.dst? end
Produces {Sequence} from this value to `this + <span units>`
@param span [Integer] @param unit [Symbol] @return [Sequence]
# File lib/time_calc/value.rb, line 224 def for(span, unit) to(self.+(span, unit)) end
@private
# File lib/time_calc/value.rb, line 60 def inspect '#<%s(%s)>' % [self.class, internal] end
Like {#+}, but allows conditional skipping of some periods. Increases value by `unit` at least `span` times, on each iteration checking with block provided if this point matches desired period; if it is not, it is skipped without increasing iterations counter. Useful for “business date/time” algorithms.
See {TimeCalc#iterate} for examples.
@param span [Integer] @param unit [Symbol] @return [Value] @yield [Time/Date/DateTime] Object of wrapped class @yieldreturn [true, false] If this point in time is “suitable”. If the falsey value is returned,
iteration is skipped without increasing the counter.
# File lib/time_calc/value.rb, line 188 def iterate(span, unit) block_given? or fail ArgumentError, 'No block given' Integer === span or fail ArgumentError, 'Only integer spans are supported' # rubocop:disable Style/CaseEquality Enumerator.produce(self) { |v| v.+((span <=> 0).nonzero? || 1, unit) } .lazy.select { |v| yield(v.internal) } .drop(span.abs).first end
Produces new value with some components of underlying time/date replaced.
@example
TimeCalc.from(Date.parse('2018-06-01')).merge(year: 1983) # => #<TimeCalc::Value(1983-06-01)>
@param attrs [Hash<Symbol => Integer>] @return [Value]
# File lib/time_calc/value.rb, line 89 def merge(**attrs) class_name = CLASS_NAME.bind(internal.class).call.tr(':', '_') Value.new(Types.public_send("merge_#{class_name.downcase}", internal, **attrs)) end
Rounds up or down underlying date/time to nearest `unit`.
@example
TimeCalc.from(Time.parse('2018-06-23 12:30')).round(:month) # => #<TimeCalc::Value(2018-07-01 00:00:00 +0300)>
@param unit [Symbol] @return Value
# File lib/time_calc/value.rb, line 136 def round(unit) f, c = floor(unit), ceil(unit) (internal - f.internal).abs < (internal - c.internal).abs ? f : c end
Produces endless {Sequence} from this value, with step specified.
@overload step(unit)
Shortcut for `step(1, unit)` @param unit [Symbol]
@overload step(span, unit)
@param span [Integer] @param unit [Symbol]
@return [Sequence]
# File lib/time_calc/value.rb, line 214 def step(span, unit = nil) span, unit = 1, span if unit.nil? Sequence.new(from: self).step(span, unit) end
Produces {Sequence} from this value to `date_or_time`
@param date_or_time [Date, Time, DateTime] @return [Sequence]
# File lib/time_calc/value.rb, line 201 def to(date_or_time) Sequence.new(from: self).to(date_or_time) end
Truncates all time components lower than `unit`. In other words, “floors” (rounds down) underlying date/time to nearest `unit`.
@example
TimeCalc.from(Time.parse('2018-06-23 12:30')).floor(:month) # => #<TimeCalc::Value(2018-06-01 00:00:00 +0300)>
@param unit [Symbol] @return Value
# File lib/time_calc/value.rb, line 103 def truncate(unit) unit = Units.(unit) return floor_week if unit == :week Units::STRUCTURAL .drop_while { |u| u != unit } .drop(1) .then { |keys| Units::DEFAULTS.slice(*keys) } .then { |attrs| merge(**attrs) } # can't simplify to &method(:merge) due to 2.7 keyword param problem end
@return [Time, Date, DateTime] The value of the original type that was wrapped and processed
# File lib/time_calc/value.rb, line 55 def unwrap @internal end
Private Instance Methods
# File lib/time_calc/value.rb, line 237 def floor_week extra_days = (internal.wday.nonzero? || 7) - 1 floor(:day).-(extra_days, :days) end
# File lib/time_calc/value.rb, line 242 def plus_months(span) target = month + span.to_i m = (target - 1) % 12 + 1 dy = (target - 1) / 12 merge(year: year + dy, month: m) end
# File lib/time_calc/value.rb, line 249 def plus_seconds(span, unit) Value.new(internal + span * Units.multiplier_for(internal.class, unit)) .then { |res| unit == :day ? DST.fix_value(res, self) : res } end