Skip to content

Time

In the beginning [time] was created. This has made a lot of people very angry and has been widely regarded as a bad move.

-- Douglas Adams, The Restaurant at the End of the Universe

Since astrodynamics is the study of the motion of objects in space, time is a fundamental quantity to the package. Unfortunately, time can quickly become a complex topic with many different time systems, formats, and conventions.

Brahe attempts to solve this challenge by providing the Epoch class to represent a single instant in time. An Epoch can be initialized in a variety of ways, it can be manipulated with simple arithmetic operations, and and it can be compared to other Epoch instances. Brahe ensures that all of the complexities of time systems, formats, and conventions are handled internally by the Epoch class so that users can work with time in a simple and intuitive way.

There are more ways to work with Epochs and time than are covered here. Checkout the Time User Guide or the API Reference for more details and examples of working with time in Brahe.

Epoch Creation

The Epoch class can be initialized in a variety of ways:

import brahe as bh
from datetime import datetime

# Create from calendar date
epoch_1 = bh.Epoch(2024, 1, 1)
epoch_2 = bh.Epoch(2024, 1, 1, 0, 0, 0)

# Create from ISO 8601 string
epoch_3 = bh.Epoch("2024-01-01T00:00:00Z")

# Create from string with time system
epoch_4 = bh.Epoch("2024-01-00 00:00:00 GPS")

# Create from datetime
epoch_5 = bh.Epoch(datetime(2024, 1, 1, 0, 0, 0))

# Current instant
epoch_6 = bh.Epoch.now()
#[allow(unused_imports)]
use brahe as bh;

fn main() {
    // Create from calendar date
    let _epoch_1 = bh::Epoch::from_date(2024, 1, 1, bh::TimeSystem::UTC);
    let _epoch_2 = bh::Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, bh::TimeSystem::UTC);

    // // Create from ISO 8601 string
    let _epoch_3 = bh::Epoch::from_string("2024-01-01T00:00:00Z");

    // Create from String with time system
    let _epoch_4 = bh::Epoch::from_string("2024-01-00 00:00:00 GPS");

    // Current instant
    let _epoch_5 = bh::Epoch::now();
}

Epoch Operations

Brahe supports intuitive operations on time through arithemtic operations on Epoch instances.

SI Units

All floating point addition/subtraction values are assumed to be in seconds. All differences between Epoch instances are returned in seconds.

import brahe as bh

# Initialize EOP
bh.initialize_eop()

# Start with an epoch
epoch = bh.Epoch(2024, 1, 1)

# Add time (in seconds)
epoch_plus_1_day = epoch + 86400    # Add one day
epoch_plus_1_hour = epoch + 3600    # Add one hour
epoch_plus_1_ns = epoch + 1e-9      # Add one nanosecond

# Subtract time (in seconds)
epoch_minus_1_day = epoch - 86400   # Subtract one day
epoch_minus_1_hour = epoch - 3600   # Subtract one hour
epoch_minus_1_ns = epoch - 1e-9     # Subtract one nan

# Get difference between two epochs (in seconds)
difference = epoch_plus_1_day - epoch  # Should be 86400 seconds
print(f"Difference in seconds: {difference:.2f}")

# Comparison operations
print(f"epoch < epoch_plus_1_day: {epoch < epoch_plus_1_day}")
print(f"epoch == epoch_minus_1_day: {epoch == epoch_minus_1_day}")
print(f"epoch > epoch_minus_1_day: {epoch > epoch_minus_1_day}")
#[allow(unused_imports)]
use brahe as bh;

