Metadata-Version: 2.4
Name: gri-nsepoch
Version: 0.2.3
Summary: Nanosecond-accurate time representation with embedded epochs and flexible parsing
Project-URL: Homepage, https://geosolresearch.com
Project-URL: Repository, https://gitlab.com/geosol-foss/python/gri-nsepoch
Project-URL: Issues, https://gitlab.com/geosol-foss/python/gri-nsepoch/-/issues
Project-URL: Changelog, https://gitlab.com/geosol-foss/python/gri-nsepoch/-/releases
Author-email: GeoSol Research Inc <contact@geosolresearch.com>
License-Expression: MIT
License-File: LICENSE
Keywords: datetime,epoch,nanosecond,precision,time
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.12
Requires-Dist: gri-memoize>=0.2.2
Requires-Dist: pytz>=2025.2
Description-Content-Type: text/markdown

[![GeoSol Research Logo](https://geosolresearch.com/logos/foss_logo.png "GeoSol Research")](https://geosolresearch.com)

# NsEpoch (Nanosecond Epoch Time)

Nanosecond-accurate time representation with embedded epochs, arithmetic operations, and flexible string parsing.

## Overview

gri-nsepoch provides three classes -- `Time`, `Delta`, and `Epoch` -- for working with time at nanosecond precision. Unlike `datetime` (microsecond resolution) or floating-point seconds (which lose precision beyond ~100 ns at typical epoch distances), gri-nsepoch stores all durations as integer nanoseconds internally, eliminating floating-point accumulation errors.

Times are always relative to an `Epoch` (defaulting to Unix epoch, 1970-01-01). Subtracting two `Time` objects produces a `Delta`. Adding a `Delta` to a `Time` produces a new `Time`. The library interoperates with `datetime`, `timedelta`, and raw numeric types.

The package also installs `ssep`, a command-line tool for converting between time formats.

Requires Python 3.12+.

## Mathematical Background

Standard IEEE 754 double-precision floats provide ~15-17 significant digits. A Unix timestamp for the year 2025 is approximately 1.74 x 10^9 seconds. Representing this with nanosecond precision requires 18+ digits -- exceeding float64 capacity. By storing nanoseconds as arbitrary-precision Python integers, gri-nsepoch avoids this limitation entirely:

    internal storage: int nanoseconds (no precision loss)
    float seconds:    limited to ~100 ns precision at current epoch distances
    datetime:         limited to microsecond resolution

## Installation

```bash
pip install gri-nsepoch
```

For development:

```bash
git clone https://gitlab.com/geosol-foss/python/gri-nsepoch.git
cd gri-nsepoch
uv sync
```

## Quick Start

```python
from gri_nsepoch import Time, Delta, Epoch

# Current time
now = Time()
print(now.iso)          # 2025-06-15T14:30:22.123+00:00

# From a string
t = Time.from_str("2025-01-15 08:30:00")
print(t.date_pp())      # 2025-01-15
print(t.time_pp(d=3))   # 08:30:00.000

# Arithmetic
t2 = t + Delta(s=3600)  # Add one hour
delta = t2 - t
print(delta.hms_pp())   # 01:00:00

# Nanosecond precision
t3 = Time(ns=1_000_000_123)
print(t3.time_pp(d=9))  # 00:00:01.000000123
```

## Time Class

`Time` represents an absolute point in time as nanoseconds since an epoch.

**Constructors:**

```python
t = Time()                        # Current time (UTC)
t = Time(ns=0)                    # Unix epoch exactly
t = Time(s=1e9)                   # From seconds (some ns precision loss)
t = Time(ns=0, epoch=86400)       # Midnight Jan 2, 1970 as epoch
t = Time.from_str("2025-01-15")   # Parse from string
t = Time.from_str("2025-01-15 08:30:00", pytz_tz="America/Denver")
```

**Properties and formatting:**

```python
t = Time.from_str("2025-06-15T14:30:22.123")

t.ns                # Integer nanoseconds since epoch (no precision loss)
t.secs              # Float seconds since epoch (some precision loss)
t.dt                # datetime object (microsecond resolution)
t.epoch             # The Epoch object
t.jd                # Julian Date as (jd, fractional_day) tuple

t.iso               # "2025-06-15T14:30:22.123+00:00"
t.date_pp()         # "2025-06-15"
t.time_pp(d=6)      # "14:30:22.123000"
t.dt_pp(d=3)        # "2025-06-15 14:30:22.123"
t.ssep_pp(s=True)   # "1,750,000,222" (with thousands separator)
t.mjd_pp(d=3)       # "60840-14:30:22.123"
```

**Arithmetic:**

- `Time + Delta` -> `Time`
- `Time - Time` -> `Delta`
- `Time - Delta` -> `Time`
- `Time + Time` -> TypeError

**Timer usage:**

```python
start = Time()
# ... do work ...
elapsed = start.delta_now()
print(elapsed.hms_pp(d=3))  # "00:00:01.234"
```

## Delta Class

`Delta` represents a time duration with nanosecond precision.

```python
d = Delta(ns=5_500_000_000)    # 5.5 seconds
d = Delta(s=5.5)               # Same, with possible ns precision loss

d.ns               # 5500000000
d.secs             # 5.5
d.timedelta        # datetime.timedelta(seconds=5, microseconds=500000)
d.hms_pp(d=3)      # "00:00:05.500"
d.secs_pp(s=True)  # "5"

# Component access
d.delta_ints.SS    # 5 (seconds component)
d.delta_ints.MS    # 500 (milliseconds component)
```

## Epoch Class

`Epoch` is a named reference point in time, stored as seconds since Unix epoch (1970-01-01). It subclasses `int`, so it works directly in arithmetic.

```python
unix = Epoch()                      # Unix epoch (default)
gps = Epoch(315964800, "GPS")       # GPS epoch (Jan 6, 1980)
custom = Epoch(946684800, "Y2K")    # Year 2000

print(gps)       # "GPS"
print(int(gps))  # 315964800

# Use with Time
t = Time(ns=0, epoch=gps)  # GPS epoch start
```

## String Parsing

`Time.from_str()` recognizes many common formats without requiring a format string:

- ISO 8601: `"2025-01-15T08:30:00"`, `"2025-01-15T08:30:00Z"`, `"2025-01-15T08:30:00+05:00"`
- Date-time: `"2025-01-15 08:30:00"`, `"2025/01/15 08:30:00"`
- Date only: `"2025-01-15"`, `"2025/01/15"`
- Epoch seconds: `"1750000000"`, `"1750000000.123456789"`
- Modified Julian Day: `"MJD60840"`

For non-standard formats, pass a `strftime` pattern:

```python
t = Time.from_str("15-Jun-2025 14:30", "%d-%b-%Y %H:%M")
```

## CLI Tool (ssep)

The `ssep` command converts any recognized time format to ISO and seconds-since-epoch:

```bash
$ ssep "2025-01-15 08:30:00"
ISO:   2025-01-15T08:30:00+00:00
Epoch: 1970-01-01
SSEP:  1736929800

$ ssep "2025-01-15 08:30:00" -e "2025-01-01"
ISO:   2025-01-15T08:30:00+00:00
Epoch: 2025-01-01
SSEP:  1236600 (unix)

$ ssep  # No argument prints current time
ISO:   2025-06-15T14:30:22+00:00
Epoch: 1970-01-01
SSEP:  1750000222
```

## Dependencies

- **gri-memoize**: Per-instance caching of computed properties
- **pytz**: Timezone-aware string parsing


## Other Projects

Current list of other [GRI FOSS Projects](https://gitlab.com/geosol-foss/python/gri-nsepoch/-/blob/main/.docs_other_projects.md) we are building and maintaining.

## License

MIT License. See [LICENSE](https://gitlab.com/geosol-foss/python/gri-nsepoch/-/blob/main/LICENSE) for details.
