fix: даты «только дата» больше не сдвигаются на день назад
Tests / frontend-test (push) Successful in 2m1s
Tests / frontend-test (push) Successful in 2m1s
SharePoint хранит поле «только дата» как полночь по поясу сайта (UTC+5) в UTC: «05.10» приходит из REST как 2026-10-04T19:00:00Z. Формы брали первые 10 символов и показывали 04.10, а каждое пересохранение (полное право, исполнитель, повторная отправка) записывало дату ещё на день раньше. Чтение всех дат идёт через spDateOnlyToIso (округление до ближайшей полуночи UTC), «сегодня» — через todayIsoDate вместо toISOString, который до 05:00 давал вчерашнее число. Уже сдвинутые записи в SP не исправляются. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { spDateOnlyToIso } from './dateUtils';
|
||||
|
||||
// SharePoint хранит поле «только дата» как полночь по поясу САЙТА, переведённую
|
||||
// в UTC, — поэтому «5 октября» приходит из REST как предыдущий день по UTC.
|
||||
describe('spDateOnlyToIso', () => {
|
||||
it('сайт в UTC+5 (Астана с 2024): 04T19:00Z → 5-е', () => {
|
||||
expect(spDateOnlyToIso('2026-10-04T19:00:00Z')).toBe('2026-10-05');
|
||||
});
|
||||
|
||||
it('сайт в UTC+6 (старый пояс Астаны): 04T18:00Z → 5-е', () => {
|
||||
expect(spDateOnlyToIso('2026-10-04T18:00:00Z')).toBe('2026-10-05');
|
||||
});
|
||||
|
||||
it('сайт в UTC: 05T00:00Z → 5-е', () => {
|
||||
expect(spDateOnlyToIso('2026-10-05T00:00:00Z')).toBe('2026-10-05');
|
||||
});
|
||||
|
||||
it('переход через месяц и год', () => {
|
||||
expect(spDateOnlyToIso('2026-09-30T19:00:00Z')).toBe('2026-10-01');
|
||||
expect(spDateOnlyToIso('2026-12-31T19:00:00Z')).toBe('2027-01-01');
|
||||
});
|
||||
|
||||
it('високосный год: 28 февраля → 29-е', () => {
|
||||
expect(spDateOnlyToIso('2028-02-28T19:00:00Z')).toBe('2028-02-29');
|
||||
});
|
||||
|
||||
it('строку без времени возвращает как есть', () => {
|
||||
expect(spDateOnlyToIso('2026-10-05')).toBe('2026-10-05');
|
||||
});
|
||||
|
||||
it('пустое/отсутствующее значение → пустая строка', () => {
|
||||
expect(spDateOnlyToIso('')).toBe('');
|
||||
expect(spDateOnlyToIso(null)).toBe('');
|
||||
expect(spDateOnlyToIso(undefined)).toBe('');
|
||||
});
|
||||
|
||||
it('повторное сохранение не сдвигает дату (запись → чтение → запись)', () => {
|
||||
// Имитация: SP вернул 04T19:00Z, мы прочитали, сохранили обратно «как есть»,
|
||||
// SP снова хранит 04T19:00Z — дата обязана остаться 5-м, а не уехать на 4-е.
|
||||
const firstRead = spDateOnlyToIso('2026-10-04T19:00:00Z');
|
||||
expect(firstRead).toBe('2026-10-05');
|
||||
const secondRead = spDateOnlyToIso(`${addDays(firstRead, -1)}T19:00:00Z`);
|
||||
expect(secondRead).toBe(firstRead);
|
||||
});
|
||||
});
|
||||
|
||||
function addDays(isoDate: string, days: number): string {
|
||||
const d = new Date(`${isoDate}T00:00:00Z`);
|
||||
d.setUTCDate(d.getUTCDate() + days);
|
||||
return d.toISOString().slice(0, 10);
|
||||
}
|
||||
@@ -25,6 +25,30 @@ export function isoDatePlusMonths(months: number): string {
|
||||
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
||||
}
|
||||
|
||||
/** Сегодня по МЕСТНОМУ времени в формате YYYY-MM-DD (toISOString дал бы дату по UTC — до 05:00 вчерашнюю). */
|
||||
export function todayIsoDate(): string {
|
||||
return isoDatePlusDays(0);
|
||||
}
|
||||
|
||||
const HALF_DAY_MS = 12 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Значение поля SharePoint «только дата» → YYYY-MM-DD для input type=date.
|
||||
*
|
||||
* REST отдаёт такие поля в UTC: «5 октября» на сайте с поясом UTC+5 приходит
|
||||
* как «2026-10-04T19:00:00Z», и `slice(0, 10)` давал 4-е число (а каждое
|
||||
* пересохранение сдвигало дату ещё на день назад). Округляем до ближайшей
|
||||
* полуночи UTC (+12 ч) — так верно для любого пояса сайта в пределах ±11 ч и
|
||||
* не зависит от пояса браузера.
|
||||
*/
|
||||
export function spDateOnlyToIso(value: unknown): string {
|
||||
if (typeof value !== 'string' || value === '') return '';
|
||||
if (value.indexOf('T') < 0) return value.slice(0, 10);
|
||||
const ms = Date.parse(value);
|
||||
if (isNaN(ms)) return value.slice(0, 10);
|
||||
return new Date(ms + HALF_DAY_MS).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
/** YYYY-MM-DD → дд.мм.гггг, для текста ошибок валидации. Невалидную строку возвращает как есть. */
|
||||
export function formatIsoDate(iso: string): string {
|
||||
const [y, m, d] = iso.split('-');
|
||||
|
||||
Reference in New Issue
Block a user