fn main() {
    // Initialize EOP provider
    bh::initialize_eop().unwrap();

    // Create from calendar date
    let epoch = bh::Epoch::from_date(2024, 1, 1, bh::TimeSystem::UTC);

    // Add time (in seconds)
    let epoch_plus_1_day = epoch + 86400.0;    // Add one day
    let _epoch_plus_1_hour = epoch + 3600.0;    // Add one hour
    let _epoch_plus_1_ns = epoch + 1e-9;        // Add one nanosecond

    // Subtract time (in seconds)
    let _epoch_minus_1_day = epoch - 86400.0;   // Subtract one day
    let _epoch_minus_1_hour = epoch - 3600.0;   // Subtract one hour
    let _epoch_minus_1_ns = epoch - 1e-9;       // Subtract one nanosecond

    // Get difference between two epochs (in seconds)
    let difference = epoch_plus_1_day - epoch; // Should be 86400 seconds
    println!("Difference in seconds: {:.2}", difference);

    // Comparison operations
    println!("epoch < epoch_plus_1_day: {}", epoch < epoch_plus_1_day);
    println!("epoch == epoch_minus_1_day: {}", epoch == _epoch_minus_1_day);
    println!("epoch > epoch_minus_1_day: {}", epoch > _epoch_minus_1_day);
}
Output
1
2
3
4
Difference in seconds: 86400.00
epoch < epoch_plus_1_day: True
epoch == epoch_minus_1_day: False
epoch > epoch_minus_1_day: True
1
2
3
4
Difference in seconds: 86400.00
epoch < epoch_plus_1_day: true
epoch == epoch_minus_1_day: false
epoch > epoch_minus_1_day: true

Epoch Output

There are also a variety of ways to output Epoch instances as other represenstations of instances in time.

import brahe as bh

# Create an Epoch
epoch = bh.Epoch(2024, 1, 1)

# Output as iso string
print(f"Epoch as ISO 8601 string: {epoch}")

# Output as Modified Julian Date
print(f"Epoch as Modified Julian Date: {epoch.mjd()}")

# Output as calendar date
print(f"Epoch as calendar date tuple: {epoch.to_datetime()}")

# Output as UNIX timestamp
print(f"Epoch as UNIX timestamp: {epoch.unix_timestamp()}")

# Output as GPS time
print(f"Epoch as GPS time: {epoch.gps_date()}")

# Output as Python datetime
print(f"Epoch as Python datetime: {epoch.to_pydatetime()}")
#[allow(unused_imports)]
use brahe as bh;

fn main() {
    // Create an Epoch
    let epoch = bh::Epoch::from_date(2024, 1, 1, bh::TimeSystem::UTC);

    // Output as iso string
    println!("Epoch as ISO 8601 string: {}", epoch);

    // Output as Modified Julian Date
    println!("Epoch as Modified Julian Date: {}", epoch.mjd());

    // Output as calendar date
    println!("Epoch as calendar date tuple: {:?}", epoch.to_datetime());

    // Output as UNIX timestamp
    println!("Epoch as UNIX timestamp: {}", epoch.unix_timestamp());

    // Output as GPS time
    println!("Epoch as GPS time: {:?}", epoch.gps_date());
}
Output
1
2
3
4
5
6
Epoch as ISO 8601 string: 2024-01-01 00:00:00.000 UTC
Epoch as Modified Julian Date: 60310.0
Epoch as calendar date tuple: (2024, 1, 1, 0, 0, 0.0, 0.0)
Epoch as UNIX timestamp: 1704067200.0
Epoch as GPS time: (2295, 86417.99999980722)
Epoch as Python datetime: 2024-01-01 00:00:00+00:00
1
2
3
4
5
Epoch as ISO 8601 string: 2024-01-01 00:00:00.000 UTC
Epoch as Modified Julian Date: 60310
Epoch as calendar date tuple: (2024, 1, 1, 0, 0, 0.0, 0.0)
Epoch as UNIX timestamp: 1704067200
Epoch as GPS time: (2295, 86417.99999980722)

Time Ranges

Since it's common to work with time ranges, Brahe provides the TimeRange iterator to quickly generate a range of epochs with a specified time step.

1
2
3
4
5
6
7
8
9
import brahe as bh

# Initialize EOP
bh.initialize_eop()

# Get epochs in a range every 6 hours
# Just like python's range it is inclusive of the start and exclusive of the end
for epoch in bh.TimeRange(bh.Epoch(2024, 1, 1), bh.Epoch(2024, 1, 2), 6*3600):
    print(epoch)
#[allow(unused_imports)]
use brahe as bh;

fn main() {
    // Initialize EOP provider
    bh::initialize_eop().unwrap();

    // Get epochs in a range every 6 hours
    // It is inclusive of the start and exclusive of the end
    for epoch in bh::TimeRange::new(
                    bh::Epoch::from_date(2024, 1, 1, bh::TimeSystem::UTC), 
                    bh::Epoch::from_date(2024, 1, 2, bh::TimeSystem::UTC), 
                    6.0*3600.0) {
        println!("{}", epoch);
    }
}
Output
1
2
3
4
2024-01-01 00:00:00.000 UTC
2024-01-01 06:00:00.000 UTC
2024-01-01 12:00:00.000 UTC
2024-01-01 18:00:00.000 UTC
1
2
3
4
2024-01-01 00:00:00.000 UTC
2024-01-01 06:00:00.000 UTC
2024-01-01 12:00:00.000 UTC
2024-01-01 18:00:00.000 UTC

