# Timezone-Aware Datetime Fix

**Date:** 2025-10-09
**Status:** ✅ RESOLVED

## Problem

Error in forecasting service logs:
```
[error] Failed to get cached prediction
error=can't compare offset-naive and offset-aware datetimes
```

## Root Cause

The forecasting service database uses `DateTime(timezone=True)` for all timestamp columns, which means they store timezone-aware datetime objects. However, the code was using `datetime.utcnow()` throughout, which returns timezone-naive datetime objects.

When comparing these two types (e.g., checking if cache has expired), Python raises:
```
TypeError: can't compare offset-naive and offset-aware datetimes
```

## Database Schema

All datetime columns in forecasting service models use `DateTime(timezone=True)`:

```python
# From app/models/predictions.py
class PredictionCache(Base):
    forecast_date = Column(DateTime(timezone=True), nullable=False)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    expires_at = Column(DateTime(timezone=True), nullable=False)  # ← Compared with datetime.utcnow()
    # ... other columns

class ModelPerformanceMetric(Base):
    evaluation_date = Column(DateTime(timezone=True), nullable=False)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    # ... other columns

# From app/models/forecasts.py
class Forecast(Base):
    forecast_date = Column(DateTime(timezone=True), nullable=False, index=True)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))

class PredictionBatch(Base):
    requested_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    completed_at = Column(DateTime(timezone=True))
```

## Solution

Replaced all `datetime.utcnow()` calls with `datetime.now(timezone.utc)` throughout the forecasting service.

### Before (BROKEN):
```python
# Returns timezone-naive datetime
cache_entry.expires_at < datetime.utcnow()  # ❌ TypeError!
```

### After (WORKING):
```python
# Returns timezone-aware datetime
cache_entry.expires_at < datetime.now(timezone.utc)  # ✅ Works!
```

## Files Fixed

### 1. Import statements updated
Added `timezone` to imports in all affected files:
```python
from datetime import datetime, timedelta, timezone
```

### 2. All datetime.utcnow() replaced
Fixed in 9 files across the forecasting service:

1. **[services/forecasting/app/repositories/prediction_cache_repository.py](services/forecasting/app/repositories/prediction_cache_repository.py)**
   - Line 53: Cache expiration time calculation
   - Line 105: Cache expiry check (the main error)
   - Line 175: Cleanup expired cache entries
   - Line 212: Cache statistics query

2. **[services/forecasting/app/repositories/prediction_batch_repository.py](services/forecasting/app/repositories/prediction_batch_repository.py)**
   - Lines 84, 113, 143, 184: Batch completion timestamps
   - Line 273: Recent activity queries
   - Line 318: Cleanup old batches
   - Line 357: Batch progress calculations

3. **[services/forecasting/app/repositories/forecast_repository.py](services/forecasting/app/repositories/forecast_repository.py)**
   - Lines 162, 241: Forecast accuracy and trend analysis date ranges

4. **[services/forecasting/app/repositories/performance_metric_repository.py](services/forecasting/app/repositories/performance_metric_repository.py)**
   - Line 101: Performance trends date range calculation

5. **[services/forecasting/app/repositories/base.py](services/forecasting/app/repositories/base.py)**
   - Lines 116, 118: Recent records queries
   - Lines 124, 159, 161: Cleanup and statistics

6. **[services/forecasting/app/services/forecasting_service.py](services/forecasting/app/services/forecasting_service.py)**
   - Lines 292, 365, 393, 409, 447, 553: Processing time calculations and timestamps

7. **[services/forecasting/app/api/forecasting_operations.py](services/forecasting/app/api/forecasting_operations.py)**
   - Line 274: API response timestamps

8. **[services/forecasting/app/api/scenario_operations.py](services/forecasting/app/api/scenario_operations.py)**
   - Lines 68, 134, 163: Scenario simulation timestamps

9. **[services/forecasting/app/services/messaging.py](services/forecasting/app/services/messaging.py)**
   - Message timestamps

