Class: Horologium::Representations::CivilTime

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

Overview

A calendar date and a time of day, in the scale it was read in. It is what Civil renders, and what Instant.from_civil is given.

The fields are the ones a clock and a calendar show. The date is in the proleptic Gregorian calendar, with astronomical year numbering: year 0 exists, and 1 BC is year 0, 2 BC is year -1. The seconds are split in two, a whole #second and the #second_fraction under it, because the whole second is the field a leap second lands in and the fraction is the field precision lands in.

A CivilTime is frozen and holds no scale of its own: 12:00 in TAI and 12:00 in TT are different instants, so two civil times are compared as sets of fields, and only comparing the instants they came from compares points on the timeline. That is why it is not Comparable.

It does not check that its fields make a real date. Civil does, when it reads one, because which days exist is calendar knowledge and which seconds exist is scale knowledge, and neither belongs to a set of fields.

Examples:

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

civil.year   # => 2025
civil.hour   # => 12

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(year, month, day, hour = 0, minute = 0, second = 0, second_fraction = 0) ⇒ CivilTime

Returns a new instance of CivilTime.

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) (defaults to: 0)

    the hour, from 0 to 23

  • minute (Integer) (defaults to: 0)

    the minute, from 0 to 59

  • second (Integer) (defaults to: 0)

    the whole second

  • second_fraction (Float, Rational, Integer) (defaults to: 0)

    the part of a second under it



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/horologium/representations/civil_time.rb', line 66

def initialize(
  year,
  month,
  day,
  hour = 0,
  minute = 0,
  second = 0,
  second_fraction = 0
)
  @year = year
  @month = month
  @day = day
  @hour = hour
  @minute = minute
  @second = second
  @second_fraction = second_fraction
  freeze
end

Instance Attribute Details

#dayInteger (readonly)

Returns the day of the month, from 1.

Returns:

  • (Integer)

    the day of the month, from 1



38
39
40
# File 'lib/horologium/representations/civil_time.rb', line 38

def day
  @day
end

#hourInteger (readonly)

Returns the hour, from 0 to 23.

Returns:

  • (Integer)

    the hour, from 0 to 23



41
42
43
# File 'lib/horologium/representations/civil_time.rb', line 41

def hour
  @hour
end

#minuteInteger (readonly)

Returns the minute, from 0 to 59.

Returns:

  • (Integer)

    the minute, from 0 to 59



44
45
46
# File 'lib/horologium/representations/civil_time.rb', line 44

def minute
  @minute
end

#monthInteger (readonly)

Returns the month, from 1 to 12.

Returns:

  • (Integer)

    the month, from 1 to 12



35
36
37
# File 'lib/horologium/representations/civil_time.rb', line 35

def month
  @month
end

#secondInteger (readonly)

Returns the whole second, from 0 to 59, and 60 on a day that holds a leap second.

Returns:

  • (Integer)

    the whole second, from 0 to 59, and 60 on a day that holds a leap second



48
49
50
# File 'lib/horologium/representations/civil_time.rb', line 48

def second
  @second
end

#second_fractionFloat, ... (readonly)

The part of a second under #second, from 0 up to but not including

  1. Its type is the one asked for when the reading was rendered: a Float by default, a Rational under as: :rational, which keeps the whole value.

Returns:

  • (Float, Rational, Integer)


56
57
58
# File 'lib/horologium/representations/civil_time.rb', line 56

def second_fraction
  @second_fraction
end

#yearInteger (readonly)

Returns the year, in astronomical numbering.

Returns:

  • (Integer)

    the year, in astronomical numbering



32
33
34
# File 'lib/horologium/representations/civil_time.rb', line 32

def year
  @year
end

Instance Method Details

#==(other) ⇒ Boolean

Two civil times are equal when every field is, the fraction of a second compared by the exact value it holds. A Float fraction and a Rational one are equal only when they are the same number, so a reading rendered as a Float is not equal to the same reading rendered as a Rational unless the Float happened to hold it exactly. This is how Instant compares too.

Parameters:

  • other (Object)

    the object to compare

Returns:

  • (Boolean)


94
95
96
# File 'lib/horologium/representations/civil_time.rb', line 94

def ==(other)
  other.is_a?(CivilTime) && fields == other.fields
end

#eql?(other) ⇒ Boolean

Parameters:

  • other (Object)

Returns:

  • (Boolean)


100
101
102
# File 'lib/horologium/representations/civil_time.rb', line 100

def eql?(other)
  self == other
end

#fieldsArray

The fields, with the fraction of a second as the exact value it holds, so that equal values compare and hash equal whatever type carries them.

Returns:

  • (Array)


134
135
136
# File 'lib/horologium/representations/civil_time.rb', line 134

def fields
  [year, month, day, hour, minute, second, second_fraction.to_r]
end

#fraction_digitsString

The fraction of a second, as the digits that follow the second, the leading decimal point among them. It is empty when there is no fraction, so that a whole second reads as a whole second.

Float#to_s writes a number under 0.0001 in scientific notation, which is not a shape a time of day has, so the digits are put back where they belong. The digits themselves are the ones Float#to_s picks, the shortest that read back as the same Float.

Returns:

  • (String)


150
151
152
153
154
155
156
157
158
159
# File 'lib/horologium/representations/civil_time.rb', line 150

def fraction_digits
  return "" if second_fraction.zero?

  mantissa, marker, exponent = second_fraction.to_f.to_s.partition("e")
  return mantissa[1..] || "" if marker.empty?

  digits = mantissa.delete(".").sub(/0+\z/, "")

  ".#{"0" * (-Integer(exponent, 10) - 1)}#{digits}"
end

#hashInteger

Returns:

  • (Integer)


105
106
107
# File 'lib/horologium/representations/civil_time.rb', line 105

def hash
  [self.class, fields].hash
end

#inspectString

The fields, for reading in a console. It is a debugging shape, not a format to parse: it names no scale, and a civil time without its scale does not say which instant it is.

Returns:

  • (String)


114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/horologium/representations/civil_time.rb', line 114

def inspect
  format(
    "#<%s %04d-%02d-%02d %02d:%02d:%02d%s>",
    self.class,
    year,
    month,
    day,
    hour,
    minute,
    second,
    fraction_digits
  )
end