@@ -7,20 +7,128 @@ Horologium is a Ruby library dedicated to **scientific time**: the time scales
77intervals, and rigorous conversions between scales that astronomy and physics
88require.
99
10+ Ruby already has ` Time ` , ` Date ` , ` DateTime ` , and ` ActiveSupport ` for civil time:
11+ time zones, calendars, human formatting. None of them knows the difference
12+ between UTC and a continuous scale, the TAI, TT, and TDB scales an ephemeris
13+ needs, or a Julian Date kept precise to the nanosecond. That is the gap
14+ Horologium fills.
15+
16+ ## Content
17+
18+ - [ Installation] ( #installation )
19+ - [ Usage] ( #usage )
20+ - [ Precision] ( #precision )
21+ - [ Status] ( #status )
22+ - [ Development] ( #development )
23+ - [ Contributing] ( #contributing )
24+ - [ License] ( #license )
25+ - [ Code of Conduct] ( #code-of-conduct )
26+
1027## Installation
1128
12- Install the gem and add it to the application's Gemfile by running:
29+ Install the gem and add it to the application's Gemfile by executing:
30+
31+ $ bundle add horologium
32+
33+ If [ Bundler] is not being used to manage dependencies, install the gem by
34+ executing:
35+
36+ $ gem install horologium
37+
38+ ## Usage
39+
40+ An ` Instant ` is a single point on the timeline, kept internally as a TAI Julian
41+ Date. A ` Duration ` is an amount of time in SI seconds, with no date and no scale
42+ attached. You shift an instant by a duration, and you subtract two instants to
43+ get the duration between them.
44+
45+ ``` rb
46+ require " horologium"
47+
48+ instant = Horologium ::Instant .from_tai_julian_date(2_460_000.5 )
49+
50+ later = instant + Horologium ::Duration .days(1 )
51+ later == Horologium ::Instant .from_tai_julian_date(2_460_001.5 ) # => true
52+ instant < later # => true
53+
54+ b = Horologium ::Instant .from_tai_julian_date(2_460_001.5 )
55+ b - instant == Horologium ::Duration .days(1 ) # => true
56+ ```
57+
58+ A ` Duration ` counts SI seconds, so ` Duration.days(1) ` is always 86,400 SI
59+ seconds. Because of leap seconds a civil day can be a second longer or shorter,
60+ so a duration and a calendar day are different things.
61+
62+ ``` rb
63+ Horologium ::Duration .days(1 ) == Horologium ::Duration .seconds(86_400 ) # => true
64+ Horologium ::Duration .nanoseconds(1_000_000_000 ) ==
65+ Horologium ::Duration .seconds(1 ) # => true
66+ ```
67+
68+ Adding a duration to an instant makes sense, but adding two instants together
69+ does not, so it raises an error.
1370
14- ``` sh
15- bundle add horologium
71+ ``` rb
72+ instant + instant # => raises Horologium::DimensionalError
1673```
1774
18- Or install it directly:
75+ Exact equality is rarely what scientific code wants, so you can compare within a
76+ tolerance:
1977
20- ``` sh
21- gem install horologium
78+ ``` rb
79+ a = Horologium ::Instant .from_tai_julian_date(2_460_000.5 )
80+ near = a + Horologium ::Duration .nanoseconds(1 )
81+
82+ a.equal_within?(near, Horologium ::Duration .nanoseconds(2 )) # => true
83+ ```
84+
85+ ## Precision
86+
87+ A modern Julian Date is around 2.46 million. A single ` Float ` spends most of its
88+ digits on that large number and has only tens of microseconds left for the
89+ fraction of a day. That is too coarse for scientific time. Horologium stores an
90+ instant across two ` Float ` s whose sum is the Julian Date, so the second one
91+ starts where the first runs out of digits. This is the representation [ ERFA]
92+ uses, it keeps the precision below a nanosecond for any date, and it does so
93+ with ordinary floating-point arithmetic.
94+
95+ Every value carries one of two precisions, fixed when it is built:
96+
97+ - ` :standard ` , the default, keeps the value as a two-part float. It is fast and
98+ stays within a few nanoseconds of the true value.
99+ - ` :exact ` keeps the value as a ` Rational ` , with no rounding. The test suite
100+ uses it to check that ` :standard ` stays within its stated precision.
101+
102+ Set the default once at boot:
103+
104+ ``` rb
105+ Horologium .configure do |c |
106+ c.default_precision = :exact
107+ end
22108```
23109
110+ Choose it for a single value, or for a scoped block:
111+
112+ ``` rb
113+ Horologium ::Instant .from_tai_julian_date(2_460_000.5 , precision: :exact )
114+
115+ Horologium .with_precision(:exact ) do
116+ # instants and durations built here default to :exact
117+ end
118+ ```
119+
120+ Exactness is contagious. An operation between two ` :standard ` values stays
121+ ` :standard ` . Mixing a ` :standard ` and an ` :exact ` value gives an ` :exact `
122+ result, so precision is not quietly lost. ` :exact ` guarantees the arithmetic
123+ Horologium performs. It cannot bring back precision that an input already lost
124+ when it was built.
125+
126+ ## Status
127+
128+ This library is in early development, before its first public release. The
129+ public API is not stable, so new versions will probably introduce breaking
130+ changes until a 1.0 release. Changes are documented in the [ CHANGELOG] .
131+
24132## Development
25133
26134After checking out the repo, run ` bin/setup ` to install dependencies. Then, run
@@ -53,6 +161,9 @@ The gem is available as open source under the terms of the [MIT License].
53161Everyone interacting in the Horologium project's codebases, issue trackers, chat
54162rooms and mailing lists is expected to follow the [ code of conduct] .
55163
164+ [ Bundler ] : https://bundler.io
165+ [ ERFA ] : https://github.com/liberfa/erfa
166+ [ CHANGELOG ] : https://github.com/rhannequin/horologium/blob/main/CHANGELOG.md
56167[ rubygems.org ] : https://rubygems.org
57168[ MIT License ] : https://opensource.org/licenses/MIT
58169[ code of conduct ] : https://github.com/rhannequin/horologium/blob/main/CODE_OF_CONDUCT.md
0 commit comments