new Date () ведет себя непоследовательно в JavaScript - PullRequest
1 голос
/ 06 октября 2019

У меня есть элемент управления JavaScript, состоящий из 3 раскрывающихся списков для сбора даты рождения от пользователей (не спрашивайте меня, почему мы не используем средство выбора даты) Выпадающий список для дня, месяца и года. Когда во всех 3 выпадающих списках выбрано значение, я собираю значения и создаю дату:

  var d = new Date(Date.UTC(this.selectedYear, this.selectedMonth - 1, this.selectedDay));

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

Очевидная проблема с этими выпадающими списками состоит в том, что пользователь может выбрать недопустимую дату. Ничто не мешает им выбрать 31 день и затем выбрать февраль. Это приведет к смещению даты на несколько дней, то есть значения 1999, 2, 31 дадут мне 3 марта 1999 года. Итак, у меня есть проверка:

if (d.getDate() != this.selectedDay) {
                d = new Date(Date.UTC(this.selectedYear, this.selectedMonth - 1, 0));
this.selectedDay = d.getDate();

Еслидень месяца отличается от моего первоначального значения, это означает, что дата сместилась, поэтому я изменяю дату на последний день прошлого месяца (следовательно, 0 для дня), а затем я обновляю значение в раскрывающемся списке «дней».

Странно то, что я не могу этого сделать.selectedMonth - 1 в этом втором преобразовании. Это вернет меня обратно к 31 января. Поэтому я должен сделать следующее:

 var d = new Date(Date.UTC(this.selectedYear, this.selectedMonth - 1, this.selectedDay));
            if (d.getDate() != this.selectedDay) {
                d = new Date(Date.UTC(this.selectedYear, this.selectedMonth, 0));
                this.selectedDay = d.getDate();
            }

Это работает, но это просто ошеломляет меня. Почему месяцы ведут себя по-разному, если я использую 0 в качестве значения для дней вместо действительного дня месяца? Зачем кому-то реализовывать это так? У кого-нибудь есть логическое объяснение?

Ответы [ 3 ]

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

На самом деле, это не поле month, которое отличается от других: это только часть day, которая индексируется 1, как вы можете найти в документах на Date:

При задании не менее года и месяца эта форма Date () возвращает объект Date, значения компонентов которого (год, месяц, день, час, минуты, секунды и миллисекунды)все исходят из следующих параметров. Любым пропущенным полям присваивается минимально возможное значение (1 для дня и 0 для каждого другого компонента).

(выделено)


Неправильное предположение, которое вы сделали, состоит в том, что использование 0 для части day будет представлять последний день месяца . У JS Date s!

нет таких причуд, выбирая день 0 вместо дня 1, просто смещает Date, выбранный вами один день. Всего за один полный день до первого дня месяца ...
Это будет последний день предыдущего месяца.


Если месяц, который вы хотите выбрать, равен this.selectedMonth - 1, егопоследний день получается следующим образом:

  • выберите следующий месяц - это + 1, поэтому: (this.selectedMonth - 1) + 1
  • вернитесь на один день раньше первого - это день 0, за день до дня 1.

Это именно то, что вы делаете, когда уходите:

//                ┌───── year ────┐  ┌───── month ────┐ day
new Date(Date.UTC(this.selectedYear, this.selectedMonth, 0))
//                                   │                │  │
//             ┌─────────────────────┘                │  │
//             the month after (this.selectedMonth - 1)  │
//                                                       │
//                        ┌──────────────────────────────┤
//                        the day before the first day (1)

Иногда даже картинка в формате ASCII стоит большетысячи слов ?.

0 голосов
/ 07 октября 2019

Как уже отмечали другие, месяцы индексируются нулем, поэтому вам необходимо вычесть 1 из номера календарного месяца. Вы можете упростить жизнь, установив значения параметров месяца в индекс месяца (от 0 до 11), а не номер календарного месяца (от 1 до 12). Отображаемый текст может быть номером или именем календарного месяца.

Чтобы проверить введенные значения даты, вы можете просто проверить, что месяц не изменился при создании даты.

Я могу 'Чтобы понять, почему вы используете Date.UTC, он изменит дату для пользователей к востоку от Гринвича, как и для любого с положительным смещением часового пояса (или с отрицательным смещением ECMAScript). 2019-10-01 UTC - 2019-09-30 по местному времени.

let i;
let ySel = document.getElementById('inYear');
for (i=0; i<10; i++) {
  ySel.appendChild(new Option(2010+i, 2010+i));
}

let mSel = document.getElementById('inMonth');
'January February March April May June July August September October November December'
  .split(' ')
  .forEach((m, i) => mSel.appendChild(new Option(m, i)));

let dSel = document.getElementById('inDay');
for (i=1; i<32; i++) {
  dSel.appendChild(new Option(i, i));
}
  
[ySel, mSel, dSel].forEach(el => el.addEventListener('change', validateDate, false));
  
function validateDate(){
  let y = document.getElementById('inYear').value;
  let m = document.getElementById('inMonth').value;
  let d = document.getElementById('inDay').value;
  let date = new Date(y, m, d); // m is already zero indexed
  let errSpan = document.getElementById('dErr');
  if (date.getMonth() != m) {
    errSpan.textContent = 'Invalid date';
  } else {
    errSpan.textContent = date.toString();
  }
}
Year: <select id="inYear"></select>
Month: <select id="inMonth"></select>
Day: <select id="inDay"></select><span id="dErr"></span>
0 голосов
/ 07 октября 2019

Я бы посоветовал сначала ввести год и ввести его в обязательном порядке, после того, как он установит значение для средства выбора месяца, а затем создать новый объект даты, из которого будет отображаться средство выбора дня

...