# How to Handle Timezone-Aware Datetime Properties in Neomodel

> Master timezone-aware datetime properties in Neomodel. Learn to use DateTimeProperty, DateTimeNeo4jFormatProperty, and DateTimeFormatProperty for seamless UTC conversion or native timezone handling.

- Repository: [Neo4j Contrib/neomodel](https://github.com/neo4j-contrib/neomodel)
- Tags: how-to-guide
- Published: 2026-03-08

---

**Neomodel provides three distinct property types—`DateTimeProperty`, `DateTimeNeo4jFormatProperty`, and `DateTimeFormatProperty`—that handle timezone-aware datetimes by either converting to UTC epoch storage, preserving native Neo4j timezone metadata, or using custom string formats without conversion.**

Managing timezone-aware datetime properties in graph databases requires careful handling to prevent silent data corruption or offset errors. The **neomodel** library for Neo4j offers specialized property implementations that control exactly how Python `datetime` objects are serialized to the database and whether timezone information is normalized or retained. Understanding these mechanisms ensures your temporal data maintains accuracy across database transactions.

## Understanding Neomodel Datetime Property Types

Neomodel implements three property classes in [`neomodel/properties.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py) that handle datetime serialization differently. Each serves specific use cases regarding timezone preservation and storage format.

### DateTimeProperty (UTC Epoch Storage)

The **`DateTimeProperty`** class stores datetimes as Unix epoch timestamps (floating-point seconds). This property always normalizes timezone-aware datetimes to UTC before storage.

According to the source code in [`neomodel/properties.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py) (lines 86-105), the `inflate` method converts stored epochs back to timezone-aware UTC datetimes:

```python
epoch = float(value)
return datetime.fromtimestamp(epoch, tz=ZoneInfo("UTC"))

```

When deflating (writing to the database), the implementation (lines 119-131) handles timezone conversion based on the `force_timezone` configuration:

```python
if value.tzinfo:
    value = value.astimezone(ZoneInfo("UTC"))
    epoch_date = datetime(1970, 1, 1, tzinfo=ZoneInfo("UTC"))
elif get_config().force_timezone:
    raise ValueError(f"Error deflating {value}: No timezone provided.")
else:
    epoch_date = datetime(1970, 1, 1)   # assume UTC

return float((value - epoch_date).total_seconds())

```

### DateTimeNeo4jFormatProperty (Native Timezone Preservation)

The **`DateTimeNeo4jFormatProperty`** preserves the original timezone information by utilizing Neo4j's native `neo4j.time.DateTime` type. Unlike `DateTimeProperty`, this implementation does not normalize to UTC.

As implemented in [`neomodel/properties.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py) (lines 134-144):

```python
def inflate(self, value):
    return value.to_native()                     # Neo4j → datetime with tz

def deflate(self, value):
    return neo4j.time.DateTime.from_native(value)  # datetime → Neo4j native type

```

This property type is essential when you need to maintain the original timezone offset (e.g., "America/Los_Angeles") rather than converting to UTC.

### DateTimeFormatProperty (Custom String Formatting)

The **`DateTimeFormatProperty`** stores datetimes as strings using a user-defined format string (e.g., `%Y-%m-%d %H:%M:%S`). This property performs **no automatic timezone conversion**—the string is stored exactly as formatted.

Use this property when you need human-readable date strings in the database or when integrating with systems that expect specific string formats.

## Configuring Strict Timezone Validation with force_timezone

Neomodel provides a global configuration flag **`force_timezone`** that controls how `DateTimeProperty` handles naive (timezone-unaware) datetimes.

Defined in [`neomodel/config.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/config.py) (lines 22-30):

```python
force_timezone: bool = field(
    default=False,
    metadata={
        "env_var": "NEOMODEL_FORCE_TIMEZONE",
        "description": "Force timezone‑aware datetime objects",
    },
)

```

When **`force_timezone`** is set to `True`, neomodel rejects any naive `datetime` objects during deflation, raising a `ValueError`. This enforces explicit timezone handling in your application code.

Enable strict validation programmatically:

```python
from neomodel import config
config.force_timezone = True

```

Or via environment variable:

```bash
export NEOMODEL_FORCE_TIMEZONE=1

```

## Practical Implementation Examples

### Basic UTC Storage with DateTimeProperty

Use `DateTimeProperty` when you want consistent UTC storage and automatic conversion:

```python
from neomodel import StructuredNode, DateTimeProperty
from datetime import datetime, timezone

class Event(StructuredNode):
    # Automatically stores current UTC time when node is created

    created_at = DateTimeProperty(default_now=True)

# Create a node – created_at is stored as float epoch in UTC

e = Event().save()
print(e.created_at)  # → 2026-03-08 12:34:56+00:00 (UTC)

```

### Enforcing Timezone-Aware Datetimes

Prevent silent errors by requiring explicit timezones:

```python
from neomodel import StructuredNode, DateTimeProperty, config
from datetime import datetime, timezone

# Enable strict timezone validation

config.force_timezone = True

class LogEntry(StructuredNode):
    timestamp = DateTimeProperty()

# ✅ Correct: timezone-aware datetime

log = LogEntry(timestamp=datetime(2026, 3, 8, 15, 0, tzinfo=timezone.utc)).save()

# ❌ Raises ValueError: "Error deflating 2026-03-08 15:00:00: No timezone provided."

LogEntry(timestamp=datetime(2026, 3, 8, 15, 0)).save()

```

### Preserving Original Timezones

Maintain the original timezone offset using `DateTimeNeo4jFormatProperty`:

```python
from neomodel import StructuredNode, DateTimeNeo4jFormatProperty
from datetime import datetime, timezone, timedelta

class Meeting(StructuredNode):
    # Preserves the specific timezone offset in Neo4j's native format

    start_time = DateTimeNeo4jFormatProperty()

# Create with Pacific Time (UTC-8)

pacific = timezone(timedelta(hours=-8))
meeting = Meeting(
    start_time=datetime(2026, 3, 8, 9, 0, tzinfo=pacific)
).save()

# Retrieved datetime maintains the original -08:00 offset

print(meeting.start_time)  # → 2026-03-08 09:00:00-08:00

```

### Custom String Formatting

Store datetimes as formatted strings without timezone conversion:

```python
from neomodel import StructuredNode, DateTimeFormatProperty
from datetime import datetime

class Appointment(StructuredNode):
    # Stores as "2026-03-08 14:30" (no timezone conversion)

    scheduled_for = DateTimeFormatProperty(format="%Y-%m-%d %H:%M")

appointment = Appointment(scheduled_for=datetime(2026, 3, 8, 14, 30)).save()

# Neo4j stores the literal string: "2026-03-08 14:30"

```

## Summary

- **`DateTimeProperty`** converts all datetimes to UTC epoch floats for storage, with optional strict timezone validation via the **`force_timezone`** configuration flag.
- **`DateTimeNeo4jFormatProperty`** preserves original timezone offsets using Neo4j's native `DateTime` type, avoiding UTC normalization.
- **`DateTimeFormatProperty`** stores datetimes as custom-formatted strings without automatic timezone conversion.
- Enable **`config.force_timezone = True`** to enforce explicit timezone handling and prevent silent assumptions about UTC for naive datetimes.

## Frequently Asked Questions

### What happens if I store a naive datetime using DateTimeProperty?

By default, `DateTimeProperty` assumes naive datetimes are in UTC and converts them to epoch timestamps accordingly. However, if you set `config.force_timezone = True`, neomodel raises a `ValueError` requiring you to provide timezone-aware datetime objects explicitly.

### Which property type should I use to preserve the original timezone offset?

Use **`DateTimeNeo4jFormatProperty`** when you need to maintain the original timezone information (such as "America/New_York" or UTC-05:00). This property utilizes Neo4j's native `DateTime` type, which stores the timezone offset alongside the timestamp, unlike `DateTimeProperty` which normalizes everything to UTC.

### How do I enable strict timezone validation across my entire application?

Set the global configuration flag before defining your node classes:

```python
from neomodel import config
config.force_timezone = True

```

Alternatively, set the environment variable `NEOMODEL_FORCE_TIMEZONE=1` before starting your application. This configuration affects all `DateTimeProperty` instances, ensuring they only accept timezone-aware datetime objects.