diff --git a/docs/make.jl b/docs/make.jl index 6ff0acc..b0bfb5b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,17 +1,25 @@ using Tempo using Documenter +const CI = get(ENV, "CI", "false") == "true" + makedocs(; authors="Julia Space Mission Design Development Team", sitename="Tempo.jl", modules=[Tempo], + format=Documenter.HTML(; prettyurls=CI, highlights=["yaml"], ansicolor=true), pages=[ "Home" => "index.md", - "API" => "api.md", + "Tutorials" => [ "Epochs" => "tutorials/t01_epochs.md", - "Scales" => "tutorials/t02_scales.md" - ] + "Custom Timescales" => "tutorials/t02_scales.md" + ], + + "API" => [ + "Public API" => "api.md", + "Low-level API" => "lapi.md" + ], ], ) diff --git a/docs/src/api.md b/docs/src/api.md index e9bd97c..99d4ce3 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -1,29 +1,72 @@ -# API Reference +# [Public Documentation](@id tempo_api) -## Constants +## Datetime +```@docs +Date +Time +DateTime +year +month +day +hour +minute +second +find_dayinyear +``` + +## Epochs -```@autodocs -Modules = [Tempo] -Order = [:constant] +### Types +```@docs +Epoch +timescale +value ``` -## Types +### Origins +```@docs +JD +J2000 +MJD +MJD2000 +``` -```@autodocs -Modules = [Tempo] -Order = [:type] +## Conversions +```@docs +j2000 +j2000s +j2000c ``` -## Types +## Timescales -```@autodocs -Modules = [Tempo] -Order = [:function] +### Time System +```@docs +@timescale +timescale_alias +timescale_name +timescale_id +TimeSystem +add_timescale! +TIMESCALES ``` -## Macros +### Default Timescales +```@docs +GPS +TAI +TCB +TCG +TDB +TDBH +TT +UT1 +UTC +``` -```@autodocs -Modules = [Tempo] -Order = [:macro] +## Constants +```@docs +DJ2000 +DMJD +DJM0 ``` \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index a8dda7a..340bc5c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,93 +1,46 @@ -# Tempo.jl +# Welcome to Tempo.jl! -_Time transformations in Julia._ +_Efficient Astronomical Time transformations in Julia._ -The `Tempo` module is thought to be a fast, efficient and precise time transformation library -capable to handle the different transformations needed in the astronomical and astrodynamical -application of the `JSMD` ecosystem. +Tempo.jl is an astronomical library that enables fast, efficient and high-accuracy time transformations between common and user-defined time scales and time representations. -## Overview +## Installation +This package can be installed using Julia's package manager: +```julia +julia> import Pkg -There are different ways to represent an epoch within `Tempo`, depending on the specific -application. This section is here to help you choose the proper time representation and to -present the capabilities of the module in transforming time between different representations. - -First of all, there is a deep difference in the way time is thought in the everyday life and -when dealing with space-related applications. -Whenever we say _the 12:35 of the 1st of January 2023_, we are merging three concepts: -the **calendar** (_1st January 2023_) and the **time representation** (_12:35_) together with -its **scale**. - - -Calculations in any scientific discipline may involve precise time, but what -sets astronomy apart is the number and variety of **time scales** that have to be used. -In fact, in astronomical applications the physical context of the “clock” matters, -whether it is on Earth, moving or stationary, or on a spacecraft. - -```@raw html -
- Image -
Time Conversions - The difference in each timescale is shown with - respect to TAI.
-
+julia> Pkg.add("Tempo.jl"); ``` -The most relevant time scales for these applications are: - -- **UT1** (Universal Time 1): UT1 is a time scale based on the rotation of the Earth. - It is used to measure the positions of celestial objects relative to the Earth's - surface. UT1 is closely related to *Greenwich Mean Time (GMT)*, and the two time - scales are often used interchangeably. - -- **TAI** (International Atomic Time): TAI is a time scale based on the average - frequency of a set of atomic clocks. It is used to measure the positions of - celestial objects relative to the Earth's surface. +## Quickstart +Create an [`Epoch`](@ref): +```julia +# Create an Epoch from an ISO-formatted string +tai = Epoch("2022-10-02T12:04:23.043 TAI") -- **TT** (Terrestrial Time): TT is a time scale based on the motion of celestial - objects around the solar system barycenter (the center of mass of the solar system). - It is used to measure the positions of celestial objects relative to the Earth's surface. +# Create an Epoch from a Julian Date +jd = Epoch("JD 2451545.0") -- **TDB** (Barycentric Dynamical Time): TDB is a time scale based on the motion of - celestial objects around the solar system barycenter (the center of mass of the - solar system). It is used to measure the positions of celestial objects relative - to the solar system barycenter. - -- **TCB** (Barycentric Coordinate Time): TCB is a time scale based on the motion of - celestial objects around the solar system barycenter (the center of mass of the - solar system). It is used to measure the positions of celestial objects relative - to the solar system barycenter. - -- **TCG** (Geocentric Coordinate Time): TCG is a time scale based on the rotation of - the Earth. It is used to measure the positions of celestial objects relative to the - Earth's surface. - -- **Teph** (Ephemeris Time): Teph is a time scale based on the motion of celestial - objects around the solar system barycenter (the center of mass of the solar - system). It is used to measure the positions of celestial objects relative to - the solar system barycenter -- here considered equivalent of _TDB_. - -Of the seven time scales to be described here, one is atomic time (TAI), -one is solar time (UT1), one is an atomic/solar hybrid (UTC) and four are -dynamical times (TT, TCG, TCB, TDB). Other time scales of interest may also be the -ones associated to the different positioning systems. In particular: **GPS** (Global -Positioning System), **GLONASS** (Global Navigation Satellite System) and **GALILEO** -(Global Navigation Satellite System) times could be defined as a constant offset with -respect to TAI. +# Create an Epoch from a DateTime object and a timescale +dt = DateTime(2001, 6, 15, 0, 0, 0, 0.0) +e = Epoch(dt, TT) +``` -## Time in `JSMD` +Efficiently transform epochs between various timescales: +```julia +# Convert an Epoch from TAI to TDB +tai = Epoch("2022-10-02T12:04:23.043 TAI") +tdb = convert(TDB, tai) -Within `Tempo`, the way in which time is represented in `Tempo` is through the use of -[`Epoch`](@ref)s. `Epoch`s are an efficient, differentiable and precise way to represent -astronomical time. To parse an epoch object, two parameters shall be assigned: +# Convert an Epoch from TAI to UTC automatically handling leapseconds +utc = convert(UTC, tai) +``` -- **Timescale**: This parameter determines the timescale that the epoch is based on. - For example, it can be set to UTC, TAI, TDB, or TCB. This allows the user to convert - the epoch between different timescales if necessary. +## Tempo.jl vs AstroTime.jl +Tempo.jl and [AstroTime.jl](https://github.com/JuliaAstro/AstroTime.jl) are very similar libraries that allow transformations between various astronomical time representations. The major differences are: -- **Origin**: This parameter determines the origin of the epoch, which is the point in time - from which the epoch is measured. This can be in the form of a Julian date, a - Modified Julian date or any user-defined origin. - The origin can also be set to a specific event, such as J2000.0 or B1950.0. +- AstroTime.jl supports accurate Epoch transformations by leveraging high + precision arithmetics. +- Tempo.jl is more efficient when multiple timescales conversions must be + performed to convert a given Epoch (e.g., it does not allocate memory). -By assigning these two parameters, [`Epoch`](@ref)s can be used to represent time in a precise -manner, which is crucial for accurate timekeeping and coordination of events in a universe model. diff --git a/docs/src/lapi.md b/docs/src/lapi.md new file mode 100644 index 0000000..49b4439 --- /dev/null +++ b/docs/src/lapi.md @@ -0,0 +1,88 @@ +# [Low-level API](@id tempo_lapi) + +These functions are not meant to be used outside of the package. They are documented only to aid future developments of the package. + +## Epochs +```@docs +Tempo.AbstractEpochOrigin +Tempo.JulianDate +Tempo.JulianDate2000 +Tempo.ModifiedJulianDate +Tempo.ModifiedJulianDate2000 +Tempo.EpochConversionError +Tempo.parse_iso +``` + +## Timescale Offsets +```@docs +Tempo.offset +Tempo.offset_gps2tai + +Tempo.offset_tai2gps +Tempo.offset_tai2tt +Tempo.offset_tai2utc + +Tempo.offset_tcb2tdb +Tempo.offset_tcg2tt + +Tempo.offset_tdb2tt +Tempo.offset_tdb2tcb + +Tempo.offset_tt2tai +Tempo.offset_tt2tcg +Tempo.offset_tt2tdb +Tempo.offset_tt2tdbh + +Tempo.offset_utc2tai +``` + +## Timescale Types +```@docs +Tempo.TimeScaleNode +Tempo.AbstractTimeScale +Tempo.GlobalPositioningSystemTime +Tempo.BarycentricDynamicalTime +Tempo.HighPrecisionBarycentricDynamicalTime +Tempo.BarycentricCoordinateTime +Tempo.TerrestrialTime +Tempo.InternationalAtomicTime +Tempo.UniversalTime +Tempo.CoordinatedUniversalTime +Tempo.GeocentricCoordinateTime +``` + +## Conversions +```@docs +Base.convert + +Tempo.cal2jd +Tempo.calhms2jd +Tempo.fd2hms +Tempo.fd2hmsf +Tempo.hms2fd +Tempo.jd2cal +Tempo.jd2calhms + +Tempo.tai2utc +Tempo.utc2tai +``` + +## Leapseconds +```@docs +Tempo.Leapseconds +Tempo.LEAPSECONDS +Tempo.get_leapseconds +Tempo.leapseconds +``` + +## Miscellaneous +```@docs +Tempo.find_year +Tempo.find_month +Tempo.find_day +Tempo.fraction_of_day +Tempo.fraction_of_second +Tempo.isleapyear +Tempo.lastj2000dayofyear +Tempo.second_in_day +``` \ No newline at end of file diff --git a/docs/src/tmp.md b/docs/src/tmp.md new file mode 100644 index 0000000..92a3069 --- /dev/null +++ b/docs/src/tmp.md @@ -0,0 +1,84 @@ + +## Overview + +There are different ways to represent an epoch within `Tempo`, depending on the specific application. This section is here to help you choose the proper time representation and to present the capabilities of the module in transforming time between different representations. + +First of all, there is a deep difference in the way time is thought in the everyday life and +when dealing with space-related applications. +Whenever we say _the 12:35 of the 1st of January 2023_, we are merging three concepts: +the **calendar** (_1st January 2023_) and the **time representation** (_12:35_) together with +its **scale**. + + +Calculations in any scientific discipline may involve precise time, but what +sets astronomy apart is the number and variety of **time scales** that have to be used. +In fact, in astronomical applications the physical context of the “clock” matters, +whether it is on Earth, moving or stationary, or on a spacecraft. + +```@raw html +
+ Image +
Time Conversions - The difference in each timescale is shown with + respect to TAI.
+
+``` + +The most relevant time scales for these applications are: + +- **UT1** (Universal Time 1): UT1 is a time scale based on the rotation of the Earth. + It is used to measure the positions of celestial objects relative to the Earth's + surface. UT1 is closely related to *Greenwich Mean Time (GMT)*, and the two time + scales are often used interchangeably. + +- **TAI** (International Atomic Time): TAI is a time scale based on the average + frequency of a set of atomic clocks. It is used to measure the positions of + celestial objects relative to the Earth's surface. + +- **TT** (Terrestrial Time): TT is a time scale based on the motion of celestial + objects around the solar system barycenter (the center of mass of the solar system). + It is used to measure the positions of celestial objects relative to the Earth's surface. + +- **TDB** (Barycentric Dynamical Time): TDB is a time scale based on the motion of + celestial objects around the solar system barycenter (the center of mass of the + solar system). It is used to measure the positions of celestial objects relative + to the solar system barycenter. + +- **TCB** (Barycentric Coordinate Time): TCB is a time scale based on the motion of + celestial objects around the solar system barycenter (the center of mass of the + solar system). It is used to measure the positions of celestial objects relative + to the solar system barycenter. + +- **TCG** (Geocentric Coordinate Time): TCG is a time scale based on the rotation of + the Earth. It is used to measure the positions of celestial objects relative to the + Earth's surface. + +- **Teph** (Ephemeris Time): Teph is a time scale based on the motion of celestial + objects around the solar system barycenter (the center of mass of the solar + system). It is used to measure the positions of celestial objects relative to + the solar system barycenter -- here considered equivalent of _TDB_. + +Of the seven time scales to be described here, one is atomic time (TAI), +one is solar time (UT1), one is an atomic/solar hybrid (UTC) and four are +dynamical times (TT, TCG, TCB, TDB). Other time scales of interest may also be the +ones associated to the different positioning systems. In particular: **GPS** (Global +Positioning System), **GLONASS** (Global Navigation Satellite System) and **GALILEO** +(Global Navigation Satellite System) times could be defined as a constant offset with +respect to TAI. + +## Time in `JSMD` + +Within `Tempo`, the way in which time is represented in `Tempo` is through the use of +[`Epoch`](@ref)s. `Epoch`s are an efficient, differentiable and precise way to represent +astronomical time. To parse an epoch object, two parameters shall be assigned: + +- **Timescale**: This parameter determines the timescale that the epoch is based on. + For example, it can be set to UTC, TAI, TDB, or TCB. This allows the user to convert + the epoch between different timescales if necessary. + +- **Origin**: This parameter determines the origin of the epoch, which is the point in time + from which the epoch is measured. This can be in the form of a Julian date, a + Modified Julian date or any user-defined origin. + The origin can also be set to a specific event, such as J2000.0 or B1950.0. + +By assigning these two parameters, [`Epoch`](@ref)s can be used to represent time in a precise +manner, which is crucial for accurate timekeeping and coordination of events in a universe model. diff --git a/docs/src/tutorials/t01_epochs.md b/docs/src/tutorials/t01_epochs.md index 2c9b3b3..69d10c3 100644 --- a/docs/src/tutorials/t01_epochs.md +++ b/docs/src/tutorials/t01_epochs.md @@ -1,118 +1,175 @@ -# [Epoch handling and timescales conversions](@id tutorial_01_epochs) +# [Epochs Handling and Conversions](@id tutorial_01_epochs) ```@setup init using Tempo ``` -In this tutorial, the general workflow to be followed when dealing with time representations and their transformations is presented. +In this tutorial, the general workflow to be followed when dealing with time representations and their transformations is presented. In particular, most of the features of this package are designed around the [`Epoch`](@ref) data type, which differently from the [`DateTime`](@ref) object, provides the capability to represent time in different standard and user-define time scales. -The first step is to load the module: +## Creating Epochs +Time representions for space applications embed three different concepts: -```@repl -using Tempo +1. The representation type (e.g. Gregorian or Julian calendar representation) +2. The origin (e.g. J2000, JD, MJD, ...) +3. The time scale (e.g. TAI, TT, TDB, UTC, UT, ...) + +All three infromation are considered when building an [`Epoch`](@ref). In particular, within `Tempo`, the (interal) time representation is always based upon the Julian calendar, with the origin fixed at [`J2000`](@ref), i.e., the 1st of January 2000 at noon. Different timescales are instead available, with the default one being the [`TDB`](@ref). The set of pre-defined time scales supported by this package is: + +* [`TT`](@ref): [Terrestrial Time](https://en.wikipedia.org/wiki/Terrestrial_Time), is a time scale that is used for the prediction or recording of the positions of celestial bodies as measured by an observer on Earth. +* [`TDB`](@ref): [Barycentric Dynamical Time](https://en.wikipedia.org/wiki/Barycentric_Dynamical_Time) is a relativistic time scale that is used for the prediction or recording of the positions of celestial bodies relative to the solar system's barycenter. +* [`TAI`](@ref): [International Atomic Time](https://en.wikipedia.org/wiki/International_Atomic_Time) is a time scale based on the average frequency of a set of atomic clocks. +* [`TCG`](@ref): [Geocentric Coordinate Time](https://en.wikipedia.org/wiki/Geocentric_Coordinate_Time) is a relativistic coordinate time scale that is used for precise calculations of objects relative to the Earth. +* [`TCB`](@ref): [Barycentric Coordinate Time](https://en.wikipedia.org/wiki/Barycentric_Coordinate_Time) is a relativistic coordinate time scale that is used for precise calculations of objects in the Solar System. +* [`UTC`](@ref): [Coordinated Universal Time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) is the primary civil time standard which is kept within one second from the mean solar time (UT1). However, since the rotation of the Earth is irregular, leap seconds are periodically inserted to keep UTC within 0.9 seconds of UT1. +* [`TDBH`](@ref): Although TDBH is not an official time scale, it is here used to provide a more accurate transformation between [`TT`](@ref) and [`TDB`](@ref), with a maximum error fo about 10 μs between 1600 and 2200. See [`Tempo.offset_tt2tdbh`](@ref) for more details. +* [`GPS`](@ref): [GPS Time](https://gssc.esa.int/navipedia/index.php/Time_References_in_GNSS) is a continuous time scale defined by the GPS Control segment defined as a constant offset of 19s from [`TAI`](@ref). + + +### ISO Strings +With this in mind, many different ways are available to create a new `Epoch` object. The first is based upon the [ISO 8601](https://it.wikipedia.org/wiki/ISO_8601) concept, an international standard to represent dates and times. The desired timescale can be either specified by appending its acronym to the string or as a second argument, as follows: + +```@repl init +e = Epoch("2022-01-02T06:30:00.0 TT") +e = Epoch("2022-01-02T06:30:00.0") +e = Epoch("2022-01-02T06:30:00.0", TAI) ``` -This package contains the basic routines needed to handle `Epoch`s and their transformations -as well as some additional types to represent `Date`s, `Time` and `DateTime` objects. +As you can see, when we did not specify a timescale, [`TDB`](@ref) has been used by default. The usage of partial ISO strings is also supported: -**Then, how and epoch may be represented when using `Tempo`?** +```@repl init +e = Epoch("2020-01-01") -Well, depends on the actual use case. +e = Epoch("2021-01-30T01") -As already said, there are different time representations in `Tempo` and they do -depends on the actual information needed for the particular application. In general, when -dealing with _space_ applications, the a time representation shall _embed_ different informations: +e = Epoch("2022-06-12 UTC") +``` -1. The representation type (e.g. Gregorian or Julian calendar representation); -2. The origin (e.g. J2000, JD, MJD, ...); -3. The time scale (e.g. TAI, TT, TDB, UTC, UT, ...). +### Julian Dates -These three informations are considered when building an `Epoch`, which is the most complete -time representation available within `Tempo` and is the one suggested to the user. Then, to -handle such _complex_ time representation, different constructors are available for `Epoch` -and a _time transformation system_ is available. However, there are some assumptions that -are considered: +[`Epoch`](@ref) objects can also be created from Julian Dates, Modified Julian Dates as well as Julian days or seconds since [`J2000`](@ref). To parse a Julian Date, in days, the input string must be in the format `JD DDDDDDDDD.ffffff`: -1. The (internal) representation type of `Epoch` is always exploiting Julian calendar; -2. The (internal) `Epoch` origin is always `J2000`, i.e. 01 Jan 2000 at noon; -3. The `Epoch` timescale could be any of the _time transformation system_ ones. -4. The default timescale is `TDB`. +```@repl init +e = Epoch("JD 2451545.04") +e = Epoch("JD 2451545.04 TT") +``` -## Epoch creation +Similarly, for Modified Julian Dates, the string format is `MJD DDDDDDDDD.ffffff`: -To create a new `Epoch` object, there are different ways: +```@repl init +e = Epoch("MJD 51544.54") +e = Epoch("MJD 51544.54 TT") +``` -### From a ISO string (without scale): +When a prefix is not specified, the epoch constructor assumes the input is expressed as Julian days since [`J2000`](@ref): -```@example init -e = Epoch("2022-01-02T06:30:00.0") +```@repl init +e = Epoch("9.0") +e = Epoch("9.0 TT") ``` -### From ISO string (with scale): +As you can see, the timescale acronym can always be appended to the predefined string format to override the default time scale. Finally, it is also possible to create an epoch by specifing the number of seconds since [`J2000`](@ref). In the latter case, the constructor has a slightly different form and always requires the timescale argument: -```@example init -e = Epoch("2022-01-02T06:30:00.0 TT") +```@repl init +e = Epoch(60.0, TT) +e = Epoch(60.0, TerrestrialTime) +e = Epoch{TerrestrialTime}(60.0) ``` -### From a (partial) ISO string: +### DateTime +Finally, an [`Epoch`](@ref) can also be constructed from the [`DateTime`](@ref) object defined within this package: -```@example init -e = Epoch("2020-01-01") +```@repl init +dt = DateTime(2001, 6, 15, 0, 0, 0, 0.0) +e = Epoch(dt, TT) +e = Epoch(dt, TerrestrialTime) ``` -```@example init -e = Epoch("2021-01-30T01") +## Working with Epochs + +### Basic Operations +The [`Epoch`](@ref) type supports a limited subset of basic mathematical and logical operations on it. For example, the offset, in seconds, between two epochs can be computed by subtracting them: +```@repl init +e1 = Epoch(90.0, TT) +e2 = Epoch(50.0, TT) + +e1 - e2 + +e3 = Epoch(40, TAI) +e1 - e3 ``` +Notice that this operation can be performed only if the two epochs are defined on the same timescale. -```@example init -e = Epoch("2022-06-12 UTC") +Epochs can also be shifted forward and backwards in time by adding or subtracting an arbitrary number of seconds: +```@repl init +e1 = Epoch(30.0, TDB) +e1 += 50 +e1 -= 30.42 ``` -### From a Julian date: +You can check whether an epoch is greater than an other with the logical operators: +```@repl init +e1 = Epoch(50.0, UTC) +e2 = Epoch(50.0, UTC) + +e1 > e2 -```@example init -e = Epoch("JD 2451545.0") +e1 == e2 ``` +Again, the operations are supported only if the two epochs belong to the same timescale. + +Finally, it is also possible to construct ranges with [`Epoch`](@ref)s, with a default timestep of one Julian day. User-defined timesteps are assumed to be expressed in seconds. -### From a Modified Julian date: +```@repl init +e1 = Epoch("2024-01-01T12:00:00") -```@example init -e = Epoch("MJD 51544.5") +e2 = Epoch("2024-01-05T12:00:00") + +collect(e1:e2) + +collect(e1:172800:e2) ``` -### From Julian days since J2000: +### Julian Dates -```@example init -e = Epoch("9.0 TT") +A predefined set of functions is also provided to easily convert [`Epoch`] objects to Julian seconds, days and centuries since [`J2000`](@ref): + +```@repl init +e = Epoch("2024-01-01T12:00:00 TAI") + +j2000(e) +j2000s(e) +j2000c(e) ``` -### From seconds since J2000: +## Converting Between Time Scales -```@example init -e = Epoch(60.0, TAI) +Epoch transformations between the standard and user-defined timescales are simply performed through the [`convert`](@ref) method by specifying the target time scale + +```@repl init +e = Epoch(90.0, TT) +eTAI = convert(TAI, e) + +eTCG = convert(TCG, e) ``` -### From `DateTime`: +These transformations are based on a **directed** graph of timescales ([`TIMESCALES`](@ref)) existing within `Tempo`. Set of functions provide then the offsets in seconds between each pair of connected timescales, offering a simple, effective and efficient way to compute these transformations. -```@example init -dt = DateTime(2001, 6, 15, 0, 0, 0, 0.0) -e = Epoch(dt, TT) +```@repl init +e = Epoch(90.0, TT) +eTAI = convert(TAI, e) ``` -## Epoch transformations +### UTC and Leap Seconds -The epoch transformations are allowed by means of a **directed** graph of timescales available in -`Tempo`. While the `offset_xxx` functions provides the offsets in seconds between two timescales -(e.g. `offset_tai2tt` provide the offset in seconds to convert from `TAI` to `TT`), there -is a simple, effective and efficient entrypoint to all time transformations: the `convert` method. +A special remark must be made on the conversion between TAI and UTC. The offset between these two timescales is defined by a leap seconds, which are introduced to keep the UTC time scale within 0.9 seconds from UT1. Since the rotation of the Earth is irregular, it is not possible to predict when a new leap second will be introduced in the future. +The latest NAIF's leap second kernel ([LSK](https://naif.jpl.nasa.gov/pub/naif/generic_kernels/lsk)) is embedded within `Tempo` as a package artifact, which will be manually updated each time a new kernel is released, so that the user effort is minimised. Indeed, transforming an [`Epoch`](@ref) from a generic timescale to UTC is a simple as: -```@example init +```@repl init e = Epoch(90.0, TT) -# Convert to TAI -eTAI = convert(TAI, e) +eUTC = convert(UTC, e) ``` -Note that e time transformation system is a **directed** graph! To allow to transform back and forth -a given timescale, two transformations are necessary. An error will be displayed in case a -single transformation is assigned and the reverse one is called. +### UTC to UT1 + +The offset between UT1 and UTC, which depends upon the rotation of the Earth, is available in the Earth Orientation Parameters (EOP) provided by the International Earth Rotation and Reference System Service ([IERS](https://www.iers.org/IERS/EN/Home/home_node.html)). Since those parameters are also required to compute the orientation of the ITRF with respect to the ICRF, a decision has been made to define the UT1 timescale in [FrameTransformations.jl](https://github.com/JuliaSpaceMissionDesign/FrameTransformations.jl), a different package which enhances `Tempo` with the capability to transform from and to UT1. \ No newline at end of file diff --git a/docs/src/tutorials/t02_scales.md b/docs/src/tutorials/t02_scales.md index 333e485..b8793c6 100644 --- a/docs/src/tutorials/t02_scales.md +++ b/docs/src/tutorials/t02_scales.md @@ -1,220 +1,194 @@ -# [Timescales graphs and extensions](@id tutorial_02_scales) +# [Creating Custom Timescales](@id tutorial_02_scales) -In `Tempo`, timescales are connected each other via a **directed graph**. Thanks to the -structure of the `Tempo` module, it is possible to either extend the current graph of -scales or create a completely custom one. Both of this possibilities are the subject of -this tutorial. +In Tempo.jl, all timescales connections and epoch conversions are handled through a **directed graph**. A default graph ([`TIMESCALES`](@ref)), containing a set of predefined timescales is provided by this package. However, this package also provided a set of routines to either extend such graph or create a completely custom one. In this tutorial, we will explore both alternatives. - -```julia +```@setup init using Tempo ``` -## Create a timescales graph - -To create a computational directed graph to handle timescales, `Tempo` provides the `TimeSystem` -type. Therefore, let us define a new time transformation system called `TIMETRANSF`: - +## Defining a New Timescale +Custom timescales can be created with the [`@timescale`](@ref) macro, which automatically creates the required types and structures, given the timescale acronym, an integer ID and, eventually, the full name of the timescale. -```julia -const TIMETRANSF = TimeSystem{Float64}() +```@repl init +@timescale ET 15 EphemerisTime ``` - TimeSystem{Float64}(MultiGraphs.MappedNodeGraph{Tempo.TimeScaleNode{Float64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}}(Graphs.SimpleGraphs.SimpleDiGraph{Int64}(0, Vector{Int64}[], Vector{Int64}[]), Dict{Int64, Int64}(), Tempo.TimeScaleNode{Float64}[], Dict{Int64, Dict{Int64, Vector{Int64}}}(), Dict{Int64, Dict{Int64, Int64}}())) +The ID is an integer that is internally used to uniquely represent the timescale, whereas the acronym is used to alias such ID. It is also possible to define multiple acronyms associated to the same ID but you cannot assign multiple IDs to the same acronym. In case a full name is not provided, a default one will be built by appending `TimeScale` to the acronym. +!!! warning + The IDs from 1 to 10 are used to define the standard timescales of the package. To avoid unexpected behaviors, custom timescales should be registered with higher IDs. -This object contains a graph and the properties associated to the new time-system defined in -`TIMETRANSF`. Note that the computational graph at the moment is empty, thus, we need to -manually populate it with the new transformations. +In the previous example, we have created a custom timescale named `EphemerisTime`, with ID 15. We are now able to define epochs with respect to ET, but we cannot perform conversions towards other timescales until we register it in a graph system: -## Create a new timescale - -In order to insert a new timescale to the graph, a new timescale type alias shall be defined. -This can be easily done via the macro `@timescale`. This step requires 3 elements: -- The timescale acronym (user-defined). -- The timescale index (it is an `Int` used to uniquely represent the timescale). -- The timescale fullname. - - -```julia -@timescale DTS 1 DefaultTimeScale +```@setup etScale +using Tempo +@timescale ET 15 EphemerisTime ``` -## Register the new timescale +```@repl etScale +ep = Epoch(20.425, ET) -Once, created, the new timescale is ready to be registered. If it is the first scale registered -in the computational graph, than, nothing else than the type alias is needed and the -registration can be performed as follows: +convert(TT, ep) +``` +## Extending the Default Graph +In this section, the goal is to register ET as a zero-offset scale with respect to [`TDB`](@ref). To register this timescale in the default graph, we first need to define the offset functions of ET with respect to TDB: -```julia -add_timescale!(TIMETRANSF, DTS) +```@repl init +offset_tdb2et(sec::Number) = 0 +offset_et2tdb(sec::Number) = 0 ``` +Since we have assumed that the two scales are identical, our functions will always return a zero offset. Rememeber that timescales graph is **directed**, meaning that if the user desires to go back and forth between two timescales, both transformations must be defined. The input argument of such functions is always the number of seconds since J2000 expressed in the origin timescale. -```julia -TIMETRANSF.scales.nodes +Finally, the [`add_timescale!`](@ref) method can be used to register ET within the default graph: + +```@setup scale1 +using Tempo +@timescale ET 15 EphemerisTime +offset_tdb2et(sec::Number) = 0 +offset_et2tdb(sec::Number) = 0 ``` - 1-element Vector{Tempo.TimeScaleNode{Float64}}: - TimeScaleNode{Float64}(name=DTS, id=1) +```@repl scale1 +add_timescale!(TIMESCALES, ET, offset_tdb2et, parent=TDB, ftp=offset_et2tdb) +``` +If the inverse transformation (from ET to TDB) is not provided, only one-way epoch conversions will be allowed. We can now check that the desired timescale has been properly registered and performs the same as TDB: +```@repl scale1 +ep = Epoch("200.432 TT") -Instead, in case the timescale is linked to a parent one, an offset function shall be defined. -Remember that the computational graph is **directed**, i.e. the transformation to go back and -forth to the parent shall be defined if two-way transformations are desired. +convert(TDB, ep) -In this example, assume we want to register timescale `NTSA` and a timescale `NTSB`. -`NTSA` has `DTS` as parent and a constant offset of 1 second. `NTSB` has `NTSA` has parent -and a linear offset with slope of 1/86400. +convert(ET, ep) +``` -Then, first create the new scales: +## Creating a Custom Graph +To create a custom directed graph to handle timescales, Tempo.jl provides the [`TimeSystem`](@ref) type. Therefore, let us define a new time transformation system called `TIMETRANSF`: -```julia -@timescale NTSA 2 NewTimeScaleA -@timescale NTSB 3 NewTimeScaleB +```@repl init +const TIMETRANSF = TimeSystem{Float64}() ``` -Now, let us define the offset functions for `NTSA`: - +This object contains a graph and the properties associated to the new time-system defined in +`TIMETRANSF`. At the moment, the computational graph is empty and we need to manually populate it with the new transformations. -```julia -const OFFSET_DTS_TO_NTSA = 1.0 -@inline offset_dts2ntsa(sec::Number) = OFFSET_DTS_TO_NTSA -@inline offset_ntsa2dts(sec::Number) = -OFFSET_DTS_TO_NTSA; +We begin by creating a new timescale: +```@repl init +@timescale DTS 1 DefaultTimeScale ``` -We can now register `NTSA` to the computational graph using the `add_timescale!` method: +Once created, the new timescale is ready to be registered. If it is the first scale registered in the computational graph, nothing else than the type alias is needed and the registration can be performed as follows: +```@setup graph1 +using Tempo +const TIMETRANSF = TimeSystem{Float64}() -```julia -add_timescale!(TIMETRANSF, NTSA, offset_dts2ntsa, parent=DTS, ftp=offset_ntsa2dts) +@timescale DTS 1 DefaultTimeScale +@timescale NTSA 2 NewTimeScaleA ``` -Now, if we have a look to the computational graph, we'll se that `NTSA` is registered: - +```@repl graph1 +add_timescale!(TIMETRANSF, DTS) -```julia TIMETRANSF.scales.nodes ``` - 2-element Vector{Tempo.TimeScaleNode{Float64}}: - TimeScaleNode{Float64}(name=DTS, id=1) - - TimeScaleNode{Float64}(name=NTSA, id=2, parent=1) - +Instead, in case the timescale is linked to a parent one, offset functions shall be defined. In this example, assume we want to register the timescales `NTSA` and `NTSB` such that +`NTSA` has `DTS` as parent and a constant offset of 1 second, whereas`NTSB` has `NTSA` as parent and a linear offset with slope of 1/86400. +We begin by creating the first timescale: +```@repl init +@timescale NTSA 2 NewTimeScaleA +``` -As well as, since we have registered both direct and inverse transformations, there is the -possibility to transform back and forth from `NTSA` to `DTS`. We can easily see this looking -at the `paths` contained in the computational graph. Here the timescale are represented by means -of the type-alias unique integer assigned during the creation of the new type. +We then define its offset functions and register it in `TIMETRANSF` via the [`add_timescale!`](@ref) method: +```@repl graph1 +const OFFSET_DTS_TO_NTSA = 1.0 +offset_dts2ntsa(sec::Number) = OFFSET_DTS_TO_NTSA +offset_ntsa2dts(sec::Number) = -OFFSET_DTS_TO_NTSA -```julia -TIMETRANSF.scales.paths +add_timescale!(TIMETRANSF, NTSA, offset_dts2ntsa, parent=DTS, ftp=offset_ntsa2dts) ``` - Dict{Int64, Dict{Int64, Vector{Int64}}} with 2 entries: - 2 => Dict(1=>[2, 1]) - 1 => Dict(2=>[1, 2]) +Now, if we have a look to the computational graph, we'll see that `NTSA` is registered: +```@setup graph3 +using Tempo +const TIMETRANSF = TimeSystem{Float64}() -If now we create a `DTS` epoch, it is possible to use the custom time transformation system -to convert to `NTSA`: +@timescale DTS 1 DefaultTimeScale +@timescale NTSA 2 NewTimeScaleA +const OFFSET_DTS_TO_NTSA = 1.0 +@inline offset_dts2ntsa(::Number) = OFFSET_DTS_TO_NTSA +@inline offset_ntsa2dts(::Number) = -OFFSET_DTS_TO_NTSA -```julia -# Create the new epoch -# IMPORTANT: only J2000 seconds Epoch parser works with custom timescales. -e = Epoch(0.0, DTS) +add_timescale!(TIMETRANSF, DTS) +add_timescale!(TIMETRANSF, NTSA, offset_dts2ntsa, parent=DTS, ftp=offset_ntsa2dts) ``` - 2000-01-01T12:00:00.000 DTS +```@repl graph3 +TIMETRANSF.scales.nodes +``` +If now we create a `DTS` epoch, we can leverage our custom time transformation system to convert it to an epoch in the `NTSA` timescale: +```@repl graph3 +e = Epoch(0.0, DTS) -```julia -# Call `convert` using the custom time transformation system convert(NTSA, e, system=TIMETRANSF) ``` - 2000-01-01T12:00:01.000 NTSA - - -!!! note - The `system` is an optimal output if the `Tempo` time transformation system is used. +Whenever the conversions are based on a custom time system, the graph must be provided as an additional argument to the [`convert`](@ref) method. -To conclude the example, `NTSB` is has to be inserted. Let's assume that only the transformation -`NTSA -> NTSB` can be constructed. Then: +To conclude the example, we will now add the `NTSB` scale but only register the `NTSA -> NTSB` transformation: +```@repl graph3 +@timescale NTSB 3 NewTimeScaleB -```julia -# Create the linear offset function offset_ntsa2ntsb(sec::Number) = sec/86400.0 -# Register the timescale to the computational graph add_timescale!(TIMETRANSF, NTSB, offset_ntsa2ntsb, parent=NTSA) ``` Now, let's have a look to the nodes in the graph: +```@setup graph4 +using Tempo +const TIMETRANSF = TimeSystem{Float64}() -```julia -TIMETRANSF.scales.nodes -``` - - 3-element Vector{Tempo.TimeScaleNode{Float64}}: - TimeScaleNode{Float64}(name=DTS, id=1) - - TimeScaleNode{Float64}(name=NTSA, id=2, parent=1) - - TimeScaleNode{Float64}(name=NTSB, id=3, parent=2) - - +@timescale DTS 1 DefaultTimeScale +@timescale NTSA 2 NewTimeScaleA +@timescale NTSB 3 NewTimeScaleB -Where the new timescale has been registered with the alias `3`. Note however, that from `3` no -transformations are available: +const OFFSET_DTS_TO_NTSA = 1.0 +@inline offset_dts2ntsa(::Number) = OFFSET_DTS_TO_NTSA +@inline offset_ntsa2dts(::Number) = -OFFSET_DTS_TO_NTSA +@inline offset_ntsa2ntsb(sec::Number) = sec/86400.0 +add_timescale!(TIMETRANSF, DTS) +add_timescale!(TIMETRANSF, NTSA, offset_dts2ntsa, parent=DTS, ftp=offset_ntsa2dts) +add_timescale!(TIMETRANSF, NTSB, offset_ntsa2ntsb, parent=NTSA) -```julia -TIMETRANSF.scales.paths +e = Epoch(0.0, DTS) ``` - Dict{Int64, Dict{Int64, Vector{Int64}}} with 3 entries: - 2 => Dict(3=>[2, 3], 1=>[2, 1]) - 3 => Dict(2=>[], 1=>[]) - 1 => Dict(2=>[1, 2], 3=>[1, 2, 3]) - - -To conclude, let's test the new time transformation system. Let's take the previous `Epoch` -translate forward of 2 days and transform to `NTSA` and `NTSB`. We should obtain a translation -of `1 sec` and `3 sec` respectively: - - -```julia -# Translate the epoch -e += 2*86400 +```@repl graph4 +TIMETRANSF.scales.nodes ``` - 2000-01-03T12:00:00.000 DTS +You can see that the new timescale has been registered with the desired integer ID `3`. To test the complete system, we will translate forwad of 2 days the previous epoch `e` and transform it in both timescales: +```@repl graph4 +e += 2*86400 -```julia -# Convert to `NTSA` ea = convert(NTSA, e, system=TIMETRANSF) -``` - 2000-01-03T12:00:01.000 NTSA - - - -```julia -# Convert to `NTSB` eb = convert(NTSB, e, system=TIMETRANSF) ``` - 2000-01-03T12:00:03.000 NTSB - +As expected, we obtain translations of 1 and 3 seconds, respectively. diff --git a/src/constants.jl b/src/constants.jl index eda7fc7..07b8dad 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -9,7 +9,7 @@ const CENTURY2DAY = 36525 """ DJ2000 -Reference epoch (J2000.0), Julian Date (`2451545.0`). +Reference epoch [`J2000`](@ref), Julian Date (`2451545.0`). It is `12:00 01-01-2000`. """ const DJ2000 = 2451545 @@ -17,7 +17,7 @@ const DJ2000 = 2451545 """ DMJD -Reference epoch (J2000.0), Modified Julian Date (`51544.5`). +Reference epoch [`J2000`](@ref), Modified Julian Date (`51544.5`). """ const DMJD = 51544.5 diff --git a/src/convert.jl b/src/convert.jl index 2bc14b0..838e097 100644 --- a/src/convert.jl +++ b/src/convert.jl @@ -85,8 +85,9 @@ Convert hours, minutes and seconds to day fraction. ### Examples ```julia-repl -julia> Tempo.hms2fd(12, 0.0, 0.0) +julia> Tempo.hms2fd(12, 0, 0.0) 0.5 +``` """ function hms2fd(h::Integer, m::Integer, s::Number) @@ -285,32 +286,69 @@ function jd2cal(dj1::Number, dj2::Number) if dj < -68569.5 || dj > 1e9 throw(DomainError(dj, "the Julian Date shall be between -68569.5 and 1e9.")) end + + d1 = round(Int, dj1) + d2 = round(Int, dj2) + jd = d1 + d2 + + # Separate day and fraction + f1, f2 = promote(dj1 - d1, dj2 - d2) + + # Compute f1 + f2 + 0.5 using the compensated summation for improved accuracy. + s = 0.5*one(f1) + cs = zero(f1) + + for x in (f1, f2) + t = s + x + cs += abs(s) >= abs(x) ? (s-t) + x : (x-t) + s + s = t + if (s >= 1) + jd += 1 + s -= 1 + end + end - # Copy the date, big then small, and re-align to midnight - if abs(dj1) ≥ abs(dj2) - d1, d2 = promote(dj1, dj2) - else - d1, d2 = promote(dj2, dj1) + f = s + cs + cs = f - s + + # Deal with negative f + if f < 0 + # Compensated summation: assume that |s| <= 1.0 + f = s + 1 + cs += (1 - f) + s + s = f + f = s + cs + cs = f - s + jd -= 1 + end + + # Deal with f that is 1.0 or more (when rounded to double) + if ((f-1) >= -eps(f)/4) + + # Compensation summation: assume that |s| <= 1.0 + t = s - 1 + cs += (s - t) - 1 + s = t + f = s + cs + if (-eps()/2 < f) + jd += 1 + f = max(f, zero(f)) + end end - d2 -= 0.5 - - # Separate day and fraction - f1 = mod(d1, 1) - f2 = mod(d2, 1) - fd = mod(f1 + f2, 1) - - d = round(Int, d1 - f1) + round(Int, d2 - f2) + round(Int, f1 + f2 - fd) - jd = round(Int, d) + 1 - - # Express day in Gregorian calendar - f = jd + 1401 + (((4 * jd + 274277) ÷ 146097) * 3) ÷ 4 - 38 - e = 4 * f + 3 - g = mod(e, 1461) ÷ 4 - h = 5 * g + 2 - D = mod(h, 153) ÷ 5 + 1 - M = mod(h ÷ 153 + 2, 12) + 1 - Y = e ÷ 1461 - 4716 + (12 + 2 - M) ÷ 12 + # Express day in Gregorian calendar + l = jd + 68569 + n = (4l) ÷ 146097 + l -= (146097n + 3) ÷ 4 + i = (4000*(l + 1)) ÷ 1461001 + l -= (1461i) ÷ 4 - 31 + k = (80l) ÷ 2447 + + D = (l - (2447k) ÷ 80) + l = k ÷ 11 + M = k + 2 - 12l + Y = 100*(n - 49) + i + l + fd = f return Y, M, D, fd diff --git a/src/datetime.jl b/src/datetime.jl index 9da68bf..53f58ad 100644 --- a/src/datetime.jl +++ b/src/datetime.jl @@ -260,7 +260,7 @@ function Base.show(io::IO, t::Time) h = lpad(hour(t), 2, '0') m = lpad(minute(t), 2, '0') s = lpad(second(t), 2, '0') - sf = lpad(convert(Int, round(1e4*fraction_of_second(t), digits=0)), 4, '0') + sf = lpad(convert(Int, floor(1e4*fraction_of_second(t), digits=0)), 4, '0') return print(io, h, ":", m, ":", s, ".", sf) end diff --git a/src/epoch.jl b/src/epoch.jl index 2734c1f..940fefd 100644 --- a/src/epoch.jl +++ b/src/epoch.jl @@ -47,29 +47,24 @@ If it is not declared, [`TDB`](@ref) will be used as a default timescale. ### Examples ```julia-repl -# standard ISO string julia> Epoch("2050-01-01T12:35:15.0000 TT") 2050-01-01T12:35:14.9999 TT -# standard ISO string (without scale) julia> Epoch("2050-01-01T12:35:15.0000") 2050-01-01T12:35:14.9999 TDB -# Parse Julian Dates julia> Epoch("JD 2400000.5") 1858-11-17T00:00:00.0000 TDB -# Parse Modified Julian Dates julia> Epoch("MJD 51544.5") 2000-01-01T12:00:00.0000 TDB -# Parse Julian Dates since J2000 julia> Epoch("12.0") 2000-01-13T12:00:00.0000 TDB -# All Julian Date parsers allow timescales julia> Epoch("12.0 TT") 2000-01-13T12:00:00.0000 TT +``` """ struct Epoch{S,T} scale::S @@ -191,6 +186,9 @@ end # Operations Base.:-(e1::Epoch{S}, e2::Epoch{S}) where S = value(e1) - value(e2) +function Base.:-(::Epoch{S1}, ::Epoch{S2}) where {S1, S2} + throw(ErrorException("only epochs defined in the same timescale can be subtracted.")) +end Base.:+(e::Epoch, x::Number) = Epoch(value(e) + x, timescale(e)) Base.:-(e::Epoch, x::Number) = Epoch(value(e) - x, timescale(e)) diff --git a/src/origin.jl b/src/origin.jl index 297a7db..561be5d 100644 --- a/src/origin.jl +++ b/src/origin.jl @@ -40,7 +40,7 @@ for (name, acr, off, start) in """ const $acr = $name() - export $name, $acr + export $acr Base.show(io::IO, ::$name) = print(io, "$($acro_str)") Base.tryparse(::Val{Symbol($acro_str)}) = $acr diff --git a/src/parse.jl b/src/parse.jl index 5b14e86..094a884 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -1,5 +1,3 @@ -export parse_iso - function tryparsenext_base10( str::AbstractString, i::Int, len::Int, min_width::Int=1, max_width::Int=0 ) diff --git a/src/scales.jl b/src/scales.jl index 227a95e..85bba3a 100644 --- a/src/scales.jl +++ b/src/scales.jl @@ -87,7 +87,6 @@ julia> add_timescale!(ts, TSB, offset_tsa2tsb; parent=TSA, ftp=offset_tsb2tsa) ### See also See also [`@timescale`](@ref) and [`add_timescale!`](@ref). - """ struct TimeSystem{T<:Number} scales::MappedNodeGraph{TimeScaleNode{T},SimpleDiGraph{Int}} @@ -249,6 +248,7 @@ julia> child_to_root(x::Number) = -13.3; julia> add_timescale!(SYSTEM, RTS) julia> add_timescale!(SYSTEM, CTS, root_to_child; parent=RTS, ftp=child_to_root) +``` ### See also See also [`@timescale`](@ref) and [`TimeSystem`](@ref). @@ -430,19 +430,19 @@ in the graph. ### Example ```@example -# define a new timescale type alias +# Define a new timescale type alias @timescale NTS 100 NewTimeScale -# define offset to and from another timescale in the graph +# Define offset to and from another timescale in the graph offset_ffp(seconds) = 1.0 offset_ftp(seconds) = -1.0 -# connect to the graph, with the parent node (TDB in this example) +# Connect to the graph, with the parent node (TDB in this example) add_timescale!(TIMESCALES, NTS, offset_ffp, parent=TDB, ftp=offset_ftp) +``` ### See also -See also [`@timescale`](@ref), [`TimeScaleNode`](@ref) and [`add_timescale`](@ref). -``` +See also [`@timescale`](@ref) and [`add_timescale!`](@ref). """ const TIMESCALES = TimeSystem{Float64}() diff --git a/test/Tempo/epoch.jl b/test/Tempo/epoch.jl index ff9dcac..1c50a28 100644 --- a/test/Tempo/epoch.jl +++ b/test/Tempo/epoch.jl @@ -52,12 +52,21 @@ ) @test Epoch(0.0, TDB) ≈ Epoch("2000-01-01T12:00:00.0000 TDB") + + # Test rounding errors + t = DateTime(Epoch(21, TT)).time + @test t.second == 21 + @test t.fraction == 0.0 end # Colon Operator e1 = Epoch("2004-05-14T16:43:00 UTC") e2 = e1 + floor(10000*rand())*86400 + e3 = Epoch("232.0 TT") + + @test_throws ErrorException e3-e1 + ems = e1:e2 for j = 2:lastindex(ems) @test ems[j] == e1 + 86400*(j-1) diff --git a/test/Tempo/parse.jl b/test/Tempo/parse.jl index 1408656..2c16fd6 100644 --- a/test/Tempo/parse.jl +++ b/test/Tempo/parse.jl @@ -1,19 +1,19 @@ @testset "Function parse_iso" begin - @test all(parse_iso("2021") .== (2021, 1, 1, 0, 0, 0, 0)) - @test all(parse_iso("2022-11") .== (2022, 11, 1, 0, 0, 0, 0)) - @test all(parse_iso("2022-12-20") .== (2022, 12, 20, 0, 0, 0, 0)) - @test all(parse_iso("2022-12-20T12") .== (2022, 12, 20, 12, 0, 0, 0)) - @test all(parse_iso("2022-12-20T13:32") .== (2022, 12, 20, 13, 32, 0, 0)) - @test all(parse_iso("2022-12-20T13:32:12") .== (2022, 12, 20, 13, 32, 12, 0)) - @test all(parse_iso("2022-12-20T13:32:12.2132") .== (2022, 12, 20, 13, 32, 12, 0.2132)) - @test all(parse_iso("2022-12-20T12:00:00.0 TDB") .== (2022, 12, 20, 12, 0, 0, 0)) + @test all(Tempo.parse_iso("2021") .== (2021, 1, 1, 0, 0, 0, 0)) + @test all(Tempo.parse_iso("2022-11") .== (2022, 11, 1, 0, 0, 0, 0)) + @test all(Tempo.parse_iso("2022-12-20") .== (2022, 12, 20, 0, 0, 0, 0)) + @test all(Tempo.parse_iso("2022-12-20T12") .== (2022, 12, 20, 12, 0, 0, 0)) + @test all(Tempo.parse_iso("2022-12-20T13:32") .== (2022, 12, 20, 13, 32, 0, 0)) + @test all(Tempo.parse_iso("2022-12-20T13:32:12") .== (2022, 12, 20, 13, 32, 12, 0)) + @test all(Tempo.parse_iso("2022-12-20T13:32:12.2132") .== (2022, 12, 20, 13, 32, 12, 0.2132)) + @test all(Tempo.parse_iso("2022-12-20T12:00:00.0 TDB") .== (2022, 12, 20, 12, 0, 0, 0)) for _ in 1:10 # s, ry, rm, rd, rH, rM, rS, rF = generate_random_epoch() - # @test all(isapprox.(parse_iso(s),(ry, rm, rd, rH, rM, rS, rF); rtol=1e-3)) + # @test all(isapprox.(Tempo.parse_iso(s),(ry, rm, rd, rH, rM, rS, rF); rtol=1e-3)) s, ry, rm, rd, rH, rM, rS, rF = _random_datetime_isostr() - @test all(isapprox.(parse_iso(s), (ry, rm, rd, rH, rM, rS, rF); rtol=1e-3)) + @test all(isapprox.(Tempo.parse_iso(s), (ry, rm, rd, rH, rM, rS, rF); rtol=1e-3)) end end