+ 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 |
+
+
+
+