Неправильный формат даты при чтении события календаря Outlook

Через 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;
}

В каком часовом поясе происходит событие в Outlook? Два часа перерыва — это точно смещение часового пояса от CEST до UTC. Похоже, что клиентская сторона интерпретирует полученную дату как 13:30 и 15:00 в формате UTC, что в точности будет 11:30 и 13:00 CEST соответственно. Как вы читаете событие из API? Можете ли вы отследить этот вызов в сетевом инспекторе и показать необработанные данные, полученные от API? И где/как создается этот информационный объект?

derpirscher 12.06.2024 10:17

@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) Брюссель, Копенгаген , Мадрид, Париж.

Hugo Jiménez 12.06.2024 11:09

@derpirscher Извините, что я указал вам время окончания, а не время начала, это время начала в разделе сетевого инспектора start : {dateTime: "2024-06-12T09:00:00.0000000", timeZone: "UTC"}

Hugo Jiménez 12.06.2024 11:25

Если ваш часовой пояс — центральная Европа, то ваше текущее смещение UTC составляет +2:00 часа. Но да, похоже, что при преобразовании ответа API в объект даты (то есть это значение, которое вы получаете от info.event.start, вы теряете информацию, которая "2024-06-12T09:00:00.0000000" должна быть меткой времени UTC. Т.е. ваш локальный браузер обрабатывает ее так, как это было в местном часовом поясе. , таким образом, разница в 2 часа. Как этот ответ API преобразуется в этот объект event?

derpirscher 12.06.2024 11:50

@derpirscher Я добавил часть своего кода, где получаю события, на случай, если это будет полезно

Hugo Jiménez 12.06.2024 12:16

Ну, как я уже сказал: вы просто берете dateTime: "..." вашего объекта результата и полностью игнорируете timeZone информацию. Таким образом, когда временная строка анализируется с объектом Date, она считается местным временем... Но все же не хватает кое-чего: где преобразовать строку dateTime, которая является строкой, в объект Date? Потому что, когда вы используете его в const startFormatted = start.toLocaleString('es-ES', ...), start уже является объектом Date... Это тот момент, когда вам нужно учитывать часовой пояс из ответа API.

derpirscher 12.06.2024 12:19

@derpirscher единственный раз, когда я справляюсь со временем, - это внутри eventClick: function(info) {

Hugo Jiménez 12.06.2024 12:26

Это кажется маловероятным, поскольку express.json() не преобразует string в Date, но когда вы обращаетесь к event.start в этом обработчике, это уже Date... Возможно, это не ваш код, а преобразование выполняет какая-то библиотека. Но где-то это происходит...

derpirscher 12.06.2024 12:34
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
8
111
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы пренебрегаете информацией о часовом поясе, которую получаете от 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 12.06.2024 15:23

@derpirscher нет, он просто предполагает, что введенное время является местным временем new Date(<string without tz>), поэтому оно начинается неправильно, а затем добавляет то же смещение к дате окончания, которое может находиться на другой стороне изменения летнего времени. API должен возвращать что-то полезное, в идеале что-то вроде -07:00, но не имена IANA (которые нельзя использовать напрямую в JS).

Salman Arshad 12.06.2024 15:54

@SalmanA «API должен возвращать что-то полезное, в идеале что-то вроде -07:00, но не имена IANA», ну, скажите это Microsoft ... С другой стороны, возможно, это недостаток JS, который не может справиться с IANA . Но это философский вопрос...

derpirscher 12.06.2024 16:09

Другие вопросы по теме