feat(timezone): implement Tehran timezone handling across the application
This commit is contained in:
@@ -14,6 +14,10 @@ export function formatNumber(n: number): string {
|
||||
return new Intl.NumberFormat('fa-IR').format(n);
|
||||
}
|
||||
|
||||
// تایمزون رسمی سراسری برنامه = ایران. همهٔ نمایش/تبدیل تاریخ باید با این tz باشد،
|
||||
// مستقل از تایمزون مرورگرِ کاربر (اجباری).
|
||||
export const APP_TZ = 'Asia/Tehran';
|
||||
|
||||
export function toDate(val: string | number | null | undefined): Date | null {
|
||||
if (val == null || val === '') return null;
|
||||
if (typeof val === 'number') return new Date(val * 1000);
|
||||
@@ -26,6 +30,7 @@ export function formatDate(val: string | number | null | undefined): string {
|
||||
const d = toDate(val);
|
||||
if (!d || isNaN(d.getTime())) return '—';
|
||||
return new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
|
||||
timeZone: APP_TZ,
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
}).format(d);
|
||||
}
|
||||
@@ -34,16 +39,47 @@ export function formatDateTime(val: string | number | null | undefined): string
|
||||
const d = toDate(val);
|
||||
if (!d || isNaN(d.getTime())) return '—';
|
||||
return new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
|
||||
timeZone: APP_TZ,
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
}).format(d);
|
||||
}
|
||||
|
||||
// ساعت (HH:MM) یک timestamp ثانیهای به وقت ایران — برای تایملاین/جدول نوبتها.
|
||||
export function formatTime(tsSeconds: number): string {
|
||||
return new Intl.DateTimeFormat('fa-IR', {
|
||||
timeZone: APP_TZ, hour: '2-digit', minute: '2-digit',
|
||||
}).format(new Date(tsSeconds * 1000));
|
||||
}
|
||||
|
||||
// اختلاف دقیقهایِ یک تایمزون با UTC در لحظهٔ مشخص (ایران ثابت +03:30 است ولی این
|
||||
// روش عمومی و درست است).
|
||||
function tzOffsetMinutes(date: Date, tz: string): number {
|
||||
const parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: tz, hour12: false,
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
}).formatToParts(date).reduce<Record<string, string>>((a, p) => (a[p.type] = p.value, a), {});
|
||||
const asUTC = Date.UTC(+parts.year, +parts.month - 1, +parts.day, +parts.hour, +parts.minute, +parts.second);
|
||||
return Math.round((asUTC - date.getTime()) / 60000);
|
||||
}
|
||||
|
||||
// «ساعت دیواریِ ایران» (تاریخ Y-m-d + HH:MM) → Unix ثانیه. مستقل از tz مرورگر، تا
|
||||
// ثبت نوبت با ساعت واردشده دقیقاً همان لحظه به وقت ایران باشد.
|
||||
export function tehranWallClockToUnix(isoDate: string, time: string): number {
|
||||
const [Y, M, D] = isoDate.split('-').map(Number);
|
||||
const [h, m] = (time || '00:00').split(':').map(Number);
|
||||
const asUtc = Date.UTC(Y, (M || 1) - 1, D || 1, h || 0, m || 0, 0);
|
||||
const offset = tzOffsetMinutes(new Date(asUtc), APP_TZ);
|
||||
return Math.floor((asUtc - offset * 60000) / 1000);
|
||||
}
|
||||
|
||||
// تاریخ تقویمیِ میلادی (Y-m-d) یک لحظه، به وقت ایران.
|
||||
export function toGregorianDate(d: Date): string {
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${day}`;
|
||||
// en-CA → قالب YYYY-MM-DD
|
||||
return new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: APP_TZ, year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
}).format(d);
|
||||
}
|
||||
|
||||
// API stores contract dates as Unix seconds; the date input speaks Y-m-d strings.
|
||||
|
||||
Reference in New Issue
Block a user