Class: Horologium::Representations::Civil

Inherits:
Object
  • Object
show all
Defined in:
lib/horologium/representations/civil.rb,
sig/horologium/representations/civil.rbs

Overview

The calendar date and time of day an instant falls on, in the scale it is read in. It is the shape a person reads, where a Julian Date is the shape a series takes.

The calendar is the proleptic Gregorian one, extended backwards past its 1582 introduction, with astronomical year numbering. The conversions are the ones ERFA performs in eraJd2cal and eraCal2jd, over the range those routines document, from the year MINIMUM_YEAR on.

The whole conversion runs in exact Rational arithmetic, at both precisions: a Numeric::TwoPartFloat pair is already an exact Rational, so nothing is lost on the way in or out, and precision only re-enters when the fraction of a second is rendered in the type asked for. That makes it more accurate than eraJd2cal, which does the same work in double precision, and slower. Reading a civil date is a display step; the Julian Date and the Duration are what arithmetic runs on.

Examples:

instant = Horologium::Instant.from_civil(2025, 5, 1, 12, scale: :tt)

instant.as(:civil, scale: :tt).hour   # => 12
instant.as(:civil, scale: :tai).hour  # => 11

Constant Summary collapse

OUTPUTS =

The types the fraction of a second can come out as. A Julian Date's :two_part is not among them: the fraction is smaller than 1, where the split exists to hold a number too large for one Float.

Returns:

  • (Array[Symbol])
%i[float rational].freeze
MINIMUM_YEAR =

The earliest year the calendar conversion covers, the range ERFA documents for eraCal2jd. Every intermediate is non-negative from here on, so Ruby's flooring integer division agrees with the truncating division the C routines are written in.

Returns:

  • (Integer)
-4799
MINIMUM_DAY_NUMBER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

The Julian Day Number of MINIMUM_YEAR-01-01, the earliest day the calendar conversion covers. It bounds the way out as MINIMUM_YEAR bounds the way in, so a reading is never handed back a date that parse would refuse to read.

Returns:

  • (Integer)
-31_738
HALF_DAY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Half a day, the offset between a Julian Date, which starts at noon, and the day the calendar counts, which starts at midnight.

Returns:

  • (Rational)
Rational(1, 2)
DAYS_IN_MONTH =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

The length of each month in a common year, from January.

Returns:

  • (Array[Integer])
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31].freeze
SECONDS_PER_HOUR =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

The seconds in an hour.

Returns:

  • (Integer)
3_600
SECONDS_PER_MINUTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

The seconds in a minute.

Returns:

  • (Integer)
60

Class Method Summary collapse

Class Method Details

.from_fields(year, month, day, hour, minute, second) ⇒ Horologium::Representations::CivilTime

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A civil time built from the fields as a caller writes them, where the whole second and the fraction under it are one number. It is what Instant.from_civil passes to parse.

Parameters:

  • year (Integer)

    the year, in astronomical numbering

  • month (Integer)

    the month, from 1 to 12

  • day (Integer)

    the day of the month

  • hour (Integer)

    the hour, from 0 to 23

  • minute (Integer)

    the minute, from 0 to 59

  • second (Integer, Float, Rational)

    the second, whole or with a fraction under it

Returns:

Raises:

  • (ArgumentError)

    when the second is not a number the library reads



159
160
161
162
163
# File 'lib/horologium/representations/civil.rb', line 159

def from_fields(year, month, day, hour, minute, second)
  whole, fraction = split_second(second)

  CivilTime.new(year, month, day, hour, minute, whole, fraction)
end

.parse(value, _low, scale, precision) ⇒ Horologium::Numeric::TwoPartFloat, Horologium::Numeric::Exact

A civil time as it was given, as a Julian Date in days, at the precision asked for. This is the way in, where render is the way out. The Julian Date is in the scale the civil time was read in; it is Instant.from_civil that reads it back in TAI.

Nothing is lost: the date becomes a whole number of days by integer arithmetic, and the time of day an exact fraction of one. A civil time is therefore an exact way to build an instant, where a Julian Date given as a single Float is not.

Parameters:

  • value (Horologium::Representations::CivilTime)

    the civil time

  • _low (nil)

    unused; a civil time has no low part

  • scale (Class)

    the scale the civil time is read in, asked how long the day is so a leap second falls in the right place

  • precision (Symbol)

    :standard or :exact

Returns:

Raises:



132
133
134
135
136
137
138
139
140
141
142
# File 'lib/horologium/representations/civil.rb', line 132

def parse(value, _low, scale, precision)
  civil = validate!(value)
  day = day_number(civil.year, civil.month, civil.day)
  seconds_in_day = scale.seconds_in_day(day)
  validate_time!(civil, seconds_in_day)

  Numeric::Precision.build(
    day - HALF_DAY + seconds_of_day(civil) / seconds_in_day,
    precision
  )
end

.render(reading, output) ⇒ Horologium::Representations::CivilTime

The reading, as a calendar date and a time of day.

The date and the whole second come from the exact value, whatever the precision, so they are the fields the instant really falls on. Only the fraction of a second is rendered in the type asked for: a Float by default, a Rational under as: :rational, which keeps the whole of it.

Examples:

instant = Horologium::Instant.from_julian_date(
  2_443_144.5,
  scale: :tai
)
instant.to(:tai).as(:civil) # => 1977-01-01 00:00:00

Parameters:

Returns:

Raises:



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/horologium/representations/civil.rb', line 96

def render(reading, output)
  validate_output!(output)

  scale = Horologium.configuration.scale(reading.scale)
  shifted = reading.value.to_r + HALF_DAY
  day_number = shifted.floor
  # A leap second makes a UTC day 86,401 seconds long; the scale says
  # so, and every other scale answers 86,400.
  seconds_in_day = scale.seconds_in_day(day_number)
  seconds = (shifted - day_number) * seconds_in_day

  civil_at(day_number, seconds, seconds_in_day, output)
end