# Date and Time Handling This document describes how dates and times are handled throughout the gateway project, including storage formats, transformations, and retrieval mechanisms. ## Overview The gateway uses **Unix timestamps** (floats representing seconds since epoch) as the standard format for all date and time values. This ensures consistency across different database backends and eliminates timezone-related issues by always working in UTC. ## Core Components ### Time Utilities Module The primary module for date/time handling is `modules/shared/timeUtils.py`, which provides: #### `getUtcTimestamp() -> float` Returns the current UTC timestamp as a float (seconds since Unix epoch with millisecond precision). **Implementation:** - Uses Python's `time.time()` which returns seconds since epoch as a float - Provides millisecond precision - Always returns UTC time #### `getUtcNow() -> datetime` Returns the current UTC time as a `datetime` object with timezone information. #### `parseTimestamp(value: Any, default: Optional[float] = None) -> Optional[float]` **Critical function for database retrieval** - Parses timestamp values from various formats and converts them to float. This function handles the transformation/decoding of timestamps when reading from the database, especially important for PostgreSQL connectors that may return numeric fields as strings in some environments (e.g., Azure PostgreSQL). **Supported input formats:** - `float`: Returns as-is - `int`: Converts to float - `str`: Attempts to parse as numeric string (e.g., "1234567890.123") - `None`: Returns default value (or None if no default provided) **Usage:** Call `parseTimestamp()` with the database value and an optional default. For example: `parseTimestamp(db_value, default=getUtcTimestamp())`. **Why this is needed:** - PostgreSQL connectors (especially Azure PostgreSQL) may return numeric fields as strings - Ensures consistent float type regardless of database return format - Provides safe fallback with default values #### `createExpirationTimestamp(expiresInSeconds: int) -> float` Creates an expiration timestamp by adding seconds to the current UTC timestamp. Call with the number of seconds until expiration, for example: `createExpirationTimestamp(3600)` for 1 hour from now. ## Database Storage ### Storage Format All timestamps are stored in the database as **floats** (Unix timestamps in seconds): - **PostgreSQL**: Stored as `NUMERIC` or `DOUBLE PRECISION` type - **JSON Database**: Stored as JSON number (float) in record files ### Saving Timestamps #### PostgreSQL Connector (`connectorDbPostgre.py`) When saving records, timestamp fields (`_createdAt`, `_modifiedAt`) are handled specially. The connector checks if the value is a string and attempts to convert it to float before storing. **Process:** 1. Timestamp values are converted to float if they're strings 2. Stored directly as numeric values in PostgreSQL 3. No encryption or encoding - stored as plain numeric values #### JSON Connector (`connectorDbJson.py`) Timestamps are stored directly as JSON numbers. The connector calls `getUtcTimestamp()` to get the current time and sets `_createdAt` on creation and `_modifiedAt` on every save operation. **Process:** 1. Timestamps are generated as floats using `getUtcTimestamp()` 2. Stored as JSON numbers in record files 3. No transformation needed - JSON natively supports float numbers ### Retrieving Timestamps #### PostgreSQL Connector When reading from PostgreSQL, timestamps may be returned in different formats: - **Expected**: Float values - **Actual (Azure PostgreSQL)**: Sometimes returned as strings - **Solution**: Use `parseTimestamp()` to normalize **Example from code:** In `interfaceDbChatObjects.py`, timestamps are parsed using `parseTimestamp(msg.get("publishedAt"), default=getUtcTimestamp())`. #### JSON Connector Timestamps are read directly from JSON and typically remain as floats, but `parseTimestamp()` is still used for safety to ensure consistent float type regardless of JSON parsing quirks. Call `parseTimestamp(record.get("timestamp"), default=getUtcTimestamp())` when reading timestamp fields. ## Data Model Definitions Timestamps are defined in Pydantic models using the `float` type with `getUtcTimestamp` as the default factory. For example, in `ChatLog` model, the `timestamp` field uses `Field(default_factory=getUtcTimestamp, ...)`. **Common timestamp fields:** - `_createdAt`: When the record was created - `_modifiedAt`: When the record was last modified - `publishedAt`: When a message was published - `timestamp`: Generic timestamp field - `expiresAt`: When something expires - `connectedAt`: When a connection was established - `lastChecked`: When something was last verified - `creationDate`: When something was created ## Transformation Flow ### Saving to Database ``` Application Code ↓ getUtcTimestamp() → float (e.g., 1234567890.123) ↓ Database Connector ↓ PostgreSQL: Store as NUMERIC/DOUBLE PRECISION JSON: Store as JSON number ``` ### Reading from Database ``` Database ↓ PostgreSQL: May return as float, int, or string JSON: Returns as float (or int if no decimals) ↓ parseTimestamp() → Normalizes to float ↓ Application Code (always receives float) ``` ## Key Points 1. **No Encryption**: Timestamps are **not encrypted**. They are stored as plain numeric values (floats) in the database. The "encrypted format" you may have seen refers to the numeric Unix timestamp format, which is not human-readable but is not encrypted. 2. **Type Transformation**: The main transformation happens when **reading** from the database: - Database may return timestamps as strings (especially Azure PostgreSQL) - `parseTimestamp()` converts strings/int/float → float - This ensures consistent float type in application code 3. **UTC Only**: All timestamps are in UTC. No timezone conversions happen at the database level. 4. **Millisecond Precision**: Timestamps use float type, providing millisecond precision (e.g., `1234567890.123`). 5. **Default Values**: When timestamps are missing or invalid, `parseTimestamp()` can provide safe defaults using the `default` parameter. ## Usage Examples ### Creating a Record with Timestamps When creating records, call `getUtcTimestamp()` to set `_createdAt` and `_modifiedAt` fields. These are typically auto-set by the database connectors. ### Reading and Using Timestamps After retrieving a record from the database, use `parseTimestamp(record.get("publishedAt"), default=getUtcTimestamp())` to safely parse the timestamp value before using it for filtering or comparison. ### Filtering by Timestamp When filtering messages or records by timestamp, iterate through the results and use `parseTimestamp(msg.get("publishedAt"), default=getUtcTimestamp())` to parse each timestamp, then compare against your threshold value. ### Sorting by Timestamp When sorting logs or records by timestamp, use `parseTimestamp()` in the sort key function, for example: `logs.sort(key=lambda x: parseTimestamp(x.get("timestamp"), default=0))`. ## Common Patterns ### Automatic Timestamp Management Database connectors automatically manage `_createdAt` and `_modifiedAt`: - `_createdAt`: Set only on record creation (if not already present) - `_modifiedAt`: Updated on every save operation ### Timestamp Filtering When filtering records by timestamp, always use `parseTimestamp()` to handle all database return types. Call `parseTimestamp(record.get("timestamp"), default=0)` before comparison. Do not directly compare timestamp values as they may be strings from the database. ### Expiration Timestamps For expiration logic, use `createExpirationTimestamp(3600)` to create an expiration timestamp, then compare it with the current time using `getUtcTimestamp()`. Parse any stored expiration timestamps using `parseTimestamp()` before comparison. ## Database-Specific Notes ### PostgreSQL (Azure) - May return numeric fields as strings - Always use `parseTimestamp()` when reading timestamp fields - Stored as `NUMERIC` or `DOUBLE PRECISION` type ### JSON Database - Timestamps stored as JSON numbers - Usually returned as floats, but `parseTimestamp()` provides safety - No special handling needed beyond normalization ## Troubleshooting ### Issue: Timestamp is a string instead of float **Solution**: Use `parseTimestamp()` to convert: `parseTimestamp(string_timestamp, default=getUtcTimestamp())`. ### Issue: Timestamp is None **Solution**: Provide a default value: `parseTimestamp(record.get("timestamp"), default=getUtcTimestamp())`. ### Issue: Timestamp comparison fails **Cause**: Comparing string to float **Solution**: Always parse first using `parseTimestamp(value, default=0)` before any comparison operations. ## Summary The date/time handling system: 1. **Stores** timestamps as Unix timestamps (floats) in UTC 2. **Transforms** database return values (string/int/float) → float using `parseTimestamp()` 3. **Provides** utilities for timestamp generation, parsing, and expiration 4. **Ensures** consistency across different database backends 5. **Handles** edge cases (None values, string returns, invalid formats) The transformation you mentioned is the **type normalization** that happens when reading from the database, not encryption. Timestamps are stored as plain numeric values and converted to consistent float types for application use. --- ## Frontend Requirements and Implementation ### API Response Format The backend API returns timestamps as **Unix timestamps (floats)** in all JSON responses. Timestamps are always in UTC and represented as numbers (not strings). **Example API Response:** The API returns timestamps as numeric values in JSON, for example: `_createdAt: 1704067200.123`, `_modifiedAt: 1704067300.456`, `publishedAt: 1704067250.789`, `expiresAt: 1704153600.0`. **Important Notes:** - Timestamps are **always numbers** (float) in JSON responses - All timestamps are in **UTC** (no timezone information in the value) - Timestamps have **millisecond precision** (decimal places) - Timestamp fields may be `null` or `undefined` if not set ### Frontend Requirements #### 1. Timestamp Decoding Module The frontend **must** implement a core timestamp utility module that handles: - **Decoding/parsing** timestamp values from API responses - **Type normalization** (handles number, string, null, undefined) - **Conversion** to JavaScript Date objects - **Formatting** for display (relative time, absolute date/time) - **Timezone handling** (convert UTC to user's local timezone) #### 2. Required Functionality The frontend timestamp module must provide: 1. **Parse/Decode Function**: Convert API timestamp (number/string/null) → JavaScript Date 2. **Format Functions**: Convert Date → human-readable strings 3. **Relative Time**: "2 hours ago", "in 5 minutes", etc. 4. **Absolute Time**: "2024-01-01 12:30:45 UTC" or localized format 5. **Comparison Utilities**: Compare timestamps, check expiration, etc. 6. **Safe Defaults**: Handle null/undefined/invalid values gracefully #### 3. Display Requirements Based on frontend documentation requirements, timestamps should be displayed as: - **List Views**: Relative time ("2 hours ago") for recent items, absolute date for older items - **Detail Views**: Absolute date/time with timezone information - **Filters**: Date/time pickers that convert to/from Unix timestamps - **Sorting**: Sort by timestamp value (numeric comparison) ### Core Frontend Timestamp Module The frontend should implement a core timestamp utility module in `src/utils/timestampUtils.ts` (or `.js`) that provides the following functions: #### Required Functions - **`parseTimestamp(value, defaultValue)`**: Parse a timestamp value from the API and convert to JavaScript Date. Handles number, string, null, and undefined inputs. Validates timestamp range (1970-2100) and converts seconds to milliseconds for JavaScript Date constructor. - **`getUtcTimestamp()`**: Get current UTC timestamp as Unix timestamp (float). Returns `Date.now() / 1000`. - **`formatRelativeTime(timestamp, options)`**: Format timestamp as relative time string. Returns strings like "just now", "2 minutes ago", "3 hours ago", "2 days ago", or "in 5 minutes" for future timestamps. Options include `includeSeconds`, `futurePrefix`, and `pastSuffix`. - **`formatAbsoluteTime(timestamp, options)`**: Format timestamp as absolute date/time string. Options include `includeTime`, `includeSeconds`, `includeTimezone`, and `format` ('iso', 'local', or 'utc'). - **`formatTimestamp(timestamp, options)`**: Smart formatting that uses relative time for recent items (default < 24 hours) and absolute time for older items. Options include `relativeThreshold`, `showRelative`, and `showAbsolute`. - **`isExpired(expiresAt)`**: Check if a timestamp has expired (is in the past). Returns true if expired, false otherwise. - **`isFuture(timestamp)`**: Check if a timestamp is in the future. Returns true if in future, false otherwise. - **`compareTimestamps(timestamp1, timestamp2)`**: Compare two timestamps. Returns -1 if timestamp1 < timestamp2, 0 if equal, 1 if timestamp1 > timestamp2. - **`dateToTimestamp(date)`**: Convert JavaScript Date to Unix timestamp (float). Defaults to current date if not provided. - **`toISOString(timestamp)`**: Convert Unix timestamp to ISO 8601 string. #### Usage Examples **Basic Usage:** Import functions from the timestamp utils module. Use `parseTimestamp(apiData._createdAt)` to parse timestamps from API responses. Use `formatTimestamp(apiData._createdAt)` for smart display formatting (returns relative time for recent items, absolute time for older). Use `formatRelativeTime(apiData._createdAt)` for relative time strings like "2 hours ago". Use `formatAbsoluteTime(apiData._createdAt, { format: 'utc' })` for absolute time strings. **In React Components:** Import `parseTimestamp`, `formatTimestamp`, and `isExpired` from the timestamp utils module. Call `parseTimestamp(workflow._createdAt)` to parse timestamps. Use `formatTimestamp(workflow._createdAt)` to display formatted time. Use `isExpired(expiresAt)` to check if a token has expired. **Sorting:** Import `compareTimestamps` and use it in sort functions: `workflows.sort((a, b) => compareTimestamps(a._createdAt, b._createdAt))`. **Filtering:** Import `isExpired` and use it to filter arrays: `tokens.filter(token => !isExpired(token.expiresAt))`. ### Frontend Integration Guidelines #### 1. Always Use the Timestamp Module **✅ DO:** Import and use `parseTimestamp()` from the timestamp utils module. Call `parseTimestamp(apiData._createdAt)` to safely parse timestamps. **❌ DON'T:** Do not assume timestamps are always numbers and directly create Date objects like `new Date(apiData._createdAt * 1000)` as this may fail if the value is a string or null. #### 2. Handle Null/Undefined Values **✅ DO:** Always provide a default value when calling `parseTimestamp()`, for example: `parseTimestamp(apiData.expiresAt, new Date())`. Check if the returned date is valid before using it. **❌ DON'T:** Do not assume timestamps always exist. Do not directly create Date objects without checking for null values first. #### 3. Use Appropriate Formatting - **List Views**: Use `formatTimestamp()` for smart relative/absolute formatting - **Detail Views**: Use `formatAbsoluteTime()` with timezone information - **Filters**: Convert Date picker values to Unix timestamps using `dateToTimestamp()` #### 4. Timezone Considerations - All backend timestamps are in **UTC** - Frontend should convert to **user's local timezone** for display - Use `formatAbsoluteTime()` with `format: 'local'` for user-friendly display - Always show timezone information in detail views ### Testing the Timestamp Module **Test Cases to Cover:** 1. Parse number timestamps (float and int) 2. Parse string timestamps 3. Handle null/undefined values 4. Handle invalid values (NaN, out of range) 5. Format relative time (past and future) 6. Format absolute time (UTC and local) 7. Compare timestamps 8. Check expiration 9. Convert Date to timestamp **Example Test:** Import functions from the timestamp utils module and test that `parseTimestamp()` correctly handles number timestamps, string timestamps, and null values. Test that `formatRelativeTime()` correctly formats timestamps as relative time strings. Test that `isExpired()` correctly identifies expired timestamps. ### Summary: Frontend Timestamp Handling 1. **Always decode** API timestamps using `parseTimestamp()` - handles number/string/null 2. **Format appropriately** - relative for recent, absolute for older items 3. **Handle timezones** - convert UTC to user's local timezone for display 4. **Provide defaults** - handle null/undefined gracefully 5. **Use consistently** - use the core module throughout the project 6. **Test thoroughly** - cover all input formats and edge cases The core timestamp module ensures consistent, safe handling of timestamps across the entire frontend application, matching the backend's `parseTimestamp()` functionality for type normalization and error handling.