Class: TimeCalc::Value

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/time_calc/value.rb

Overview

Wrapper (one can say “monad”) around date/time value, allowing to perform several TimeCalc operations in a chain.

Examples:

TimeCalc.wrap(Time.parse('2019-06-01 14:50')).+(1, :year).-(1, :month).round(:week).unwrap
# => 2020-05-04 00:00:00 +0300

Constant Summary collapse

TIMEY =
proc { |t| t.respond_to?(:to_time) }
CLASS_NAME =

Because AS::TimeWithZone so frigging smart that it returns “Time” from redefined class name.

Class.instance_method(:name)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(time_or_date) ⇒ Value

Note:

Prefer TimeCalc.wrap to create a Value.

Returns a new instance of Value.

Parameters:

  • time_or_date (Time, Date, DateTime)


50
51
52
# File 'lib/time_calc/value.rb', line 50

def initialize(time_or_date)
  @internal = time_or_date
end

Instance Attribute Details

#internalObject (readonly)



45
46
47
# File 'lib/time_calc/value.rb', line 45

def internal
  @internal
end

Class Method Details

.wrap(value) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 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

Instance Method Details

#+(span, unit) ⇒ Value

Add ‘<span units>` to wrapped value.

Parameters:

  • span (Integer)
  • unit (Symbol)

Returns:



147
148
149
150
151
152
153
154
155
156
157
158
159
# 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

#-(span, unit) ⇒ Value #-(date_or_time) ⇒ Diff

Subtracts ‘span units` from wrapped value.

Overloads:

  • #-(span, unit) ⇒ Value

    Subtracts ‘span units` from wrapped value.

    Parameters:

    • span (Integer)
    • unit (Symbol)

    Returns:

  • #-(date_or_time) ⇒ Diff

    Produces Diff, allowing to calculate structured difference between two points in time.

    Parameters:

    • date_or_time (Date, Time, DateTime)

    Returns:



171
172
173
# 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

#<=>(other) ⇒ 1, ...

Returns:

  • (1, 0, -1)


65
66
67
68
69
# File 'lib/time_calc/value.rb', line 65

def <=>(other)
  return unless other.is_a?(self.class)

  Types.compare(internal, other.internal)
end

#ceil(unit) ⇒ Value

Ceils (rounds up) underlying date/time to nearest ‘unit`.

Examples:

TimeCalc.from(Time.parse('2018-06-23 12:30')).ceil(:month)
# => #<TimeCalc::Value(2018-07-01 00:00:00 +0300)>

Parameters:

  • unit (Symbol)

Returns:



124
125
126
# File 'lib/time_calc/value.rb', line 124

def ceil(unit)
  floor(unit).then { |res| res == self ? res : res.+(1, unit) }
end

#convert(klass) ⇒ Object



229
230
231
232
233
# File 'lib/time_calc/value.rb', line 229

def convert(klass)
  return dup if internal.class == klass

  Value.new(Types.convert(internal, klass))
end

#dst?Boolean

Returns:

  • (Boolean)


75
76
77
78
79
# File 'lib/time_calc/value.rb', line 75

def dst?
  return unless internal.respond_to?(:dst?)

  internal.dst?
end

#for(span, unit) ⇒ Sequence

Produces Sequence from this value to ‘this + <span units>`

Parameters:

  • span (Integer)
  • unit (Symbol)

Returns:



224
225
226
# File 'lib/time_calc/value.rb', line 224

def for(span, unit)
  to(self.+(span, unit))
end

#inspectObject



60
61
62
# File 'lib/time_calc/value.rb', line 60

def inspect
  '#<%s(%s)>' % [self.class, internal]
end

#iterate(span, unit) {|Time/Date/DateTime| ... } ⇒ Value

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.

Parameters:

  • span (Integer)
  • unit (Symbol)

Yields:

  • (Time/Date/DateTime)

    Object of wrapped class

Yield Returns:

  • (true, false)

    If this point in time is “suitable”. If the falsey value is returned, iteration is skipped without increasing the counter.

Returns:



188
189
190
191
192
193
194
195
# 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

#merge(**attrs) ⇒ Value

Produces new value with some components of underlying time/date replaced.

Examples:

TimeCalc.from(Date.parse('2018-06-01')).merge(year: 1983)
# => #<TimeCalc::Value(1983-06-01)>

Parameters:

  • attrs (Hash<Symbol => Integer>)

Returns:



89
90
91
92
# 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

#round(unit) ⇒ Object

Rounds up or down underlying date/time to nearest ‘unit`.

Examples:

TimeCalc.from(Time.parse('2018-06-23 12:30')).round(:month)
# => #<TimeCalc::Value(2018-07-01 00:00:00 +0300)>

Parameters:

  • unit (Symbol)

Returns:

  • Value



136
137
138
139
140
# 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

#step(unit) ⇒ Sequence #step(span, unit) ⇒ Sequence

Produces endless Sequence from this value, with step specified.

Overloads:

  • #step(unit) ⇒ Sequence

    Shortcut for ‘step(1, unit)`

    Parameters:

    • unit (Symbol)
  • #step(span, unit) ⇒ Sequence

    Parameters:

    • span (Integer)
    • unit (Symbol)

Returns:



214
215
216
217
# 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

#to(date_or_time) ⇒ Sequence

Produces Sequence from this value to ‘date_or_time`

Parameters:

  • date_or_time (Date, Time, DateTime)

Returns:



201
202
203
# File 'lib/time_calc/value.rb', line 201

def to(date_or_time)
  Sequence.new(from: self).to(date_or_time)
end

#truncate(unit) ⇒ Object Also known as: floor

Truncates all time components lower than ‘unit`. In other words, “floors” (rounds down) underlying date/time to nearest `unit`.

Examples:

TimeCalc.from(Time.parse('2018-06-23 12:30')).floor(:month)
# => #<TimeCalc::Value(2018-06-01 00:00:00 +0300)>

Parameters:

  • unit (Symbol)

Returns:

  • Value



103
104
105
106
107
108
109
110
111
112
# 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

#unwrapTime, ...

Returns The value of the original type that was wrapped and processed.

Returns:

  • (Time, Date, DateTime)

    The value of the original type that was wrapped and processed



55
56
57
# File 'lib/time_calc/value.rb', line 55

def unwrap
  @internal
end