# edwh-uuid7

A Python implementation of [pg_uuidv7](https://pgxn.org/dist/pg_uuidv7/),
providing sorting compatible with UUIDs generated by the `pg_uuidv7` PostgreSQL extension,
as well as UUIDv7 generation and conversion to/from `datetime`.

## Overview

UUIDv7 is a time-sortable UUID format composed of a timestamp and random bits. Our implementation
follows the UUIDv7 specification, which provides flexibility in ensuring monotonicity. We utilize microseconds for 
sub-millisecond precision within the timestamp; this acts as a stateless monotonic counter for UUIDs generated within the 
same millisecond and leaves more room for entropy compared to using nanoseconds.

### UUIDv7 Layout (128 bits)

| Bits       | Size (bits) | Field          | Description                                      |
|------------|-------------|----------------|--------------------------------------------------|
| 0–47       | 48          | timestamp (ms) | Unix timestamp in milliseconds (sortable)        |
| 48–51      | 4           | version        | UUID version (`0b0111` for v7)                   |
| 52–71      | 20          | sub-ms (μs)    | Sub-millisecond fraction (0–999,999)             |
| 72–127     | 56          | random         | Random bits (includes UUID variant information)  |


This design makes UUIDv7 globally sortable by creation time (1 μs resolution) 
while retaining enough entropy for distributed uniqueness.

### Key Features of `edwh-uuid7`:

- Compatible with `pg_uuidv7`’s `uuid_generate_v7()`, `uuid_v7_to_timestamptz()`, `uuid_timestamptz_to_v7()` functions.
- Sorting compatible with Python 3.14's `uuid.uuid7()`
- Provides conversion between `UUIDv7` and `datetime`.
- Supports timezone-aware and naive datetime conversions.

## Why this package?

The widely-used [`uuid7`](https://pypi.org/project/uuid7/) Python package uses
a different timestamp scheme, which is **not compatible** with sorting behavior as implemented in the PostgreSQL
`pg_uuidv7` extension. This package ensures compatibility, ensuring that UUIDv7s generated will sort correctly alongside
those produced by `pg_uuidv7`.

### Comparison with Python 3.14's uuid7 functionality

Key differences between this library and Python 3.14's built-in uuid7:

- **Timestamp Implementation**: This library works with μs (microseconds), while Python 3.14+ uses a counter mechanism
- **Conversion Utilities**: This library provides uuid7 ↔ datetime conversion, which isn't available in the standard library implementation (at the time of writing)
- **PostgreSQL Compatibility**: Both implementations are sorting compatible with the `pg_uuidv7` PostgreSQL extension


## Installation

```bash
pip install edwh-uuid7
```

## Usage

Here are examples of how to use `edwh-uuid7`:

### Generating a UUIDv7

```python
from edwh_uuid7 import uuid7  # or `uuid_generate_v7`

uuid = uuid7()
print(f"Generated UUIDv7: {uuid}")
```

### Converting a UUIDv7 to a datetime

```python
from edwh_uuid7 import uuid7, uuid7_to_datetime       # or uuid_generate_v7, uuid_v7_to_timestamptz

uuid = uuid7()                                        # UUID('0196905b-4434-74ef-aa27-442be7069beb')
datetime_value = uuid7_to_datetime(uuid)              # datetime.datetime(2025, 5, 2, 9, 37, 2, 516000, tzinfo=datetime.timezone.utc)
print(f"Corresponding datetime: {datetime_value}")    # 2025-05-02 09:37:02.516000+00:00
```

#### High-precision mode

This library provides enhanced timestamp precision capabilities:

- **Default Precision**: By default, uuid7 supports *milli*second precision
- **Enhanced Recovery**: When working with UUIDs created by this library (`uuid7()` or `uuid7(timestamp_ns=...)`), 
  you can enable `high_precision=True` to recover datetime values with *micro*second accuracy

**Example:**

```python
now = datetime.fromtimestamp(time.time())     # datetime.datetime(2025, 5, 9, 17, 53, 50, 514106)
uuid = datetime_to_uuid7(now)                 # UUID('0196b5c0-c0b2-719e-802c-9964c4992a66')
uuid7_to_datetime(uuid)                       # datetime(2025, 5, 9, 15, 53, 50, 514000, tzinfo=datetime.timezone.utc)
uuid7_to_datetime(uuid, high_precision=True)  # datetime(2025, 5, 9, 15, 53, 50, 514106, tzinfo=datetime.timezone.utc)
```

### Converting a datetime to a UUIDv7

```python
from edwh_uuid7.core import datetime_to_uuid7                      # or uuid_timestamptz_to_v7
from datetime import datetime, timezone

dt = datetime(2025, 5, 2, 9, 37, 2, 516000, tzinfo=timezone.utc)
uuid_from_dt = datetime_to_uuid7(dt)                               # UUID('0196905b-4434-7000-8012-620314c8b88b')
print(f"UUIDv7 from datetime: {uuid_from_dt}")                     # 0196905b-4434-7000-8012-620314c8b88b
```

### Extracting a timezone-aware datetime

```python
from edwh_uuid7 import uuid7, uuid7_to_datetime                            # or uuid_generate_v7, uuid_v7_to_timestamptz
from zoneinfo import ZoneInfo

uuid = uuid7()                                                             # UUID('0196905f-0339-769a-9f0b-8fedc645a688')
datetime_value = uuid7_to_datetime(uuid, tz=ZoneInfo("Europe/Amsterdam"))  # datetime.datetime(2025, 5, 2, 11, 41, 8, 25000, tzinfo=zoneinfo.ZoneInfo(key='Europe/Amsterdam'))
print(f"Generated UUID: {uuid}")                                           # 0196905f-0339-769a-9f0b-8fedc645a688
print(f"Timezone-aware datetime: {datetime_value}")                        # 2025-05-02 11:41:08.025000+02:00
```

## Caveats

While microseconds are used to ensure correct sorting, accuracy is limited (truncated) when converting back to
`datetime`, consistent with the behavior of the `pg_uuidv7` function `uuid_timestamptz_to_v7`.

For example:

```text
Input: 2025-05-02 08:46:48.307329+00:00
UUID conversion: 0196902d-45f3-7505-8038-7795a5ec9a1b (example)
Converted Back: 2025-05-02 08:46:48.307000+00:00
```

This truncation is by design (in favor of entropy) and aligns with the PostgreSQL `pg_uuidv7` implementation.