Каков источник этой ошибки в дату JavaScript? - PullRequest
1 голос
/ 03 марта 2020

Я пытаюсь написать функцию, которая будет принимать строку типа 07/2020, а затем возвращать, если прошло более трех месяцев.

Я написал функцию isMoreThan3MonthsHence, в которой я достаточно уверен работает правильно:

const isMoreThan3MonthsHence = ({ utcYear, utcMonth }, 
                                now = new Date, 
                                target = new Date(Date.UTC(utcYear, utcMonth)), 
                                threeMonthsAway = new Date(now.valueOf()).setUTCMonth(now.getUTCMonth() + 3)) => 
    (target > threeMonthsAway)


console.log(isMoreThan3MonthsHence({ utcYear: 2020, utcMonth: 7 })) // true (correct!)

Проблема возникает, когда я пытаюсь создать объект Date, чтобы использовать его для заполнения аргументов isMoreThan3MonthsHence.

const validate = (str, 
                  [localMonth, localYear] = str.split('/'), 
                  date = new Date(+localYear, (+localMonth)-1)) => 
    isMoreThan3MonthsHence({ utcYear: date.getUTCFullYear(), utcMonth: date.getUTCMonth() })

// Note: input is one-based months
console.log(validate('07/2020')) // false (but should be true!)

Я думаю, причина в том, что при обновлении Date в validate без указания часового пояса будет использоваться местный часовой пояс, действующий на предоставленную дату, который будет BST (UTC + 1).

Wed Jul 01 2020 00:00:00 GMT+0100 (British Summer Time)

Это время фактически 23:00 июня 30-го в UT C. Таким образом, месяц на самом деле составляет 5 в терминах нуля. Но я не хочу такого поведения. Я хочу, чтобы указание на июль фактически означало июль в UT C.

Как это можно исправить?

Ответы [ 2 ]

2 голосов
/ 03 марта 2020

Похоже, вы смешиваете использование Date.UTC, а не при создании экземпляров дат. Например, если вы используете следующее для своей функции проверки:

const validate = (str, 
                  [month, year] = str.split('/'), 
                  date = new Date(Date.UTC(+year, (+month)-1))) => 
    isMoreThan3MonthsHence({ utcYear: date.getUTCFullYear(), utcMonth: date.getUTCMonth() })

// Note: input is one-based months
console.log(validate('07/2020')) // Now true

Это работает как ожидалось: JSFiddle

Удаление использования Date.UTC в целом будет выполнять расчет в местном часовом поясе пользователя, с учетом любых применимых настроек перехода на летнее время. Этот можно рассматривать как как правильный подход, однако это приведет к описанному вами поведению.


Примечание. Я переименовал префиксные переменные local на основе отзывов Берги. , Использование Date.UTC подразумевает, что вы передаете UT C аргументы.

1 голос
/ 04 марта 2020

Помимо смешивания UT C и локальных дат, добавление 3 месяцев приведет к неправильному ответу на даты, такие как 31 марта, где добавление 3 месяцев просто путем увеличения номера месяца приводит к дате на 1 июля , См. Добавление месяцев к дате в JavaScript.

Так что validate('07,2020') вернет значение false, если выполнить 31 марта.

Чтобы исправить это при добавлении месяцев, убедитесь, что обновленная дата все еще в том же дне месяца, в противном случае она переносится, поэтому установите последний день предыдущего месяца.

function validate(s) {
  let testDate = addMonths(new Date(), 3);
  let [m, y] = s.split(/\D/);
  return testDate < new Date(y, m-1);
};

function addMonths(date, months) {
  let d = date.getDate();
  date.setMonth(date.getMonth() + +months);
  // If rolled over to next month, set to last day of previous month
  if (date.getDate() != d) {
    date.setDate(0);
  }
  return date;
}

// Sample
console.log('On ' + new Date().toDateString() + ':');
['07/2020', '04/2020'].forEach(
  s => console.log(s + ' - ' + validate(s))
);
...