mirror of
https://github.com/TheSmallHanCat/sora2api.git
synced 2026-02-04 10:14:41 +08:00
feat: 新增时区配置功能、支持UTC时间自动转换为本地时区及环境变量配置
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set timezone to Asia/Shanghai (UTC+8) by default
|
||||||
|
# Can be overridden with -e TZ=<timezone> when running container
|
||||||
|
ENV TZ=Asia/Shanghai \
|
||||||
|
TIMEZONE_OFFSET=8
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|||||||
@@ -52,3 +52,10 @@ at_auto_refresh_enabled = false
|
|||||||
|
|
||||||
[call_logic]
|
[call_logic]
|
||||||
call_mode = "default"
|
call_mode = "default"
|
||||||
|
|
||||||
|
[timezone]
|
||||||
|
# 时区偏移小时数,默认为东八区(中国标准时间)
|
||||||
|
# 可选值:-12 到 +14 的整数
|
||||||
|
# 常用时区:中国/新加坡 +8, 日本/韩国 +9, 印度 +5.5, 伦敦 0, 纽约 -5, 洛杉矶 -8
|
||||||
|
timezone_offset = 8
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ services:
|
|||||||
- ./config/setting.toml:/app/config/setting.toml
|
- ./config/setting.toml:/app/config/setting.toml
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
- TIMEZONE_OFFSET=8
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -928,9 +928,16 @@ async def get_stats(token: str = Depends(verify_admin_token)):
|
|||||||
@router.get("/api/logs")
|
@router.get("/api/logs")
|
||||||
async def get_logs(limit: int = 100, token: str = Depends(verify_admin_token)):
|
async def get_logs(limit: int = 100, token: str = Depends(verify_admin_token)):
|
||||||
"""Get recent logs with token email and task progress"""
|
"""Get recent logs with token email and task progress"""
|
||||||
|
from src.utils.timezone import convert_utc_to_local
|
||||||
|
|
||||||
logs = await db.get_recent_logs(limit)
|
logs = await db.get_recent_logs(limit)
|
||||||
result = []
|
result = []
|
||||||
for log in logs:
|
for log in logs:
|
||||||
|
# Convert UTC time to local timezone
|
||||||
|
created_at = log.get("created_at")
|
||||||
|
if created_at:
|
||||||
|
created_at = convert_utc_to_local(created_at)
|
||||||
|
|
||||||
log_data = {
|
log_data = {
|
||||||
"id": log.get("id"),
|
"id": log.get("id"),
|
||||||
"token_id": log.get("token_id"),
|
"token_id": log.get("token_id"),
|
||||||
@@ -939,7 +946,7 @@ async def get_logs(limit: int = 100, token: str = Depends(verify_admin_token)):
|
|||||||
"operation": log.get("operation"),
|
"operation": log.get("operation"),
|
||||||
"status_code": log.get("status_code"),
|
"status_code": log.get("status_code"),
|
||||||
"duration": log.get("duration"),
|
"duration": log.get("duration"),
|
||||||
"created_at": log.get("created_at"),
|
"created_at": created_at,
|
||||||
"request_body": log.get("request_body"),
|
"request_body": log.get("request_body"),
|
||||||
"response_body": log.get("response_body"),
|
"response_body": log.get("response_body"),
|
||||||
"task_id": log.get("task_id")
|
"task_id": log.get("task_id")
|
||||||
|
|||||||
@@ -813,7 +813,7 @@ class GenerationHandler:
|
|||||||
# Send retry notification to user if streaming
|
# Send retry notification to user if streaming
|
||||||
if stream:
|
if stream:
|
||||||
yield self._format_stream_chunk(
|
yield self._format_stream_chunk(
|
||||||
reasoning_content=f"**生成失败,正在重试**\\n\\n第 {retry_count} 次重试(共 {max_retries} 次)...\\n\\n失败原因:{str(e)}\\n\\n"
|
reasoning_content=f"**生成失败,正在重试**\n\n第 {retry_count} 次重试(共 {max_retries} 次)...\n\n失败原因:{str(e)}\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Small delay before retry
|
# Small delay before retry
|
||||||
|
|||||||
95
src/utils/timezone.py
Normal file
95
src/utils/timezone.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
"""Timezone utilities for consistent time handling across the application"""
|
||||||
|
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from typing import Optional
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def get_timezone_offset() -> int:
|
||||||
|
"""Get timezone offset in hours from environment variable or default to UTC+8
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Timezone offset in hours (default: 8 for China/Asia/Shanghai)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(os.getenv("TIMEZONE_OFFSET", "8"))
|
||||||
|
except ValueError:
|
||||||
|
return 8
|
||||||
|
|
||||||
|
|
||||||
|
def get_timezone() -> timezone:
|
||||||
|
"""Get timezone object based on configured offset
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
timezone: Timezone object with configured offset
|
||||||
|
"""
|
||||||
|
offset_hours = get_timezone_offset()
|
||||||
|
return timezone(timedelta(hours=offset_hours))
|
||||||
|
|
||||||
|
|
||||||
|
def convert_utc_to_local(utc_time_str: Optional[str]) -> Optional[str]:
|
||||||
|
"""Convert UTC timestamp string to local timezone with ISO format
|
||||||
|
|
||||||
|
Args:
|
||||||
|
utc_time_str: UTC timestamp string (e.g., "2024-01-24 10:30:45")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: ISO formatted timestamp with timezone info (e.g., "2024-01-24T18:30:45+08:00")
|
||||||
|
None: If conversion fails or input is None
|
||||||
|
"""
|
||||||
|
if not utc_time_str:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse SQLite timestamp (UTC) - handle both with and without 'Z' suffix
|
||||||
|
dt = datetime.fromisoformat(utc_time_str.replace('Z', '+00:00'))
|
||||||
|
|
||||||
|
# If no timezone info, assume UTC
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
# Convert to local timezone
|
||||||
|
local_tz = get_timezone()
|
||||||
|
dt_local = dt.astimezone(local_tz)
|
||||||
|
|
||||||
|
# Return ISO format with timezone
|
||||||
|
return dt_local.isoformat()
|
||||||
|
except Exception as e:
|
||||||
|
# If conversion fails, return original value
|
||||||
|
print(f"Warning: Failed to convert timestamp '{utc_time_str}': {e}")
|
||||||
|
return utc_time_str
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_local_time() -> datetime:
|
||||||
|
"""Get current time in local timezone
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
datetime: Current datetime with local timezone
|
||||||
|
"""
|
||||||
|
return datetime.now(get_timezone())
|
||||||
|
|
||||||
|
|
||||||
|
def format_local_time(dt: Optional[datetime], fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
|
||||||
|
"""Format datetime to string in local timezone
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dt: Datetime object to format
|
||||||
|
fmt: Format string (default: "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Formatted time string, or "-" if dt is None
|
||||||
|
"""
|
||||||
|
if not dt:
|
||||||
|
return "-"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Convert to local timezone if needed
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
dt = dt.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
local_tz = get_timezone()
|
||||||
|
dt_local = dt.astimezone(local_tz)
|
||||||
|
return dt_local.strftime(fmt)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Failed to format datetime: {e}")
|
||||||
|
return str(dt)
|
||||||
Reference in New Issue
Block a user