gateway/modules/shared/timeUtils.py
patrick-motsch 5d987e72fe fix(teamsbot): Fix Invalid Date + pass language to browser bot
- Add getIsoTimestamp() to timeUtils for JS-compatible ISO 8601 strings
- Replace getUtcTimestamp() (epoch float) with getIsoTimestamp() for all
  teamsbot session/transcript/response timestamp fields (startedAt, endedAt,
  creationDate, lastModified, SSE event timestamps)
- Pass config.language to browser bot in join request for captions spoken language

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 01:26:27 +01:00

119 lines
No EOL
3.8 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Timezone utilities for consistent timestamp handling across the gateway.
Ensures all timestamps are properly handled as UTC.
"""
from datetime import datetime, timezone
from typing import Optional, Any
import time
import logging
# Configure logger
logger = logging.getLogger(__name__)
def getUtcNow() -> datetime:
"""
Get current time in UTC with timezone info.
Returns:
datetime: Current UTC time with timezone info
"""
return datetime.now(timezone.utc)
def getUtcTimestamp() -> float:
"""
Get current UTC timestamp (seconds since epoch with millisecond precision).
Returns:
float: Current UTC timestamp in seconds with millisecond precision
"""
return time.time()
def getIsoTimestamp() -> str:
"""
Get current UTC timestamp as ISO 8601 string.
Use this for fields declared as 'ISO timestamp' strings (e.g. Pydantic str fields
that will be parsed by JavaScript's new Date()). JavaScript cannot parse epoch
floats from getUtcTimestamp() as dates.
Returns:
str: Current UTC time in ISO 8601 format (e.g. "2026-02-15T00:08:32.070000+00:00")
"""
return datetime.now(timezone.utc).isoformat()
def createExpirationTimestamp(expiresInSeconds: int) -> float:
"""
Create a new expiration timestamp from seconds until expiration.
Args:
expiresInSeconds (int): Seconds until expiration
Returns:
float: UTC timestamp in seconds
"""
return getUtcTimestamp() + expiresInSeconds
def parseTimestamp(value: Any, default: Optional[float] = None) -> Optional[float]:
"""
Parse a timestamp value from various source formats and return as float.
Handles conversion from:
- 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)
This function is useful when database connectors (e.g., psycopg2) may return
numeric fields as strings in some environments (e.g., Azure PostgreSQL).
Args:
value: The timestamp value to parse (can be float, int, str, or None)
default: Optional default value to return if value is None or invalid.
If None and value is invalid, returns None.
Returns:
float: Parsed timestamp as float, or default value if provided, or None
Examples:
>>> parseTimestamp(1234567890.123)
1234567890.123
>>> parseTimestamp("1234567890.123")
1234567890.123
>>> parseTimestamp(None, default=0.0)
0.0
>>> parseTimestamp("invalid", default=getUtcTimestamp())
# Returns current timestamp
"""
if value is None:
return default
# Already a float
if isinstance(value, float):
return value
# Integer - convert to float
if isinstance(value, int):
return float(value)
# String - try to parse as numeric
if isinstance(value, str):
# Empty string
if not value.strip():
logger.warning(f"parseTimestamp: Received empty string, returning default={default}")
return default
try:
return float(value)
except (ValueError, TypeError) as e:
# Invalid string format
logger.warning(f"parseTimestamp: Failed to parse string '{value}' as float: {type(e).__name__}: {str(e)}, returning default={default}")
return default
# Unknown type - try to convert anyway
try:
return float(value)
except (ValueError, TypeError) as e:
logger.warning(f"parseTimestamp: Failed to convert value of type {type(value).__name__} '{value}' to float: {type(e).__name__}: {str(e)}, returning default={default}")
return default