// Время показываем в поясе приложения — одном для всех, независимо от пояса браузера. // Смещение задаёт APP_TZ_OFFSET_HOURS на бэкенде (в нём же считается «дата игры»); // App.tsx получает его из /api/auth/config до первой отрисовки страниц (#68). // 3 ч (МСК) — запасное значение, если конфиг недоступен. let appTzOffsetMin = 3 * 60; export function setAppTzOffsetHours(hours: number): void { appTzOffsetMin = hours * 60; } const p2 = (n: number) => String(n).padStart(2, "0"); // iso — корректный момент (бэкенд отдаёт UTC со смещением). Сдвигаем в пояс приложения // и форматируем по UTC-частям, чтобы получить «настенное» время этого пояса. function shifted(iso: string): Date { return new Date(new Date(iso).getTime() + appTzOffsetMin * 60_000); } // Склонение существительного при числе: plural(3, "игрок", "игрока", "игроков"). export function plural(n: number, one: string, few: string, many: string): string { const m10 = n % 10; const m100 = n % 100; if (m10 === 1 && m100 !== 11) return one; if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return few; return many; } // Изменение рейтинга за партию: всегда со знаком и одним знаком после запятой // («+12.3», «−4.1», «0.0»); минус — типографский, как в справке. export function formatRatingDelta(delta: number): string { const abs = Math.abs(delta).toFixed(1); if (abs === "0.0") return "0.0"; return `${delta > 0 ? "+" : "−"}${abs}`; } export function formatDuration(minutes: number | null | undefined): string { if (minutes == null) return "—"; const h = Math.floor(minutes / 60); const m = minutes % 60; if (h && m) return `≈ ${h} ч ${m} мин`; if (h) return `≈ ${h} ч`; return `≈ ${m} мин`; } export function formatTime(iso: string | null | undefined): string { if (!iso) return "—"; const d = shifted(iso); if (Number.isNaN(d.getTime())) return iso; return `${p2(d.getUTCDate())}.${p2(d.getUTCMonth() + 1)} ${p2(d.getUTCHours())}:${p2(d.getUTCMinutes())}`; } // «Дата игры» приходит date-only строкой (YYYY-MM-DD) — просто переставляем части, // без сдвига пояса (бэкенд уже посчитал дату в поясе приложения). export function formatDate(iso: string | null | undefined): string { if (!iso) return "—"; const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso); return m ? `${m[3]}.${m[2]}.${m[1]}` : iso; } // Значение — «настенное» время в том же поясе приложения, что // и всё отображение: админ вводит время показа объявления в нём, где бы ни был браузер. export function toAppLocalInput(iso: string): string { const d = shifted(iso); return `${d.getUTCFullYear()}-${p2(d.getUTCMonth() + 1)}-${p2(d.getUTCDate())}T${p2(d.getUTCHours())}:${p2(d.getUTCMinutes())}`; } // Обратно: «настенное» время пояса приложения из datetime-local → момент ISO (UTC). // null — поле пустое. export function fromAppLocalInput(value: string): string | null { const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(value); if (!m) return null; const ms = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]) - appTzOffsetMin * 60_000; return new Date(ms).toISOString(); } export function formatDateTime(iso: string | null | undefined): string { if (!iso) return "—"; const d = shifted(iso); if (Number.isNaN(d.getTime())) return iso; return `${p2(d.getUTCDate())}.${p2(d.getUTCMonth() + 1)}.${d.getUTCFullYear()} ${p2(d.getUTCHours())}:${p2(d.getUTCMinutes())}`; }