From d284c6e940964fa96a54a8c049294ca32451bbb7 Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Thu, 17 Sep 2026 18:57:55 +0300 Subject: [PATCH] feat: update alert timestamps to use Minsk timezone for accurate display and input handling --- frontend/src/pages/AlertsPage.jsx | 34 ++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/AlertsPage.jsx b/frontend/src/pages/AlertsPage.jsx index 6eb3d57..725df3f 100644 --- a/frontend/src/pages/AlertsPage.jsx +++ b/frontend/src/pages/AlertsPage.jsx @@ -380,16 +380,16 @@ function AlertCard({ alert, index, onEdit, onDelete }) {
{alert.start_time && (
- Начало: {new Date(alert.start_time).toLocaleString('ru-RU')} + Начало: {new Date(alert.start_time).toLocaleString('ru-RU', { timeZone: MINSK_TIME_ZONE })}
)} {alert.end_time && (
- Конец: {new Date(alert.end_time).toLocaleString('ru-RU')} + Конец: {new Date(alert.end_time).toLocaleString('ru-RU', { timeZone: MINSK_TIME_ZONE })}
)} {status === 'scheduled' &&
Push будет отправлен в момент начала
} - {alert.notification_sent_at &&
Push отправлен: {new Date(alert.notification_sent_at).toLocaleString('ru-RU')}
} + {alert.notification_sent_at &&
Push отправлен: {new Date(alert.notification_sent_at).toLocaleString('ru-RU', { timeZone: MINSK_TIME_ZONE })}
}
@@ -416,11 +416,30 @@ function createAlertIdempotencyKey() { return `alert-${Date.now()}-${Math.random().toString(36).slice(2)}`; } +const MINSK_TIME_ZONE = 'Europe/Minsk'; +const MINSK_OFFSET = '+03:00'; + function toDateTimeInput(value = new Date()) { const date = new Date(value); if (Number.isNaN(date.getTime())) return ''; - const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000); - return local.toISOString().slice(0, 16); + const parts = new Intl.DateTimeFormat('en-CA', { + timeZone: MINSK_TIME_ZONE, + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', hourCycle: 'h23' + }).formatToParts(date).reduce((result, part) => ({ ...result, [part.type]: part.value }), {}); + return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`; +} + +// PostgreSQL currently stores alert timestamps without a timezone. Keep UTC +// values there, while the operator always sees and enters Polotsk time. +function toApiDateTime(value) { + if (!value) return null; + const date = new Date(`${value}:00${MINSK_OFFSET}`); + return Number.isNaN(date.getTime()) ? value : date.toISOString().slice(0, 19); +} + +function toMinskDate(value) { + return new Date(`${value}:00${MINSK_OFFSET}`); } function AlertForm({ alert, routes, templates, onSubmit, onCancel }) { @@ -443,7 +462,8 @@ function AlertForm({ alert, routes, templates, onSubmit, onCancel }) { const data = { ...formData, route_id: formData.route_id || null, - end_time: formData.end_time || null + start_time: toApiDateTime(formData.start_time), + end_time: toApiDateTime(formData.end_time) }; setSubmitting(true); @@ -472,7 +492,7 @@ function AlertForm({ alert, routes, templates, onSubmit, onCancel }) { })); }; - const startDate = new Date(formData.start_time); + const startDate = toMinskDate(formData.start_time); const isScheduled = formData.is_active && !Number.isNaN(startDate.getTime()) && startDate > new Date(); const saveLabel = alert ? 'Сохранить' : !formData.is_active ? 'Сохранить черновик' : isScheduled ? 'Запланировать' : 'Опубликовать';