Британское летнее время с jQuery Datepicker (расчет потерян за 1 день) - PullRequest
1 голос
/ 14 октября 2019

Я использую Vue.js с jQuery Datepicker и вижу проблему с подсчетом, сколько дней осталось до выбранной даты: enter image description here

Расчетразница в днях:

getDifferenceInDays: function(date1, date2) {
  const diffTime = date2.getTime() - date1.getTime(); //Math.abs(...);
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  return diffDays;
},

В результате я не вижу оставшихся 13 дней. Возможно, проблема связана с британским летним временем, которое заканчивается 27 октября: British Summer Time ends on 27th October

Не могли бы вы подсказать, как решить эту проблему. Большое спасибо!

1 Ответ

2 голосов
/ 14 октября 2019

Вы правы, это связано с переходом на летнее время (DST), в частности с окончанием перехода на летнее время в Великобритании 27 октября.

Даты, время и часовые пояса действительно сложно в JavaScript. Нативный объект Date очень неудобен, когда мы обращаемся к разным часовым поясам.

Я бы предложил проверить библиотеку, которая лучше подходит для такого рода вещей: moment и moment-timezone очень хорошо справляются с корректной обработкой изменений летнего времени.

Причина, по которой вы получаете неправильный результат, заключается в том, что вы смотрите на разницу между датами в часовом поясе UTC (поскольку getTime () всегда возвращает количество миллисекунд с 1970-01-01 00:00 UTC (эпоха Unix)).

Результат (в вашем случае) будет 13.04 (или 13 + 1/24) дней, так как смещение UTC изменяется между двумя датами (с UTC + 01 до UTC + 00).

Например, 12:00 по лондонскому времени 14 октября - 11:00 UTC, а 12:00 по лондонскому времени - 12:00 UTC. Таким образом, разница составит 313 часов или 13,04 дня.

Поскольку вы звоните в Math.ceil, это число округляется до 14, что неверно.

Это работает, когда обе датынаходятся в пределах одного и того же смещения UTC (например, оба летом, оба зимой). Но тогда они являются одной из сторон изменения летнего времени, вы получите неверный результат.

Приведенный ниже код будет вычислять дни между двумя датами в указанном часовом поясе . Вы можете передать третий аргумент true в moment.diff, чтобы получить дробные дни. (В данном случае это будет 0).

Если вы не хотите использовать другую библиотеку, такую ​​как момент, вы можете просто использовать Math.round, чтобы получить приблизительную разницу в днях. Это не будет таким же надежным, поскольку, если дата2 опережает дату менее чем на 12 часов, вы получите разницу в ноль дней. Пока вы знаете об этом, это может быть достаточно для ваших нужд. Я добавил getDifferenceInDaysRounding, чтобы показать это.

const CURRENT_TIMEZONE = "Europe/London";

// Date 1 is 12:00, 14th October, London time
const date1 = moment.tz("2019-10-14T12:00:00", CURRENT_TIMEZONE).toDate();

// Date 2 is 12:00, 27th October, London time
const date2 = moment.tz("2019-10-27T12:00:00", CURRENT_TIMEZONE).toDate();

function getDifferenceInDaysOriginal (date1, date2) {
  const diffTime = date2.getTime() - date1.getTime();
  let diff1 = diffTime / (1000 * 60 * 60 * 24);
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  return diffDays;
}

// Use with caution.. this will not work in all cases!
function getDifferenceInDaysRounding (date1, date2) {
  const diffTime = date2.getTime() - date1.getTime();
  const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));
  return diffDays;
}

function getDifferenceInDaysTimezoneAware(date1, date2, timezone) {
  // Ensure the dates are in the correct timezone.
  const dateLocal1 = moment.tz(date1.getTime(), timezone);
  const dateLocal2 = moment.tz(date2.getTime(), timezone);

  // Pass false to return the integer number of days. NB, this will be 0 for less than 24 hours difference. Pass true to get the fractional days.
  return dateLocal2.diff(dateLocal1, "days", false);
}

console.log("Original result:", getDifferenceInDaysOriginal(date1, date2));
console.log("Get difference (rounding):", getDifferenceInDaysRounding(date1, date2));
console.log("Timezone-aware result:", getDifferenceInDaysTimezoneAware(date1, date2, CURRENT_TIMEZONE));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://momentjs.com/downloads/moment-timezone-with-data-1970-2030.js"></script>
...