From ca030b46dc75d8843dc60828fa1d2cb23078a750 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 10 Jan 2025 13:52:53 -0700 Subject: [PATCH] Recompute timezone adjustments for a given date. Different dates can have different timezone adjustments in the case that the client timezone and server timezone are different and one of those time zones adheres to daylight savings time and the other does not. For example if the client timezone is America/Phoenix and the server timezone is America/Chicago. The America/Phoenix timezone does not adhere to daylight savings time and is always UTC-7 while America/Chicago is UTC-5 during daylight savings time, but is UTC-6 during standard time. So if an instructor is in the America/Phoenix timezone, but working on a server set for the America/Chicago timezone and selects a date that is during daylight savings time, then the current code use an incorrect differential of 1 hour because it uses the same differential for all dates. So this computes the timezone adjustment for a given date so that the correct timezone differential for that time is used. This fixes issue #2654. --- htdocs/js/DatePicker/datepicker.js | 28 +++++++++++++--------- htdocs/js/ProblemSetList/problemsetlist.js | 25 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/htdocs/js/DatePicker/datepicker.js b/htdocs/js/DatePicker/datepicker.js index e7d01f365a..193ae9b6a1 100644 --- a/htdocs/js/DatePicker/datepicker.js +++ b/htdocs/js/DatePicker/datepicker.js @@ -33,20 +33,25 @@ const reduced_rule = document.getElementById(`${name}.reduced_scoring_date_id`); if (reduced_rule) groupRules.splice(1, 0, [reduced_rule]); - // Compute the time difference between the current browser timezone and the course timezone. + // Compute the time difference between a time in the browser timezone and the same time in the course timezone. // flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone. - // Note that this is in seconds. - const timezoneAdjustment = - new Date(new Date().toLocaleString('en-US')).getTime() - - new Date( - new Date().toLocaleString('en-US', { timeZone: open_rule.dataset.timezone ?? 'America/New_York' }) - ).getTime(); + // Note that the input time is in seconds and output times is in milliseconds. + const timezoneAdjustment = (time) => { + const dateTime = new Date(0); + dateTime.setUTCSeconds(time); + return ( + new Date(dateTime.toLocaleString('en-US')).getTime() - + new Date( + dateTime.toLocaleString('en-US', { timeZone: open_rule.dataset.timezone ?? 'America/New_York' }) + ).getTime() + ); + }; for (const rule of groupRules) { const classValue = document.getElementsByName(`${rule[0].name}.class_value`)[0]?.dataset.classValue; const value = rule[0].value || classValue; - rule.push(value ? parseInt(value) * 1000 - timezoneAdjustment : 0); - if (classValue) rule.push(parseInt(classValue) * 1000 - timezoneAdjustment); + rule.push(value ? parseInt(value) * 1000 - timezoneAdjustment(parseInt(value)) : 0); + if (classValue) rule.push(parseInt(classValue) * 1000 - timezoneAdjustment(parseInt(classValue))); } const update = (input) => { @@ -156,7 +161,8 @@ parseDate(datestr, format) { // Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for // the unix timestamp stored in the hidden input whose value will be sent to the server. - if (format === 'U') return new Date(parseInt(datestr) * 1000 - timezoneAdjustment); + if (format === 'U') + return new Date(parseInt(datestr) * 1000 - timezoneAdjustment(parseInt(datestr))); // Next attempt to parse the datestr with the current format. This should not be adjusted. It is // for display only. @@ -171,7 +177,7 @@ formatDate(date, format) { // In this case the date provided is in the browser's time zone. So it needs to be adjusted to the // timezone of the course. - if (format === 'U') return (date.getTime() + timezoneAdjustment) / 1000; + if (format === 'U') return (date.getTime() + timezoneAdjustment(date.getTime() / 1000)) / 1000; return luxon.DateTime.fromMillis(date.getTime()).toFormat( datetimeFormats[luxon.Settings.defaultLocale] diff --git a/htdocs/js/ProblemSetList/problemsetlist.js b/htdocs/js/ProblemSetList/problemsetlist.js index 8e96c19ddb..765dd79ff9 100644 --- a/htdocs/js/ProblemSetList/problemsetlist.js +++ b/htdocs/js/ProblemSetList/problemsetlist.js @@ -179,17 +179,22 @@ if (importDateShift) { luxon.Settings.defaultLocale = importDateShift.dataset.locale ?? 'en'; - // Compute the time difference between the current browser timezone and the course timezone. + // Compute the time difference between a time in the browser timezone and the same time in the course timezone. // flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone. - // Note that this is in seconds. - const timezoneAdjustment = - new Date(new Date().toLocaleString('en-US')).getTime() - - new Date( - new Date().toLocaleString('en-US', { timeZone: importDateShift.dataset.timezone ?? 'America/New_York' }) - ).getTime(); + // Note that the input time is in seconds and output times is in milliseconds. + const timezoneAdjustment = (time) => { + const dateTime = new Date(0); + dateTime.setUTCSeconds(time); + return ( + new Date(dateTime.toLocaleString('en-US')).getTime() - + new Date( + dateTime.toLocaleString('en-US', { timeZone: open_rule.dataset.timezone ?? 'America/New_York' }) + ).getTime() + ); + }; let fallbackDate = importDateShift.value - ? new Date(parseInt(importDateShift.value) * 1000 - timezoneAdjustment) + ? new Date(parseInt(importDateShift.value) * 1000 - timezoneAdjustment(parseInt(importDateShift.value))) : new Date(); const fp = flatpickr(importDateShift.parentNode, { @@ -248,7 +253,7 @@ parseDate(datestr, format) { // Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for // the unix timestamp stored in the hidden input whose value will be sent to the server. - if (format === 'U') return new Date(parseInt(datestr) * 1000 - timezoneAdjustment); + if (format === 'U') return new Date(parseInt(datestr) * 1000 - timezoneAdjustment(parseInt(datestr))); // Next attempt to parse the datestr with the current format. This should not be adjusted. It is // for display only. @@ -263,7 +268,7 @@ formatDate(date, format) { // In this case the date provided is in the browser's time zone. So it needs to be adjusted to the // timezone of the course. - if (format === 'U') return (date.getTime() + timezoneAdjustment) / 1000; + if (format === 'U') return (date.getTime() + timezoneAdjustment(date.getTime() / 1000)) / 1000; return luxon.DateTime.fromMillis(date.getTime()).toFormat( datetimeFormats[luxon.Settings.defaultLocale]