修改时区显示问题
This commit is contained in:
@@ -1,11 +1,38 @@
|
|||||||
|
const CST_OFFSET = 8 * 60; // CST = UTC+8, in minutes
|
||||||
|
|
||||||
export function formatDate(iso: string | null | undefined): string {
|
export function formatDate(iso: string | null | undefined): string {
|
||||||
if (!iso) return '-';
|
if (!iso) return '-';
|
||||||
let s = iso.trim();
|
const s = iso.trim();
|
||||||
if (!s.includes('T')) s = s.replace(' ', 'T');
|
if (!s) return '-';
|
||||||
// Truncate microseconds: 2026-05-13T15:04:04.313751 → 2026-05-13T15:04:04
|
|
||||||
const dotIdx = s.indexOf('.');
|
// Parse the ISO string, handling timezone offset
|
||||||
if (dotIdx > 0) s = s.slice(0, dotIdx);
|
// Match: 2026-05-13T15:04:04.313751+00:00 or 2026-05-13T15:04:04Z or 2026-05-13T15:04:04
|
||||||
// Remove any trailing timezone info (backend now sends naive datetimes)
|
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/);
|
||||||
s = s.replace(/[+-]\d{2}:?\d{0,2}$/, '').replace(/Z$/, '');
|
if (!m) return s.slice(0, 16).replace('T', ' ');
|
||||||
return s.replace('T', ' ').slice(0, 16);
|
|
||||||
|
const [, year, month, day, hour, min, sec, tz] = m;
|
||||||
|
// Build a Date in UTC
|
||||||
|
const utcMs = Date.UTC(+year, +month - 1, +day, +hour, +min, +sec);
|
||||||
|
|
||||||
|
if (tz && tz !== 'Z') {
|
||||||
|
// Has explicit offset like +00:00 or +08:00 — already accounted for in the matched components
|
||||||
|
// We parsed HH:MM:SS as-is, which are in the given offset.
|
||||||
|
// Convert to UTC first by subtracting the offset
|
||||||
|
const sign = tz[0] === '+' ? 1 : -1;
|
||||||
|
const [oh, om] = tz.slice(1).split(':');
|
||||||
|
const offsetMin = sign * (+oh * 60 + +om);
|
||||||
|
const localMs = utcMs - offsetMin * 60000 + CST_OFFSET * 60000;
|
||||||
|
const d = new Date(localMs);
|
||||||
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No tz or Z: if Z it's UTC, if no tz it's naive (assume CST from backend)
|
||||||
|
const isUTC = tz === 'Z';
|
||||||
|
const localMs = isUTC ? utcMs + CST_OFFSET * 60000 : utcMs;
|
||||||
|
const d = new Date(localMs);
|
||||||
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n: number): string {
|
||||||
|
return n < 10 ? `0${n}` : String(n);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1668,21 +1668,21 @@ async def get_stats(
|
|||||||
start_date: str = Query(None),
|
start_date: str = Query(None),
|
||||||
end_date: str = Query(None),
|
end_date: str = Query(None),
|
||||||
):
|
):
|
||||||
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
today_start = datetime.now(CST).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if start_date:
|
if start_date:
|
||||||
date_start = datetime.strptime(start_date, "%Y-%m-%d")
|
date_start = datetime.strptime(start_date, "%Y-%m-%d").replace(tzinfo=CST)
|
||||||
else:
|
else:
|
||||||
date_start = today_start
|
date_start = today_start
|
||||||
if end_date:
|
if end_date:
|
||||||
date_end = datetime.strptime(end_date, "%Y-%m-%d")
|
date_end = datetime.strptime(end_date, "%Y-%m-%d").replace(tzinfo=CST)
|
||||||
date_end = date_end.replace(hour=23, minute=59, second=59, microsecond=999999)
|
date_end = date_end.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||||
else:
|
else:
|
||||||
date_end = datetime.now()
|
date_end = datetime.now(CST)
|
||||||
except:
|
except:
|
||||||
date_start = today_start
|
date_start = today_start
|
||||||
date_end = datetime.now()
|
date_end = datetime.now(CST)
|
||||||
|
|
||||||
total_users = (await db.execute(
|
total_users = (await db.execute(
|
||||||
select(func.count(User.id)).where(
|
select(func.count(User.id)).where(
|
||||||
@@ -1983,7 +1983,7 @@ async def admin_update_generation_status(
|
|||||||
if body.get("image_url"):
|
if body.get("image_url"):
|
||||||
record.image_url = body["image_url"]
|
record.image_url = body["image_url"]
|
||||||
if new_status == "completed":
|
if new_status == "completed":
|
||||||
record.generated_at = datetime.now()
|
record.generated_at = datetime.now(CST)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
await log_operation(
|
await log_operation(
|
||||||
db,
|
db,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
CST = timezone(timedelta(hours=8))
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -118,7 +120,7 @@ async def _handle_daily_login_credits(db: AsyncSession, user: User) -> None:
|
|||||||
if credits <= 0:
|
if credits <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
today = datetime.now().date()
|
today = datetime.now(CST).date()
|
||||||
if user.last_login_at:
|
if user.last_login_at:
|
||||||
last_login_date = user.last_login_at.date()
|
last_login_date = user.last_login_at.date()
|
||||||
if last_login_date >= today:
|
if last_login_date >= today:
|
||||||
@@ -160,7 +162,7 @@ async def login(req: LoginRequest, db: AsyncSession = Depends(get_db)):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await _handle_daily_login_credits(db, user)
|
await _handle_daily_login_credits(db, user)
|
||||||
user.last_login_at = datetime.now()
|
user.last_login_at = datetime.now(CST)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
return _token_response(user, req.remember_me)
|
return _token_response(user, req.remember_me)
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ async def sms_login(req: SmsLoginRequest, db: AsyncSession = Depends(get_db)):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await _handle_daily_login_credits(db, user)
|
await _handle_daily_login_credits(db, user)
|
||||||
user.last_login_at = datetime.now()
|
user.last_login_at = datetime.now(CST)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
return _token_response(user, req.remember_me)
|
return _token_response(user, req.remember_me)
|
||||||
|
|
||||||
@@ -223,7 +225,7 @@ async def register(req: RegisterRequest, db: AsyncSession = Depends(get_db)):
|
|||||||
username=req.phone,
|
username=req.phone,
|
||||||
phone=req.phone,
|
phone=req.phone,
|
||||||
hashed_password=hash_password(req.password),
|
hashed_password=hash_password(req.password),
|
||||||
password_set_at=datetime.now(),
|
password_set_at=datetime.now(CST),
|
||||||
credits=register_credits,
|
credits=register_credits,
|
||||||
is_admin=False,
|
is_admin=False,
|
||||||
user_type="frontend",
|
user_type="frontend",
|
||||||
@@ -295,7 +297,7 @@ async def set_password(
|
|||||||
)
|
)
|
||||||
|
|
||||||
current_user.hashed_password = hash_password(req.new_password)
|
current_user.hashed_password = hash_password(req.new_password)
|
||||||
current_user.password_set_at = datetime.now()
|
current_user.password_set_at = datetime.now(CST)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
return {"message": "密码设置成功", "must_set_password": False}
|
return {"message": "密码设置成功", "must_set_password": False}
|
||||||
|
|
||||||
@@ -319,7 +321,7 @@ async def change_password(
|
|||||||
)
|
)
|
||||||
|
|
||||||
current_user.hashed_password = hash_password(req.new_password)
|
current_user.hashed_password = hash_password(req.new_password)
|
||||||
current_user.password_set_at = datetime.now()
|
current_user.password_set_at = datetime.now(CST)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
return {"message": "密码修改成功"}
|
return {"message": "密码修改成功"}
|
||||||
|
|
||||||
@@ -382,7 +384,7 @@ async def admin_login(req: LoginRequest, db: AsyncSession = Depends(get_db)):
|
|||||||
detail="该账号不是管理员账号",
|
detail="该账号不是管理员账号",
|
||||||
)
|
)
|
||||||
|
|
||||||
user.last_login_at = datetime.now()
|
user.last_login_at = datetime.now(CST)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
token = create_access_token(user.id, req.remember_me)
|
token = create_access_token(user.id, req.remember_me)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
CST = timezone(timedelta(hours=8))
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, File, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, File, status
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
@@ -733,7 +735,7 @@ async def seedance_callback(request: Request, db: AsyncSession = Depends(get_db)
|
|||||||
record.video_url = remote_url
|
record.video_url = remote_url
|
||||||
else:
|
else:
|
||||||
record.video_url = remote_url
|
record.video_url = remote_url
|
||||||
record.generated_at = datetime.now()
|
record.generated_at = datetime.now(CST)
|
||||||
if record.video_url:
|
if record.video_url:
|
||||||
await record_generation_record_generated_resource(
|
await record_generation_record_generated_resource(
|
||||||
db,
|
db,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
CST = timezone(timedelta(hours=8))
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -405,7 +407,7 @@ async def export_team_credit_records(
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
safe_team_name = team.name or "team"
|
safe_team_name = team.name or "team"
|
||||||
filename = f"团队积分_{safe_team_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
filename = f"团队积分_{safe_team_name}_{datetime.now(CST).strftime('%Y%m%d_%H%M%S')}.csv"
|
||||||
encoded_filename = quote(filename)
|
encoded_filename = quote(filename)
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
iter([output.getvalue()]),
|
iter([output.getvalue()]),
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
|
const CST_OFFSET = 8 * 60; // CST = UTC+8, in minutes
|
||||||
|
|
||||||
export function formatDate(iso: string | null | undefined): string {
|
export function formatDate(iso: string | null | undefined): string {
|
||||||
if (!iso) return '-';
|
if (!iso) return '-';
|
||||||
let s = iso.trim();
|
const s = iso.trim();
|
||||||
// Normalize: space → T
|
if (!s) return '-';
|
||||||
if (!s.includes('T')) s = s.replace(' ', 'T');
|
|
||||||
// Truncate microseconds: 2026-05-13T15:04:04.313751 → 2026-05-13T15:04:04
|
// Parse the ISO string, handling timezone offset
|
||||||
const dotIdx = s.indexOf('.');
|
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/);
|
||||||
if (dotIdx > 0) s = s.slice(0, dotIdx);
|
if (!m) return s.slice(0, 16).replace('T', ' ');
|
||||||
// Remove any trailing timezone info (backend now sends naive datetimes)
|
|
||||||
s = s.replace(/[+-]\d{2}:?\d{0,2}$/, '').replace(/Z$/, '');
|
const [, year, month, day, hour, min, sec, tz] = m;
|
||||||
return s.replace('T', ' ').slice(0, 16);
|
const utcMs = Date.UTC(+year, +month - 1, +day, +hour, +min, +sec);
|
||||||
|
|
||||||
|
if (tz && tz !== 'Z') {
|
||||||
|
const sign = tz[0] === '+' ? 1 : -1;
|
||||||
|
const [oh, om] = tz.slice(1).split(':');
|
||||||
|
const offsetMin = sign * (+oh * 60 + +om);
|
||||||
|
const localMs = utcMs - offsetMin * 60000 + CST_OFFSET * 60000;
|
||||||
|
const d = new Date(localMs);
|
||||||
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUTC = tz === 'Z';
|
||||||
|
const localMs = isUTC ? utcMs + CST_OFFSET * 60000 : utcMs;
|
||||||
|
const d = new Date(localMs);
|
||||||
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n: number): string {
|
||||||
|
return n < 10 ? `0${n}` : String(n);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user