Через API я читаю свой календарь Outlook и показываю его в календаре из библиотеки FullCalendar.
У меня проблема возникает при чтении события с продолжительностью, что все события, созданные в Outlook, в момент их отображения в моем веб-календаре вычитаются два часа от начала и конца, прилагаю примерное изображение:
ПРОГНОЗ СОБЫТИЯ
перевод: Ср, 05.06.2024 с 13:30. до 15:00.
СОБЫТИЕ В МОЕМ КАЛЕНДАРЕ
перевод: Начало: 05.06.2024 11:30 | Конец 05.06.2024 13:00.
оба происходят 5-го числа, но исходное событие в Outlook начинается в 13:30. и заканчивается в 15:00. и мой календарь читает его так, как будто он начался в 11:00 и закончился в 13:30 с разницей в два часа.
В своем коде я помещаю сообщение в консоль, чтобы узнать, как пришло время в результате чтения с помощью API, и оно приходит в следующем формате: среда, 5 июня 2024 г., 11:30:00 GMT+0200 (центральноевропейское летнее время).
поэтому я предполагаю, что с точки зрения перспективы он поступает неправильно, но я удивлен, поскольку там, где я живу, GTM правильный.
Это часть, которая получает события:
//EVENTS
async function getCalendarEvents(accessToken) {
const response = await fetch('https://graph.microsoft.com/v1.0/me/events', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Error al obtener los eventos del calendario: ${response.status} ${response.statusText}`, errorText);
throw new Error('Error al obtener los eventos del calendario');
}
const data = await response.json();
return data.value.map(event => ({
title: event.subject,
start: event.start.dateTime,
end: event.end.dateTime,
allDay: event.isAllDay
}));
}
Это часть кода, которая обрабатывает информацию о событии:
function renderCalendar(events) {
const calendarEl = document.getElementById('calendar');
const calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
buttonText: {
today: 'Hoy',
month: 'Mes',
week: 'Semana',
day: 'Día',
list: 'Lista'
},
initialView: 'dayGridMonth',
locale: 'es',
events: events,
eventClick: function(info) {
// Get the event properties
const event = info.event;
const title = event.title;
const start = event.start;
const end = event.end;
const allDay = event.allDay;
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const startFormatted = start.toLocaleString('es-ES', { timeZone: timeZone });
const endFormatted = end ? end.toLocaleString('es-ES', { timeZone: timeZone }) : 'Evento de todo el día';
// Create the event information string
let content = `<h2>${title}</h2>`;
if (!allDay) {
content += `<p><strong>Inicio:</strong> ${startFormatted}</p>`;
content += `<p><strong>Fin:</strong> ${endFormatted}</p>`;
} else {
content += `<p><strong>Evento de todo el día</strong></p>`;
}
// Show SweetAlert2 popup with event information
Swal.fire({
title: 'Información del Evento',
html: content,
icon: 'info'
});
console.info(info.event);
console.info(title);
console.info(start);
console.info(end);
console.info(allDay);
}
});
calendar.render();
}
Большое спасибо!
Обновлено: хотя решение @derpirscher сработало отлично, я добавил еще один способ, который мне порекомендовал коллега, который также работает.
async function getCalendarEvents_Alt(accessToken) {
const response = await fetch('https://graph.microsoft.com/v1.0/me/events', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
const errorText = await response.text();
console.error(`Error al obtener los eventos del calendario: ${response.status} ${response.statusText}`, errorText);
throw new Error('Error al obtener los eventos del calendario');
}
const data = await response.json();
const events = data.value.map(event => {
// Convertir las fechas y horas a objetos de fecha considerando la zona horaria
const start = new Date(event.start.dateTime);
const end = new Date(event.end.dateTime);
// Ajustar la hora para reflejar la zona horaria local
const timeZoneOffset = start.getTimezoneOffset() * 60000; // Convertir minutos a milisegundos
const localStart = new Date(start.getTime() - timeZoneOffset);
const localEnd = new Date(end.getTime() - timeZoneOffset);
return {
title: event.subject,
start: localStart.toISOString(), // Convertir a cadena ISO para asegurar la representación correcta de la fecha y hora
end: localEnd.toISOString(),
allDay: event.isAllDay
};
});
return events;
}
@derpirscher Ответ от сетевого инспектора такой createdDateTime : "2024-06-12T08:41:37.1442878Z" и end : {dateTime: "2024-06-12T09:30:00.0000000", timeZone: "UTC"}, и мой код получает дату как: Wed Jun 12 2024 09:00:00 GMT+0200 (Central European Summer Time ) но событие в моем календаре Outlook начинается в 11:00, формат времени моего календаря: (UTC+01:00) Брюссель, Копенгаген , Мадрид, Париж.
@derpirscher Извините, что я указал вам время окончания, а не время начала, это время начала в разделе сетевого инспектора start : {dateTime: "2024-06-12T09:00:00.0000000", timeZone: "UTC"}
Если ваш часовой пояс — центральная Европа, то ваше текущее смещение UTC составляет +2:00 часа. Но да, похоже, что при преобразовании ответа API в объект даты (то есть это значение, которое вы получаете от info.event.start, вы теряете информацию, которая "2024-06-12T09:00:00.0000000" должна быть меткой времени UTC. Т.е. ваш локальный браузер обрабатывает ее так, как это было в местном часовом поясе. , таким образом, разница в 2 часа. Как этот ответ API преобразуется в этот объект event?
@derpirscher Я добавил часть своего кода, где получаю события, на случай, если это будет полезно
Ну, как я уже сказал: вы просто берете dateTime: "..." вашего объекта результата и полностью игнорируете timeZone информацию. Таким образом, когда временная строка анализируется с объектом Date, она считается местным временем... Но все же не хватает кое-чего: где преобразовать строку dateTime, которая является строкой, в объект Date? Потому что, когда вы используете его в const startFormatted = start.toLocaleString('es-ES', ...), start уже является объектом Date... Это тот момент, когда вам нужно учитывать часовой пояс из ответа API.
@derpirscher единственный раз, когда я справляюсь со временем, - это внутри eventClick: function(info) {
Это кажется маловероятным, поскольку express.json() не преобразует string в Date, но когда вы обращаетесь к event.start в этом обработчике, это уже Date... Возможно, это не ваш код, а преобразование выполняет какая-то библиотека. Но где-то это происходит...



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы пренебрегаете информацией о часовом поясе, которую получаете от API. Ваш ответ содержит временные метки в формате
{
dateTime: "2024-06-12T09:30:00.0000000",
timeZone: "UTC"
}
но в вашем коде при чтении ответа API вы учитываете только
...
start: event.start.dateTime,
end: event.end.dateTime,
...
и игнорировать event.start.timeZone информацию.
Таким образом, где-то в процессе вы, вероятно, делаете что-то вроде
new Date(start)
чтобы преобразовать временную строку в объект Date. Но поскольку временная строка не содержит никакой информации о часовом поясе, считается, что она соответствует местному времени (т.е. в вашем случае CEST, что на 2 часа опережает UTC).
Т.е. "2024-06-12T09:30:00.0000000" в UTC — это тот же момент времени, что и "2024-06-12T11:30:00.0000000" в CEST. Но когда вы делаете new Date("2024-06-12T09:30:00.0000000") на компьютере, работающем в CEST, он интерпретирует эту временную строку в CEST (поскольку у него нет никакой другой информации), что приводит к сдвигу ваших временных меток на 2 часа.
Поэтому при преобразовании строк в Date необходимо учитывать правильный часовой пояс, к которому относятся эти строки. Очень простой подход может заключаться в следующем.
Проверьте, всегда ли API Outlook предоставляет временные метки в качестве часового пояса UTC. Если да, просто добавьте Z к строкам временной метки. Это самый простой маркер для анализа даты: временная метка должна рассматриваться как временная метка UTC. Т.е.
new Date("2024-06-12T09:30:00.0000000") даст вам 9:30 CEST, но new Date("2024-06-12T09:30:00.0000000Z") даст вам 9:30 UTC, что то же самое, что 11:30 CEST. Имейте в виду, что это решение не работает, если возвращаемый timeZone отличается от UTC!
Если вы хотите быть уверенным, что всегда будете иметь правильное время, если API Outlook также возвращает разные часовые пояса, вам нужно каким-то образом поместить эту информацию в метку времени. Глядя на документацию этого API, информация timeZone возвращается как имя Олсона (т.е. что-то вроде Europe/Paris или UTC), поэтому для правильного анализа вам нужна библиотека, которая может правильно обрабатывать эту информацию (встроенный класс Date с этим не очень хорошо, по крайней мере, при разборе). Например, вы можете использовать moment-js (но это считается устаревшим и больше не должно использоваться)
import { moment } from "moment-timezone"
...
start: moment.tz(event.start.dateTime, event.start.timeZone).toISOSTring();
или более современную библиотеку, например luxon
import { DateTime } from "luxon"
...
start: DateTime.fromISO(event.start.dateTime, { zone: event.start.timeZone }).toISO();
В обоих приведенных выше фрагментах start будет присвоена строка, содержащая соответствующую информацию о часовом поясе. То есть либо оставить временную часть как UTC и добавить маркер часового пояса UTC, либо преобразовать время в местное время и добавить соответствующую информацию о смещении. Таким образом, строку впоследствии можно будет правильно проанализировать и получить правильное местное время.
@SalmanA На самом деле это должно работать (но, по общему признанию, довольно необычным образом), потому что оно сдвигает метку времени на величину смещения UTC. Т.е. он меняет что-то вроде 09:00, которое интерпретируется как 09:00 CEST, на 11:00 CEST, а toISOString() затем преобразует его в строку как 09:00Z, что является меткой времени, первоначально отправленной API. Но, конечно, это правильно (так же, как и мой наивный подход выше), если API всегда отправляет метки времени UTC... На всякий случай OP следует рассмотреть возможность использования возвращенного часового пояса при анализе строки...
@derpirscher нет, он просто предполагает, что введенное время является местным временем new Date(<string without tz>), поэтому оно начинается неправильно, а затем добавляет то же смещение к дате окончания, которое может находиться на другой стороне изменения летнего времени. API должен возвращать что-то полезное, в идеале что-то вроде -07:00, но не имена IANA (которые нельзя использовать напрямую в JS).
@SalmanA «API должен возвращать что-то полезное, в идеале что-то вроде -07:00, но не имена IANA», ну, скажите это Microsoft ... С другой стороны, возможно, это недостаток JS, который не может справиться с IANA . Но это философский вопрос...
В каком часовом поясе происходит событие в Outlook? Два часа перерыва — это точно смещение часового пояса от CEST до UTC. Похоже, что клиентская сторона интерпретирует полученную дату как
13:30и15:00в формате UTC, что в точности будет11:30и13:00CEST соответственно. Как вы читаете событие из API? Можете ли вы отследить этот вызов в сетевом инспекторе и показать необработанные данные, полученные от API? И где/как создается этот информационный объект?