goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
/**
* 앱 전역 표시 시간대 (IANA).
* DB app-settings · 설정 화면 · 하단 바에서 동일 값 사용.
*/
import { useSyncExternalStore } from 'react';
import { TickMarkType, type Time } from 'lightweight-charts';
import type { Timeframe } from '../types';
export const DEFAULT_DISPLAY_TIMEZONE = 'Asia/Seoul';
export interface TimezoneOption {
id: string;
label: string;
abbr: string;
}
export const TIMEZONE_OPTIONS: TimezoneOption[] = [
{ id: 'Asia/Seoul', label: 'Asia/Seoul (KST, UTC+9)', abbr: 'KST' },
{ id: 'UTC', label: 'UTC', abbr: 'UTC' },
{ id: 'Asia/Tokyo', label: 'Asia/Tokyo (JST, UTC+9)', abbr: 'JST' },
{ id: 'Asia/Shanghai', label: 'Asia/Shanghai (CST, UTC+8)', abbr: 'CST' },
{ id: 'Asia/Hong_Kong', label: 'Asia/Hong_Kong (HKT, UTC+8)', abbr: 'HKT' },
{ id: 'Asia/Singapore', label: 'Asia/Singapore (SGT, UTC+8)', abbr: 'SGT' },
{ id: 'Europe/London', label: 'Europe/London (GMT/BST)', abbr: 'GMT' },
{ id: 'Europe/Berlin', label: 'Europe/Berlin (CET)', abbr: 'CET' },
{ id: 'America/New_York', label: 'America/New_York (ET)', abbr: 'ET' },
{ id: 'America/Chicago', label: 'America/Chicago (CT)', abbr: 'CT' },
{ id: 'America/Los_Angeles',label: 'America/Los_Angeles (PT)', abbr: 'PT' },
];
let _tz = DEFAULT_DISPLAY_TIMEZONE;
const _listeners = new Set<() => void>();
export function getDisplayTimezone(): string {
return _tz;
}
export function setDisplayTimezone(tz: string): void {
const next = normalizeTimezone(tz);
if (_tz === next) return;
_tz = next;
_listeners.forEach(l => l());
}
export function subscribeDisplayTimezone(cb: () => void): () => void {
_listeners.add(cb);
return () => _listeners.delete(cb);
}
export function useDisplayTimezone(): string {
return useSyncExternalStore(subscribeDisplayTimezone, getDisplayTimezone, () => DEFAULT_DISPLAY_TIMEZONE);
}
export function normalizeTimezone(tz: string): string {
const t = (tz || '').trim();
if (!t) return DEFAULT_DISPLAY_TIMEZONE;
if (TIMEZONE_OPTIONS.some(o => o.id === t)) return t;
try {
Intl.DateTimeFormat(undefined, { timeZone: t });
return t;
} catch {
return DEFAULT_DISPLAY_TIMEZONE;
}
}
export function getTimezoneAbbr(tz: string): string {
const id = normalizeTimezone(tz);
return TIMEZONE_OPTIONS.find(o => o.id === id)?.abbr ?? id.split('/').pop() ?? 'TZ';
}
function isDailyTimeframe(tf?: string): boolean {
return tf === '1D' || tf === '1W' || tf === '1M';
}
/** 하단 바·시계 등 HH:mm:ss */
export function formatUnixClock(unixSec: number, tz?: string, withSeconds = true): string {
if (!unixSec) return '';
const zone = normalizeTimezone(tz ?? _tz);
const d = new Date(unixSec * 1000);
const opts: Intl.DateTimeFormatOptions = {
timeZone: zone,
hour: '2-digit',
minute: '2-digit',
hour12: false,
};
if (withSeconds) opts.second = '2-digit';
return new Intl.DateTimeFormat('en-GB', opts).format(d);
}
/** 차트 축·범례용 */
export function formatUnixForChart(unixSec: number, timeframe?: Timeframe, tz?: string): string {
if (!unixSec) return '';
const zone = normalizeTimezone(tz ?? _tz);
const d = new Date(unixSec * 1000);
if (isDailyTimeframe(timeframe)) {
return new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
year: '2-digit',
month: '2-digit',
day: '2-digit',
}).format(d);
}
return new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
}).format(d);
}
/** LWC Time → unix 초 (없으면 null) */
export function lwcTimeToUnix(time: Time): number | null {
if (typeof time === 'number') return time;
if (time && typeof time === 'object' && 'year' in time) {
const t = time as { year: number; month: number; day: number };
return Math.floor(Date.UTC(t.year, t.month - 1, t.day) / 1000);
}
return null;
}
/** LWC Time → 문자열 (크로스헤어 등) */
export function formatLwcTime(
time: number | { year: number; month: number; day: number },
timeframe?: Timeframe,
tz?: string,
): string {
if (typeof time === 'number') {
return formatUnixForChart(time, timeframe, tz);
}
const zone = normalizeTimezone(tz ?? _tz);
const utc = Date.UTC(time.year, time.month - 1, time.day);
return new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
year: '2-digit',
month: '2-digit',
day: '2-digit',
}).format(new Date(utc));
}
/** LWC X축 눈금 — 선택한 표시 시간대 기준 (기본 포맷터는 브라우저 로컬 TZ 사용) */
export function formatLwcTickMark(
time: Time,
tickMarkType: TickMarkType,
timeframe?: Timeframe,
tz?: string,
): string {
const zone = normalizeTimezone(tz ?? _tz);
const unixSec = lwcTimeToUnix(time);
if (unixSec == null) return '';
const d = new Date(unixSec * 1000);
switch (tickMarkType) {
case TickMarkType.Year:
return new Intl.DateTimeFormat('en-GB', { timeZone: zone, year: 'numeric' }).format(d);
case TickMarkType.Month:
return new Intl.DateTimeFormat('en-GB', { timeZone: zone, month: 'short' }).format(d);
case TickMarkType.DayOfMonth:
if (isDailyTimeframe(timeframe)) {
return new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
month: 'short',
day: 'numeric',
}).format(d);
}
return new Intl.DateTimeFormat('en-GB', { timeZone: zone, day: 'numeric' }).format(d);
case TickMarkType.Time:
return new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
hour: '2-digit',
minute: '2-digit',
hour12: false,
}).format(d);
case TickMarkType.TimeWithSeconds:
return new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
}).format(d);
default:
return formatUnixForChart(unixSec, timeframe, zone);
}
}
/** 하단 바 표시: `06:36:00 KST` */
export function formatBottomBarTime(unixSec: number, tz?: string): string {
if (!unixSec) return ` ${getTimezoneAbbr(tz ?? _tz)}`;
const zone = normalizeTimezone(tz ?? _tz);
return `${formatUnixClock(unixSec, zone, true)} ${getTimezoneAbbr(zone)}`;
}
/** 알림·드로잉 등 날짜+시간 */
export function formatUnixDateTime(unixSec: number, tz?: string): string {
if (!unixSec) return '—';
const zone = normalizeTimezone(tz ?? _tz);
const d = new Date(unixSec * 1000);
const date = new Intl.DateTimeFormat('en-GB', {
timeZone: zone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).format(d);
return `${date} ${formatUnixClock(unixSec, zone, true)}`;
}
/** 현재 시각 (호가창 시계 등) */
export function formatNowClock(tz?: string, withSeconds = true): string {
const zone = normalizeTimezone(tz ?? _tz);
return formatUnixClock(Math.floor(Date.now() / 1000), zone, withSeconds);
}