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 consistency and documentation purposes.
1.6 Direct Float Storage
The system now stores all timestamp fields directly as float values in the database:
def to_dict(self) -> Dict[str, Any]:
"""
Convert a Pydantic model to a dictionary.
Handles both Pydantic v1 and v2.
All timestamp fields remain as float values.
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
# All fields (including timestamps) remain in their original format
# No conversions needed - timestamps are already float
return data
Key Benefits:
- No unnecessary conversions between float and string
- Direct database storage as float values
- Consistent data types across the entire system
- Better performance without string parsing
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 as float |
| Backend APIs |
COMPLETED |
All endpoints return float timestamps |
| Frontend Modules |
COMPLETED |
All utilities expect float timestamps |
| Database Storage |
COMPLETED |
All timestamps stored as float (no string conversion) |
| Testing & Validation |
COMPLETED |
24 tests passing, all functionality verified |