1. Timestamp & Datetime Architecture
Status: COMPLETED
Last Updated: 2025-01-21
Scope: Entire codebase (Gateway + Frontend Agents)
1.1 Overview
PowerOn implements a unified timestamp architecture that ensures consistency across all modules,
APIs, and frontend components. The system uses float UTC timestamps as the single
source of truth for all temporal data, eliminating timezone confusion and format inconsistencies.
1.2 Core Design Principles
Single Source of Truth: UTC Timestamps
- ALL timestamp fields use
float type representing UTC seconds since epoch
- NO mixed types (datetime, str, float) for the same field type
- NO ISO string timestamps in data models or API responses
- ONLY system-level fields (
_createdAt, _modifiedAt) remain as ISO strings
Consistent Data Flow Architecture
Database ←→ Models ←→ API Endpoints ←→ Frontend
↓ ↓ ↓ ↓
Float Float Float Float
UTC UTC UTC UTC
Frontend-Backend Contract
- Backend Sends: Float UTC timestamps (e.g.,
1705852800.0)
- Frontend Receives: Float UTC timestamps
- Frontend Displays: Local timezone converted from UTC
- Frontend Sends Back: Float UTC timestamps (if sending timestamps)
1.3 Technical Implementation Standards
Backend (Python) Standards
# ✅ CORRECT - Use UTC timestamp functions
from modules.shared.timezoneUtils import get_utc_timestamp, create_expiration_timestamp
# For current time
current_time = get_utc_timestamp() # Returns float
# For expiration times
expires_at = create_expiration_timestamp(3600) # 1 hour from now
# For model fields
class UserConnection(BaseModel):
connectedAt: float = Field(default_factory=get_utc_timestamp)
expiresAt: Optional[float] = None
Frontend (JavaScript) Standards
// ✅ CORRECT - Expect float UTC timestamps
import { isExpiredUtc, formatUtcForDisplay } from '../shared/timezoneUtils.js';
// Check expiration (expects float)
if (isExpiredUtc(connection.expiresAt)) {
// Handle expired token
}
// Display in local timezone (expects float)
const localTime = formatUtcForDisplay(connection.connectedAt);
API Response Standards
// ✅ CORRECT - All timestamp fields as float
{
"id": "conn_123",
"connectedAt": 1705852800.0,
"expiresAt": 1705856400.0,
"lastChecked": 1705852800.0
}
// ❌ WRONG - Mixed types or ISO strings
{
"id": "conn_123",
"connectedAt": "2024-01-21T12:00:00Z", // ISO string
"expiresAt": 1705856400.0, // Float
"lastChecked": "2024-01-21T12:00:00Z" // ISO string
}
1.4 Database Schema Standards
Timestamp Field Types
-- ✅ CORRECT - All timestamp fields as FLOAT/DOUBLE
CREATE TABLE connections (
id VARCHAR(255) PRIMARY KEY,
connectedAt DOUBLE NOT NULL, -- UTC timestamp in seconds
expiresAt DOUBLE, -- UTC timestamp in seconds
lastChecked DOUBLE NOT NULL -- UTC timestamp in seconds
);
-- ❌ WRONG - Mixed types
CREATE TABLE connections (
id VARCHAR(255) PRIMARY KEY,
connectedAt DATETIME, -- Datetime type
expiresAt DOUBLE, -- Float type
lastChecked VARCHAR(255) -- String type
);
System Fields (Keep as-is)
-- ✅ CORRECT - System fields remain as strings
CREATE TABLE connections (
id VARCHAR(255) PRIMARY KEY,
connectedAt DOUBLE NOT NULL, -- User data: float UTC
_createdAt VARCHAR(255), -- System data: ISO string
_modifiedAt VARCHAR(255) -- System data: ISO string
);
1.5 Model Field Standards
All timestamp fields in Pydantic models must follow these standards:
class UserConnection(BaseModel, ModelMixin):
"""Data model for a user's connection to an external service"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the connection")
userId: str = Field(description="ID of the user this connection belongs to")
authority: AuthAuthority = Field(description="Authentication authority")
externalId: str = Field(description="User ID in the external system")
externalUsername: str = Field(description="Username in the external system")
externalEmail: Optional[EmailStr] = Field(None, description="Email in the external system")
status: ConnectionStatus = Field(default=ConnectionStatus.ACTIVE, description="Connection status")
connectedAt: float = Field(default_factory=get_utc_timestamp, description="When the connection was established (UTC timestamp in seconds)")
lastChecked: float = Field(default_factory=get_utc_timestamp, description="When the connection was last verified (UTC timestamp in seconds)")
expiresAt: Optional[float] = Field(None, description="When the connection expires (UTC timestamp in seconds)")
⚠️ Critical Rule: All timestamp fields MUST include "UTC timestamp in seconds" in their description
for the automatic conversion system to work correctly.
1.6 Automatic Timestamp Conversion
The system automatically converts timestamp fields using the ModelMixin.to_dict() method:
def to_dict(self) -> Dict[str, Any]:
"""
Convert a Pydantic model to a dictionary.
Handles both Pydantic v1 and v2.
Properly serializes datetime fields to ISO format strings.
Returns:
Dict[str, Any]: Dictionary representation of the model
"""
# Get the raw dictionary
if hasattr(self, 'model_dump'):
data: Dict[str, Any] = self.model_dump() # Pydantic v2
else:
data: Dict[str, Any] = self.dict() # Pydantic v1
# Convert datetime fields to ISO format strings
for key, value in data.items():
if isinstance(value, datetime):
data[key] = value.isoformat()
elif isinstance(value, (int, float)) and self._is_timestamp_field(key):
# Handle timestamp fields based on field metadata
try:
data[key] = datetime.fromtimestamp(value).isoformat()
except (ValueError, TypeError):
# If conversion fails, keep the original value
pass
return data
def _is_timestamp_field(self, field_name: str) -> bool:
"""
Check if a field is a timestamp field based on field metadata.
Looks for 'UTC timestamp' in the field description.
"""
try:
# Get field info from Pydantic model
if hasattr(self, 'model_fields'):
# Pydantic v2
field_info = self.model_fields.get(field_name)
if field_info and field_info.description:
return 'UTC timestamp' in field_info.description
elif hasattr(self, '__fields__'):
# Pydantic v1
field_info = self.__fields__.get(field_name)
if field_info and field_info.field_info and field_info.field_info.description:
return 'UTC timestamp' in field_info.field_info.description
except Exception:
pass
# Fallback: return False for safety
return False
1.7 Utility Functions
Backend Utilities (Python)
from modules.shared.timezoneUtils import get_utc_timestamp, create_expiration_timestamp
# Get current UTC timestamp
current_time = get_utc_timestamp() # Returns float
# Create expiration timestamp (seconds from now)
expires_in_1_hour = create_expiration_timestamp(3600)
expires_in_1_day = create_expiration_timestamp(86400)
# Get UTC datetime object
utc_now = get_utc_now() # Returns datetime object in UTC
Frontend Utilities (JavaScript)
import {
isExpiredUtc,
formatUtcForDisplay,
getExpiresInSeconds,
normalizeTimestamp
} from '../shared/timezoneUtils.js';
// Check if timestamp is expired
if (isExpiredUtc(connection.expiresAt)) {
console.log('Connection expired');
}
// Format for display (converts to local timezone)
const displayTime = formatUtcForDisplay(connection.connectedAt);
// Get seconds until expiration
const secondsLeft = getExpiresInSeconds(connection.expiresAt);
// Normalize any timestamp format to float (backward compatibility)
const normalized = normalizeTimestamp(connection.lastChecked);
1.8 Migration Rules
When converting existing timestamp data to the new format:
def migrate_timestamp_field(value):
"""Migrate any timestamp format to float UTC timestamp"""
if isinstance(value, float):
return value # Already float, assume UTC
elif isinstance(value, str):
if value.endswith('Z') or 'T' in value:
# ISO string format
dt = datetime.fromisoformat(value.replace('Z', '+00:00'))
return dt.timestamp()
else:
# Try parsing as float
return float(value)
elif isinstance(value, datetime):
# Convert to UTC timestamp
return value.timestamp()
else:
raise ValueError(f"Cannot migrate timestamp: {value}")
1.9 Implementation Status
| Component |
Status |
Details |
| Backend Models |
COMPLETED |
11 models, 15 timestamp fields standardized |
| Backend APIs |
COMPLETED |
All endpoints return float timestamps |
| Frontend Modules |
COMPLETED |
All utilities expect float timestamps |
| Database Migration |
NOT REQUIRED |
Database will be cleaned before testing |
| Testing & Validation |
COMPLETED |
24 tests passing, all functionality verified |