Skip to content

Commit 6cf60b7

Browse files
committed
Prepare v0.0.2
This adds documentation for the first proper version of the gem.
1 parent a714750 commit 6cf60b7

2 files changed

Lines changed: 146 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Changelog
22

3+
## 0.0.2 - 2026-07-14
4+
5+
The first functional release. It ships the numeric core and the two value
6+
objects the rest of the library is built on: `Instant` and `Duration`. The
7+
scale conversions are not here yet, so an instant is built directly from a
8+
TAI Julian Date for now.
9+
10+
### Features
11+
12+
- Add `Numeric::TwoPartFloat`, a number kept as a high and a low `Float` for
13+
about twice the precision of one, with Shewchuk error-free arithmetic
14+
- Add `Numeric::Exact`, a value kept as an exact `Rational`, with no rounding
15+
- Add the precision contract: every value carries a precision, `:standard` or
16+
`:exact`, set when it is built and never changed. Mixing the two promotes
17+
the result to `:exact` instead of dropping to `:standard`
18+
- Add `Horologium.configure` for the set-once default precision, and
19+
`Horologium.with_precision` for a scoped, per-fiber override
20+
- Add `Instant`, a frozen point on the TAI timeline, built with
21+
`Instant.from_tai_julian_date`
22+
- Add `Duration`, a frozen span in SI seconds, built with `Duration.seconds`,
23+
`Duration.days`, and `Duration.nanoseconds`
24+
- Add instant and duration arithmetic: shift an instant by a duration, and
25+
subtract two instants to measure the duration between them
26+
- Add `Instant#equal_within?` for comparison inside a tolerance
27+
- Guard against meaningless operations: adding two instants raises
28+
`DimensionalError`
29+
30+
**Full Changelog**: https://github.com/rhannequin/horologium/compare/v0.0.1...v0.0.2
31+
332
## 0.0.1 - 2026-07-06
433

534
- Gem creation

README.md

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,128 @@ Horologium is a Ruby library dedicated to **scientific time**: the time scales
77
intervals, and rigorous conversions between scales that astronomy and physics
88
require.
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

26134
After 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].
53161
Everyone interacting in the Horologium project's codebases, issue trackers, chat
54162
rooms 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

Comments
 (0)