feat(utils): 添加 UTC 时间戳解析函数以支持日期格式化

This commit is contained in:
Jeff Gu committed 2026-04-12 03:10:09 +08:00
1 parent f96bdc7cb9
commit e49ed2427a
4 files changed
+46 -8

No files matched your search

@@ -1,10 +1,10 @@
import { AnalyticsRange, AnalyticsTrendPoint } from "@/types";
import { formatCurrency } from "@/lib/utils";
import { formatCurrency, parseUtcTimestamp } from "@/lib/utils";
import { Card } from "../ui/card";
const formatAxisLabel = (value: string, range: AnalyticsRange): string => {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
const date = parseUtcTimestamp(value);
if (!date) {
return value;
}
+19
View File
@@ -5,6 +5,25 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
const TIMESTAMP_TIMEZONE_PATTERN = /(Z|[+-]\d{2}:\d{2})$/i
export function parseUtcTimestamp(value: string): Date | null {
const rawValue = value.trim()
if (!rawValue) {
return null
}
const normalizedValue = rawValue.includes('T')
? rawValue
: rawValue.replace(' ', 'T')
const candidate = TIMESTAMP_TIMEZONE_PATTERN.test(normalizedValue)
? normalizedValue
: `${normalizedValue}Z`
const date = new Date(candidate)
return Number.isNaN(date.getTime()) ? null : date
}
export function formatCurrency(value: number): string {
return `$${(value / 1000000).toFixed(2)}`
}
+3 -3
View File
@@ -10,7 +10,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f
import { Input } from "@/components/ui/input";
import { Select } from "@/components/ui/select";
import { readScopedCache, writeScopedCache } from "@/lib/local-cache";
import { cn, formatCurrency } from "@/lib/utils";
import { cn, formatCurrency, parseUtcTimestamp } from "@/lib/utils";
import { Eye, RefreshCw, RotateCcw, Search } from "lucide-react";
type UsageLogFilterState = {
@@ -89,8 +89,8 @@ const hydrateUsageLogFilters = (
};
const formatDateTime = (value: string): string => {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
const date = parseUtcTimestamp(value);
if (!date) {
return value;
}
+21 -2
View File
@@ -235,6 +235,25 @@ const toText = (value: unknown): string => {
return typeof value === "string" ? value : "";
};
const TIMESTAMP_TIMEZONE_PATTERN = /(Z|[+-]\d{2}:\d{2})$/i;
const normalizeAnalyticsTimestamp = (value: unknown): string => {
const rawValue = toText(value).trim();
if (!rawValue) {
return "";
}
const normalizedValue = rawValue.includes("T")
? rawValue
: rawValue.replace(" ", "T");
const candidate = TIMESTAMP_TIMEZONE_PATTERN.test(normalizedValue)
? normalizedValue
: `${normalizedValue}Z`;
const date = new Date(candidate);
return Number.isNaN(date.getTime()) ? rawValue : date.toISOString();
};
const escapeSqlString = (value: string): string => {
return value.replace(/'/g, "''");
};
@@ -785,7 +804,7 @@ LIMIT ${limit}
sampled: true,
compatibilityWarning,
items: rows.map((row) => ({
timestamp: toText(row.timestamp),
timestamp: normalizeAnalyticsTimestamp(row.timestamp),
routeId: toText(row.route_id),
tokenName: toText(row.token_name),
channelKey: toText(row.channel_key),
@@ -970,7 +989,7 @@ OFFSET ${offset}
hasPrevPage: totalPages > 0 && page > 1,
hasNextPage: totalPages > 0 && page < totalPages,
items: rows.map((row) => ({
timestamp: toText(row.timestamp),
timestamp: normalizeAnalyticsTimestamp(row.timestamp),
routeId: toText(row.route_id),
tokenName: toText(row.token_name),
channelKey: toText(row.channel_key),