Class: Horologium::Instant

Inherits:
Object
  • Object
show all
Includes:
PreciseValue
Defined in:
lib/horologium/instant.rb,
sig/horologium/instant.rbs

Overview

A single point on the timeline, independent of any scale. It is stored as a TAI Julian Date, in days, at a fixed precision.

An Instant is frozen. Its precision is set when it is built, from the precision in effect unless you pass one. At :standard the Julian Date is a Numeric::TwoPartFloat, at :exact a Numeric::Exact.

You can add or subtract a Duration, and subtract another Instant to get the Duration between them. Adding two instants raises DimensionalError. Mixing a :standard and an :exact operand gives an :exact result.

Examples:

Shift an instant, then measure back to it

instant = Horologium::Instant.from_tai_julian_date(2_460_000.5)
later = instant + Horologium::Duration.seconds(3600)
(later - instant) == Horologium::Duration.seconds(3600)
# => true

Instance Attribute Summary

Attributes included from PreciseValue

#precision, #rational, #value

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PreciseValue

#<=>, #eql?, #hash, #initialize

Class Method Details

.from_tai_julian_date(high, low = 0.0, precision: Horologium.current_precision) ⇒ Horologium::Instant

Builds an instant from a TAI Julian Date, split into a high and a low part in days. At :exact the two parts are kept as a Rational, with no loss. At :standard they are normalized so the high part sits on the integer-day grid and the low part holds the fraction, in [-0.5, 0.5].

Examples:

Horologium::Instant.from_tai_julian_date(2_443_144.5, 0.000_372_5)

Parameters:

  • high (Float)

    the high part of the Julian Date, in days

  • low (Float) (defaults to: 0.0)

    the low part, in days

  • precision (Symbol) (defaults to: Horologium.current_precision)

    :standard or :exact, taken from the precision in effect when omitted

  • precision: (Symbol) (defaults to: Horologium.current_precision)

Returns:



35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/horologium/instant.rb', line 35

def self.from_tai_julian_date(
  high,
  low = 0.0,
  precision: Horologium.current_precision
)
  value =
    case Numeric::Precision.validate!(precision)
    when :exact
      Numeric::Exact.new(Numeric::TwoPartFloat.new(high, low))
    else
      Numeric::TwoPartFloat.normalize(high, low)
    end
  new(value, precision)
end

Instance Method Details

#+(duration) ⇒ Horologium::Instant

Adds a duration and returns a later instant.

Parameters:

Returns:

Raises:



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/horologium/instant.rb', line 58

def +(duration) # rubocop:disable Naming/BinaryOperatorParameterName
  unless duration.is_a?(Duration)
    raise DimensionalError,
      "cannot add a #{duration.class} to an Instant; " \
      "only a Duration shifts an Instant"
  end

  precision = Numeric::Precision.resolve(self.precision, duration.precision)
  days = seconds_to_days(duration, precision)
  self.class.new(add(value, days), precision)
end

#-(arg0) ⇒ Instant #-(arg0) ⇒ Duration

Subtracts a duration to get an earlier instant, or another instant to get the Duration between them.

Examples:

An earlier instant

Horologium::Instant.from_tai_julian_date(2_460_000.5) -
  Horologium::Duration.days(1)

The Duration between two instants

a = Horologium::Instant.from_tai_julian_date(2_460_000.5)
b = Horologium::Instant.from_tai_julian_date(2_460_001.5)
b - a == Horologium::Duration.days(1) # => true

Overloads:

Parameters:

Returns:

Raises:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/horologium/instant.rb', line 83

def -(other)
  case other
  when Duration
    precision = Numeric::Precision.resolve(self.precision, other.precision)
    days = seconds_to_days(other, precision)
    self.class.new(subtract(value, days), precision)
  when Instant
    precision = Numeric::Precision.resolve(self.precision, other.precision)
    gap = subtract(value, other.value)
    Duration.new(gap * Duration::SECONDS_PER_DAY, precision)
  else
    raise DimensionalError,
      "cannot subtract a #{other.class} from an Instant; " \
      "subtract a Duration or another Instant"
  end
end

#add(left, right) ⇒ Numeric::TwoPartFloat, Numeric::Exact

Adds two values. Two standard values add as two-part floats; if either is exact, both are promoted to exact Rationals first.



138
139
140
141
142
143
144
145
# File 'lib/horologium/instant.rb', line 138

def add(left, right)
  if left.is_a?(Numeric::TwoPartFloat) && right.is_a?(Numeric::TwoPartFloat)
    left + right
  else
    Numeric::Precision.coerce(left, to: :exact) +
      Numeric::Precision.coerce(right, to: :exact)
  end
end

#equal_within?(other, tolerance) ⇒ Boolean

Whether two instants fall within a tolerance of each other. Use this rather than == in scientific code.

Examples:

a = Horologium::Instant.from_tai_julian_date(2_460_000.5)
b = a + Horologium::Duration.nanoseconds(1)
a.equal_within?(b, Horologium::Duration.nanoseconds(2)) # => true

Parameters:

Returns:

  • (Boolean)


110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/horologium/instant.rb', line 110

def equal_within?(other, tolerance)
  unless other.is_a?(Instant)
    raise DimensionalError,
      "cannot compare an Instant with a #{other.class}"
  end
  unless tolerance.is_a?(Duration)
    raise DimensionalError,
      "a tolerance must be a Duration, got a #{tolerance.class}"
  end

  (self - other).abs <= tolerance
end

#seconds_to_days(duration, precision) ⇒ Horologium::Numeric::TwoPartFloat, Horologium::Numeric::Exact

The duration's seconds counted in days, at the given precision. A Julian Date counts days, so a duration is scaled before it is added.

Parameters:

Returns:



131
132
133
134
# File 'lib/horologium/instant.rb', line 131

def seconds_to_days(duration, precision)
  Numeric::Precision.coerce(duration.value, to: precision) /
    Duration::SECONDS_PER_DAY
end

#subtract(left, right) ⇒ Numeric::TwoPartFloat, Numeric::Exact

Subtracts two values, promoting to exact the same way #add does.



148
149
150
151
152
153
154
155
# File 'lib/horologium/instant.rb', line 148

def subtract(left, right)
  if left.is_a?(Numeric::TwoPartFloat) && right.is_a?(Numeric::TwoPartFloat)
    left - right
  else
    Numeric::Precision.coerce(left, to: :exact) -
      Numeric::Precision.coerce(right, to: :exact)
  end
end