DateДаты – это числа (serial):
// Константы
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const SHEETS_EPOCH = Date.UTC(1899, 11, 30); // 1899-12-30 UTC
/**
* Преобразование serial-числа Google Sheets → Date в нужном поясе
* @param {number} serial - число из Google Sheets
* @param {string} timeZone - IANA-часовой пояс, напр. "Europe/Moscow"
* @returns {Date} объект Date, локализованный под timeZone
*/
function sheetsSerialToDate(serial, timeZone) {
const utcMs = SHEETS_EPOCH + serial * MS_PER_DAY;
const dtf = new Intl.DateTimeFormat("en-US", {
timeZone,
hour12: false,
year: "numeric", month: "2-digit", day: "2-digit",
hour: "2-digit", minute: "2-digit", second: "2-digit",
fractionalSecondDigits: 3
});
const parts = dtf.formatToParts(new Date(utcMs));
const get = t => parts.find(p => p.type === t).value;
const iso = `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}.${get("fractionalSecond")}Z`;
return new Date(iso);
}
/**
* Преобразование Date → serial-число Google Sheets
* @param {Date} date - JS Date (UTC внутри)
* @param {string} timeZone - IANA-часовой пояс, напр. "Europe/Moscow"
* @returns {number} serial число (с долями суток)
*/
function dateToSheetsSerial(date, timeZone) {
const dtf = new Intl.DateTimeFormat("en-US", {
timeZone,
hour12: false,
year: "numeric", month: "2-digit", day: "2-digit",
hour: "2-digit", minute: "2-digit", second: "2-digit",
fractionalSecondDigits: 3
});
const parts = dtf.formatToParts(date);
const get = t => parts.find(p => p.type === t).value;
const localIso = `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}.${get("fractionalSecond")}Z`;
const localMs = Date.parse(localIso);
return (localMs - SHEETS_EPOCH) / MS_PER_DAY;
}
✔ Универсально, учитывает все пояса и переходы DST.
✘ Чуть медленнее, т.к. использует Intl.DateTimeFormat.
Если у тебя один постоянный пояс (например, всегда UTC+3), можно обойтись без Intl и работать напрямую со смещением в минутах. Это будет существенно быстрее.
// Константы
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const SHEETS_EPOCH = Date.UTC(1899, 11, 30); // 1899-12-30 UTC
/**
* serial → Date (фиксированный offset)
* @param {number} serial - число из Google Sheets
* @param {number} offsetMinutes - смещение в минутах (например, +180 для UTC+3)
* @returns {Date}
*/
function sheetsSerialToDateFixed(serial, offsetMinutes) {
const utcMs = SHEETS_EPOCH + serial * MS_PER_DAY;
return new Date(utcMs + offsetMinutes * 60 * 1000);
}
/**
* Date → serial (фиксированный offset)
* @param {Date} date - JS Date
* @param {number} offsetMinutes - смещение в минутах (например, +180 для UTC+3)
* @returns {number}
*/
function dateToSheetsSerialFixed(date, offsetMinutes) {
const localMs = date.getTime() - offsetMinutes * 60 * 1000;
return (localMs - SHEETS_EPOCH) / MS_PER_DAY;
}
✔ Очень быстро, т.к. только арифметика. ✘ Не учитывает переходы DST – подходит только для фиксированного смещения.
// Универсальный способ
const serial = 45909.6458333; // пришло из Sheets
const d = sheetsSerialToDate(serial, "Europe/Moscow");
console.log(d.toISOString());
// → 2025-09-23T15:30:00.000Z
const back = dateToSheetsSerial(d, "Europe/Moscow");
console.log(back.toFixed(6));
// → 45909.645833
// Турбо-способ (фиксированный UTC+3)
const d2 = sheetsSerialToDateFixed(serial, 180);
console.log(d2.toISOString());
const back2 = dateToSheetsSerialFixed(d2, 180);
console.log(back2.toFixed(6));
Intl.