## Verification

```bash
# Before fix
$ grep -r "datetime\.utcnow()" services/forecasting/app --include="*.py" | wc -l
20

# After fix
$ grep -r "datetime\.utcnow()" services/forecasting/app --include="*.py" | wc -l
0
```

## Why This Matters

### Timezone-Naive (datetime.utcnow())
```python
>>> datetime.utcnow()
datetime.datetime(2025, 10, 9, 9, 10, 37, 123456)  # No timezone info
```

### Timezone-Aware (datetime.now(timezone.utc))
```python
>>> datetime.now(timezone.utc)
datetime.datetime(2025, 10, 9, 9, 10, 37, 123456, tzinfo=datetime.timezone.utc)  # Has timezone
```

When PostgreSQL stores `DateTime(timezone=True)` columns, it stores them as timezone-aware. Comparing these with timezone-naive datetimes fails.

## Impact

This fix resolves:
- ✅ Cache expiration checks
- ✅ Batch status updates
- ✅ Performance metric queries
- ✅ Forecast analytics date ranges
- ✅ Cleanup operations
- ✅ Recent activity queries

## Best Practice

**Always use timezone-aware datetimes with PostgreSQL `DateTime(timezone=True)` columns:**

```python
# ✅ GOOD
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
expires_at = datetime.now(timezone.utc) + timedelta(hours=24)
if record.created_at < datetime.now(timezone.utc):
    ...

# ❌ BAD
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)  # No timezone!
expires_at = datetime.utcnow() + timedelta(hours=24)  # Naive!
if record.created_at < datetime.utcnow():  # TypeError!
    ...
```

## Additional Issue Found and Fixed

### Local Import Shadowing

After the initial fix, a new error appeared:
```
[error] Multi-day forecast generation failed
error=cannot access local variable 'timezone' where it is not associated with a value
```

**Cause:** In `forecasting_service.py` line 428, there was a local import inside a conditional block that shadowed the module-level import:

```python
# Module level (line 9)
from datetime import datetime, date, timedelta, timezone

# Inside function (line 428) - PROBLEM
if day_offset > 0:
    from datetime import timedelta, timezone  # ← Creates LOCAL variable
    current_date = current_date + timedelta(days=day_offset)

# Later in same function (line 447)
processing_time = (datetime.now(timezone.utc) - start_time)  # ← Error! timezone not accessible
```

When Python sees the local import on line 428, it creates a local variable `timezone` that only exists within that `if` block. When line 447 tries to use `timezone.utc`, Python looks for the local variable but can't find it (it's out of scope), resulting in: "cannot access local variable 'timezone' where it is not associated with a value".

**Fix:** Removed the redundant local import since `timezone` is already imported at module level:

```python
# Before (BROKEN)
if day_offset > 0:
    from datetime import timedelta, timezone
    current_date = current_date + timedelta(days=day_offset)

# After (WORKING)
if day_offset > 0:
    current_date = current_date + timedelta(days=day_offset)
```

**File:** [services/forecasting/app/services/forecasting_service.py](services/forecasting/app/services/forecasting_service.py#L427-L428)

## Deployment

```bash
# Restart forecasting service to apply changes
kubectl -n bakery-ia rollout restart deployment forecasting-service

# Monitor for errors
kubectl -n bakery-ia logs -f deployment/forecasting-service | grep -E "(can't compare|cannot access)"
```

## Related Issues

This same issue may exist in other services. Search for:
```bash
# Find services using timezone-aware columns
grep -r "DateTime(timezone=True)" services/*/app/models --include="*.py"

# Find services using datetime.utcnow()
grep -r "datetime\.utcnow()" services/*/app --include="*.py"
```

## References

- Python datetime docs: https://docs.python.org/3/library/datetime.html#aware-and-naive-objects
- SQLAlchemy DateTime: https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.DateTime
- PostgreSQL TIMESTAMP WITH TIME ZONE: https://www.postgresql.org/docs/current/datatype-datetime.html