Time Systems

In astrodynamics, we often deal with models and measurements in other time systems (e.g. GPS, TAI, TT, etc.). Brahe provides support for working with different time systems through the TimeSystem enum. When creating an Epoch, you can specify the time system of the input time, and Brahe will handle the conversion to the internal time system (TAI) for you. Similarly when outputting an Epoch, you can specify the desired time system for the output, and Brahe will handle the conversion for you. Comparisons between Epoch instances are time-system aware, so you can compare Epoch instances in different time systems and Brahe will handle the conversion for you.

import brahe as bh

# Create from calendar date
epoch_1 = bh.Epoch(2024, 1, 1, time_system=bh.TimeSystem.UTC)
epoch_2 = bh.Epoch(2024, 1, 1, 0, 0, 0, time_system=bh.TimeSystem.GPS)
print(f"Epoch 1: {epoch_1}")
print(f"Epoch 2: {epoch_2}")
print(f"(Epoch 2) - (Epoch 1): {epoch_2 - epoch_1} seconds")

# Compare two Epochs
epoch_3 = bh.Epoch(2024, 1, 1, time_system=bh.TimeSystem.TAI)
epoch_4 = bh.Epoch(2024, 1, 1, time_system=bh.TimeSystem.UTC)
print(f"Epoch 3: {epoch_3}")
print(f"Epoch 4: {epoch_4}")
print(f"Epoch 3 > Epoch 4: {epoch_3 > epoch_4}")

# Output as MJD in time system
print(f"Epoch 1 MJD (TT): {epoch_1.mjd_as_time_system(bh.TimeSystem.TT)}")
print(f"Epoch 2 MJD (TT): {epoch_2.mjd_as_time_system(bh.TimeSystem.TT)}")
#[allow(unused_imports)]
use brahe as bh;

fn main() {
    // Create from calendar date
    let epoch_1 = bh::Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, bh::TimeSystem::UTC);
    let epoch_2 = bh::Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, bh::TimeSystem::GPS);
    println!("Epoch 1: {}", epoch_1);
    println!("Epoch 2: {}", epoch_2);
    println!("(Epoch 2) - (Epoch 1): {} seconds", epoch_2 - epoch_1);

    // Compare two Epochs
    let epoch_3 = bh::Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, bh::TimeSystem::TAI);
    let epoch_4 = bh::Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, bh::TimeSystem::UTC);
    println!("Epoch 3: {}", epoch_3);
    println!("Epoch 4: {}", epoch_4);
    println!("Epoch 3 > Epoch 4: {}", epoch_3 > epoch_4);

    // Output as MJD in time system
    println!("Epoch 1 MJD (TT): {}", epoch_1.mjd_as_time_system(bh::TimeSystem::TT));
    println!("Epoch 2 MJD (TT): {}", epoch_2.mjd_as_time_system(bh::TimeSystem::TT));
}
Output
1
2
3
4
5
6
7
8
Epoch 1: 2024-01-01 00:00:00.000 UTC
Epoch 2: 2024-01-01 00:00:00.000 GPS
(Epoch 2) - (Epoch 1): -18.0 seconds
Epoch 3: 2024-01-01 00:00:00.000 TAI
Epoch 4: 2024-01-01 00:00:00.000 UTC
Epoch 3 > Epoch 4: False
Epoch 1 MJD (TT): 60310.00080074074
Epoch 2 MJD (TT): 60310.00059240741
1
2
3
4
5
6
7
8
Epoch 1: 2024-01-01 00:00:00.000 UTC
Epoch 2: 2024-01-01 00:00:00.000 GPS
(Epoch 2) - (Epoch 1): -18 seconds
Epoch 3: 2024-01-01 00:00:00.000 TAI
Epoch 4: 2024-01-01 00:00:00.000 UTC
Epoch 3 > Epoch 4: false
Epoch 1 MJD (TT): 60310.00080074074
Epoch 2 MJD (TT): 60310.00059240741