Работа с датами и временем является неотъемлемой частью многих автоматизированных процессов, создаваемых с помощью Google Apps Script (GAS). Часто данные о датах поступают из внешних источников (API, веб-формы, таблицы Google Sheets) в виде строк. Для выполнения операций сравнения, сортировки, вычислений временных интервалов или форматирования эти строки необходимо преобразовать в нативные объекты Date
.
Почему необходимо преобразовывать строки в даты?
Строковое представление даты не позволяет выполнять арифметические операции или сравнения. Например, строка «01.12.2023» будет лексикографически больше, чем «10.11.2023», что некорректно при сравнении дат. Преобразование в объект Date
предоставляет доступ к методам для работы с компонентами даты (год, месяц, день) и выполнения корректных операций.
Обзор основных методов работы с датами в Google Apps Script
Google Apps Script базируется на JavaScript и предоставляет как стандартные возможности JavaScript для работы с датами (объект Date
), так и специфичные методы сервиса Utilities
. Основными инструментами для преобразования строк в даты являются конструктор new Date()
и метод Utilities.parseDate()
.
Основные методы преобразования строк в дату
Выбор метода зависит от формата входной строки и требуемой точности обработки часовых поясов.
Использование Utilities.parseDate()
Метод Utilities.parseDate(dateString, timeZone, format)
является предпочтительным для предсказуемого парсинга строк, формат которых известен заранее. Он позволяет явно указать формат и часовой пояс.
/**
* Преобразует строку в объект Date с использованием Utilities.parseDate.
*
* @param {string} dateString Строка с датой.
* @param {string} format Формат даты (например, 'yyyy-MM-dd HH:mm:ss').
* @param {string} timeZone Часовой пояс (например, 'Europe/Moscow' или 'GMT').
* @return {Date | null} Объект Date или null в случае ошибки.
*/
function convertStringToDateUtilities(dateString: string, format: string, timeZone: string): Date | null {
try {
// Устанавливаем локаль 'ru' для корректного разбора месяцев, если они указаны словами
const locale = 'ru';
const parsedDate = Utilities.parseDate(dateString, timeZone, format);
Logger.log(`Строка "${dateString}" успешно преобразована в ${parsedDate}`);
return parsedDate;
} catch (e) {
Logger.log(`Ошибка преобразования строки "${dateString}" с форматом "${format}": ${e}`);
return null;
}
}
// Пример использования
const dateFromUtilities = convertStringToDateUtilities('2023-12-01 15:30:00', 'yyyy-MM-dd HH:mm:ss', 'UTC');
const dateFromUtilitiesRu = convertStringToDateUtilities('05.10.2024', 'dd.MM.yyyy', Session.getScriptTimeZone());
Применение new Date()
для преобразования
Конструктор new Date(dateString)
использует встроенный в JavaScript механизм парсинга. Он хорошо справляется со стандартизированными форматами, такими как ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ
), но может давать непредсказуемые результаты для других форматов, особенно тех, где порядок дня и месяца неоднозначен (например, MM/DD/YYYY
vs DD/MM/YYYY
).
/**
* Преобразует строку в объект Date с использованием конструктора new Date().
*
* @param {string} dateString Строка с датой (предпочтительно в формате ISO 8601).
* @return {Date | null} Объект Date или null, если строка не может быть разобрана.
*/
function convertStringToDateConstructor(dateString: string): Date | null {
const date = new Date(dateString);
// Проверяем, является ли результат валидной датой
if (isNaN(date.getTime())) {
Logger.log(`Ошибка преобразования строки "${dateString}" конструктором new Date().`);
return null;
}
Logger.log(`Строка "${dateString}" успешно преобразована в ${date}`);
return date;
}
// Пример использования
const dateIso = convertStringToDateConstructor('2023-12-01T15:30:00Z'); // ISO 8601 UTC
const dateSimple = convertStringToDateConstructor('2023-12-01'); // Может интерпретироваться по-разному
const dateInvalid = convertStringToDateConstructor('01-12-2023'); // Нестандартный формат, скорее всего, вернет Invalid Date
Разбор строки с помощью регулярных выражений и ручное создание объекта Date
Если формат даты нестандартный или не поддерживается Utilities.parseDate()
, можно использовать регулярные выражения для извлечения компонентов даты (год, месяц, день, часы, минуты, секунды) и затем создать объект Date
вручную.
/**
* Преобразует строку нестандартного формата в объект Date с помощью RegExp.
*
* @param {string} dateString Строка с датой, например, "Дата: 01/12/2023 Время: 10:05".
* @param {string} timeZone Часовой пояс для создаваемого объекта Date.
* @return {Date | null} Объект Date или null при ошибке.
*/
function convertStringManually(dateString: string, timeZone: string): Date | null {
// Пример регулярного выражения для формата "Дата: ДД/ММ/ГГГГ Время: ЧЧ:ММ"
const regex = /Дата: (\d{2})\/(\d{2})\/(\d{4}) Время: (\d{2}):(\d{2})/;
const match = dateString.match(regex);
if (match) {
try {
// ВАЖНО: Месяцы в JavaScript Date начинаются с 0 (Январь = 0)
const day = parseInt(match[1], 10);
const month = parseInt(match[2], 10) - 1;
const year = parseInt(match[3], 10);
const hours = parseInt(match[4], 10);
const minutes = parseInt(match[5], 10);
// Создаем дату в UTC, чтобы избежать смещений часового пояса при создании
const utcDate = new Date(Date.UTC(year, month, day, hours, minutes));
// Если нужно представить дату в локальном времени скрипта или указанном поясе,
// лучше форматировать ее с указанием этого пояса, а не модифицировать объект Date
Logger.log(`Строка "${dateString}" разобрана: ${utcDate.toISOString()}`);
// Для работы с датой в конкретном поясе используйте Utilities.formatDate
Logger.log(`Представление в ${timeZone}: ${Utilities.formatDate(utcDate, timeZone, 'yyyy-MM-dd HH:mm:ss Z')}`);
return utcDate; // Возвращаем объект Date (в UTC)
} catch (e) {
Logger.log(`Ошибка при создании Date из компонентов для строки "${dateString}": ${e}`);
return null;
}
} else {
Logger.log(`Строка "${dateString}" не соответствует ожидаемому формату.`);
return null;
}
}
// Пример использования
const manualDate = convertStringManually("Дата: 01/12/2023 Время: 10:05", Session.getScriptTimeZone());
Работа с различными форматами дат
Рассмотрим преобразование наиболее распространенных форматов.
Преобразование дат в формате ‘ГГГГ-ММ-ДД’
Этот формат (ISO 8601) хорошо понимается как new Date()
, так и Utilities.parseDate()
.
const dateStringIso = '2024-03-15';
const date1 = new Date(dateStringIso); // Обычно работает корректно, интерпретируя как UTC полночь
const date2 = Utilities.parseDate(dateStringIso, 'UTC', 'yyyy-MM-dd'); // Явное указание UTC
Logger.log(`new Date(): ${date1}, Utilities.parseDate(): ${date2}`);
Преобразование дат в формате ‘ДД.ММ.ГГГГ’
Этот формат не является стандартным для new Date()
. Следует использовать Utilities.parseDate()
.
const dateStringRu = '15.03.2024';
const timeZone = Session.getScriptTimeZone();
const format = 'dd.MM.yyyy';
const dateRu = convertStringToDateUtilities(dateStringRu, format, timeZone); // Используем функцию из примера выше
// const dateRuInvalid = new Date(dateStringRu); // Скорее всего, вернет Invalid Date
Logger.log(`Дата из '${dateStringRu}': ${dateRu}`);
Обработка дат с указанием времени (например, ‘ДД.ММ.ГГГГ ЧЧ:ММ:СС’)
Аналогично предыдущему пункту, лучше всего использовать Utilities.parseDate()
.
const dateTimeStringRu = '15.03.2024 18:45:00';
const timeZone = 'Europe/Moscow';
const formatDateTime = 'dd.MM.yyyy HH:mm:ss';
const dateTimeRu = convertStringToDateUtilities(dateTimeStringRu, formatDateTime, timeZone);
Logger.log(`Дата и время из '${dateTimeStringRu}': ${dateTimeRu}`);
Преобразование дат из Google Sheets (числовой формат)
Google Sheets хранит даты как количество дней, прошедших с 30 декабря 1899 года (иногда с 31 декабря 1899). Время представляется дробной частью. GAS получает эти значения как числа при чтении ячейки с датой (getValue()
).
/**
* Преобразует числовое представление даты из Google Sheets в объект Date.
*
* @param {number} sheetDateValue Числовое значение из ячейки Google Sheet.
* @return {Date} Объект Date.
*/
function convertSheetDateToDate(sheetDateValue: number): Date {
// Базовая дата для пересчета - 30 декабря 1899 г. (соответствует Excel/Sheets)
// 25569 - это количество дней между 1970-01-01 (эпоха Unix) и 1899-12-30.
const millisecondsPerDay = 24 * 60 * 60 * 1000;
const jsDateMilliseconds = (sheetDateValue - 25569) * millisecondsPerDay;
return new Date(jsDateMilliseconds);
}
// Пример: Предположим, из ячейки прочитано значение 45309.5 (соответствует 15.03.2024 12:00:00)
const sheetValue = 45309.5;
const dateFromSheet = convertSheetDateToDate(sheetValue);
Logger.log(`Число ${sheetValue} из Sheets преобразовано в ${dateFromSheet}`);
// Форматирование для проверки
Logger.log(`Отформатированная дата: ${Utilities.formatDate(dateFromSheet, Session.getScriptTimeZone(), 'dd.MM.yyyy HH:mm:ss')}`);
Обработка ошибок и исключений при преобразовании
Преобразование строк в даты может завершиться неудачей, если строка имеет неверный формат или содержит некорректные значения (например, 32-е число месяца).
Проверка валидности входной строки перед преобразованием
Перед вызовом парсинга можно выполнить предварительную проверку формата с помощью регулярных выражений. Это не гарантирует корректность самой даты (например, 31 февраля), но отсеивает заведомо неверные строки.
/**
* Проверяет, соответствует ли строка базовому формату ДД.ММ.ГГГГ.
*
* @param {string} dateString Входная строка.
* @return {boolean} true, если формат соответствует, иначе false.
*/
function isValidDateFormat(dateString: string): boolean {
const regex = /^\d{2}\.\d{2}\.\d{4}$/;
return regex.test(dateString);
}
Logger.log(`'15.03.2024' валидный формат? ${isValidDateFormat('15.03.2024')}`); // true
Logger.log(`'15/03/2024' валидный формат? ${isValidDateFormat('15/03/2024')}`); // false
Использование try-catch блоков для обработки неверных форнатов
Методы Utilities.parseDate()
и new Date()
(при некорректном вводе) могут генерировать ошибки. Оборачивание вызовов этих методов в блок try...catch
позволяет перехватить исключения и обработать их, избегая аварийного завершения скрипта.
function safeParseDate(dateString: string, format: string, timeZone: string): Date | null {
if (!isValidDateFormat(dateString)) { // Пример предварительной проверки
Logger.log(`Предварительная проверка формата для "${dateString}" не пройдена.`);
return null;
}
try {
return Utilities.parseDate(dateString, timeZone, format);
} catch (error) {
Logger.log(`Ошибка парсинга строки "${dateString}" с форматом "${format}": ${error}`);
return null;
}
}
const result1 = safeParseDate('30.02.2024', 'dd.MM.yyyy', 'UTC'); // Ошибка парсинга
const result2 = safeParseDate('29.02.2024', 'dd.MM.yyyy', 'UTC'); // Успешно
Logger.log(result1); // null
Logger.log(result2); // Объект Date
Возвращение null или ошибки при невозможности преобразования
В случае ошибки парсинга функция должна возвращать предсказуемый результат. Обычно это null
, что позволяет вызывающему коду проверить успешность операции. Альтернативно, можно выбросить собственное исключение (throw new Error(...)
), если ошибка считается критической для дальнейшего выполнения.
Примеры практического использования
Преобразование даты из ячейки Google Sheets
Часто требуется обработать даты, введенные пользователями в таблицу.
/**
* Читает значение из ячейки, пытается преобразовать его в Date.
* Обрабатывает как строковые, так и числовые форматы дат Sheets.
*
* @param {GoogleAppsScript.Spreadsheet.Range} cell Ячейка для чтения.
* @return {Date | null} Объект Date или null, если преобразование невозможно.
*/
function getDateFromCell(cell: GoogleAppsScript.Spreadsheet.Range): Date | null {
const value = cell.getValue();
const timeZone = Session.getScriptTimeZone();
if (value instanceof Date) {
// Если getValue() уже вернуло Date (например, для ячеек с форматом даты)
return value;
} else if (typeof value === 'number' && value > 0) {
// Если это числовое представление даты Sheets
try {
return convertSheetDateToDate(value); // Используем функцию выше
} catch (e) {
Logger.log(`Ошибка преобразования числового значения ${value} из ячейки ${cell.getA1Notation()}: ${e}`);
return null;
}
} else if (typeof value === 'string' && value.trim() !== '') {
// Попытка разобрать строку (пример для ДД.ММ.ГГГГ)
let parsedDate = safeParseDate(value, 'dd.MM.yyyy', timeZone);
if (!parsedDate) {
// Попытка с другим форматом, если первый не удался
parsedDate = safeParseDate(value, 'yyyy-MM-dd', timeZone);
}
if (!parsedDate) {
// Можно попробовать new Date как последнюю попытку для ISO форматов
parsedDate = convertStringToDateConstructor(value);
}
if (!parsedDate) {
Logger.log(`Не удалось преобразовать строку "${value}" из ячейки ${cell.getA1Notation()} в дату.`);
}
return parsedDate;
} else {
Logger.log(`Ячейка ${cell.getA1Notation()} не содержит распознаваемой даты.`);
return null;
}
}
// Пример использования:
// const ss = SpreadsheetApp.getActiveSpreadsheet();
// const sheet = ss.getActiveSheet();
// const dateCell = sheet.getRange('A1');
// const processedDate = getDateFromCell(dateCell);
// if (processedDate) {
// Logger.log(`Дата из ячейки A1: ${processedDate}`);
// } else {
// Logger.log('Не удалось получить дату из ячейки A1.');
// }
Преобразование даты, полученной из API
API часто возвращают даты в формате ISO 8601, который хорошо обрабатывается new Date()
.
/**
* Пример обработки ответа API с датой в формате ISO 8601.
*
* @param {string} apiResponseString JSON-строка от API.
* @return {Date | null} Объект Date или null.
*/
function getDateFromApiResponse(apiResponseString: string): Date | null {
try {
const response = JSON.parse(apiResponseString);
// Предположим, дата находится в поле 'createdAt'
const dateString = response.createdAt;
if (typeof dateString === 'string') {
return convertStringToDateConstructor(dateString); // Используем new Date() для ISO 8601
} else {
Logger.log('Поле с датой в ответе API отсутствует или имеет неверный тип.');
return null;
}
} catch (e) {
Logger.log(`Ошибка разбора ответа API или преобразования даты: ${e}`);
return null;
}
}
// Пример использования:
const jsonResponse = '{"id": 123, "user": "test", "createdAt": "2024-03-15T10:30:00Z"}';
const apiDate = getDateFromApiResponse(jsonResponse);
if (apiDate) {
Logger.log(`Дата из API: ${apiDate}`);
}
Форматирование даты после преобразования для отображения в нужном формате
После успешного преобразования строки в объект Date
, его можно отформатировать для вывода в удобном виде с помощью Utilities.formatDate()
.
/**
* Преобразует строку в дату и форматирует ее.
*
* @param {string} inputString Входная строка с датой.
* @param {string} inputFormat Формат входной строки.
* @param {string} outputFormat Требуемый формат выходной строки.
* @param {string} timeZone Часовой пояс.
* @return {string | null} Отформатированная строка с датой или null.
*/
function parseAndFormatDate(inputString: string, inputFormat: string, outputFormat: string, timeZone: string): string | null {
const dateObject = convertStringToDateUtilities(inputString, inputFormat, timeZone);
if (dateObject) {
try {
return Utilities.formatDate(dateObject, timeZone, outputFormat);
} catch (e) {
Logger.log(`Ошибка форматирования даты ${dateObject}: ${e}`);
return null;
}
} else {
return null;
}
}
// Пример:
const input = '2024/03/15 09:00:00';
const inputFmt = 'yyyy/MM/dd HH:mm:ss';
const outputFmt = 'dd MMMM yyyy\'г.\', HH:mm (zz)';
const tz = 'Asia/Yekaterinburg';
const formattedDate = parseAndFormatDate(input, inputFmt, outputFmt, tz);
if (formattedDate) {
Logger.log(`Результат форматирования: ${formattedDate}`); // Пример: "15 марта 2024г., 09:00 (YEKT)"
}