Преглед изворни кода

DatePicker: Daylight Saving Time (#7599)

* date-table: fix daylight saving time highlight

* test: update date-picker range test
Jiewei Qian пре 7 година
родитељ
комит
af46f968ab

+ 6 - 20
packages/date-picker/src/basic/date-table.vue

@@ -30,7 +30,7 @@
 </template>
 
 <script>
-  import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, DAY_DURATION, isDate } from '../util';
+  import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, nextDate, isDate } from '../util';
   import { hasClass } from 'element-ui/src/utils/dom';
   import Locale from 'element-ui/src/mixins/locale';
 
@@ -136,7 +136,7 @@
 
           if (this.showWeekNumber) {
             if (!row[0]) {
-              row[0] = { type: 'week', text: getWeekNumber(new Date(startDate.getTime() + DAY_DURATION * (i * 7 + 1))) };
+              row[0] = { type: 'week', text: getWeekNumber(nextDate(startDate, i * 7 + 1)) };
             }
           }
 
@@ -149,7 +149,7 @@
             cell.type = 'normal';
 
             const index = i * 7 + j;
-            const time = startDate.getTime() + DAY_DURATION * (index - offset);
+            const time = nextDate(startDate, index - offset).getTime();
             cell.inRange = time >= clearHours(this.minDate) && time <= clearHours(this.maxDate);
             cell.start = this.minDate && time === clearHours(this.minDate);
             cell.end = this.maxDate && time === clearHours(this.maxDate);
@@ -289,22 +289,8 @@
       },
 
       getDateOfCell(row, column) {
-        const startDate = this.startDate;
-
-        return new Date(startDate.getTime() + (row * 7 + (column - (this.showWeekNumber ? 1 : 0)) - this.offsetDay) * DAY_DURATION);
-      },
-
-      getCellByDate(date) {
-        const startDate = this.startDate;
-        const rows = this.rows;
-        const index = (date - startDate) / DAY_DURATION;
-        const row = rows[Math.floor(index / 7)];
-
-        if (this.showWeekNumber) {
-          return row[index % 7 + 1];
-        } else {
-          return row[index % 7];
-        }
+        const offsetFromStart = row * 7 + (column - (this.showWeekNumber ? 1 : 0)) - this.offsetDay;
+        return nextDate(this.startDate, offsetFromStart);
       },
 
       isWeekActive(cell) {
@@ -343,7 +329,7 @@
 
             const cell = row[j];
             const index = i * 7 + j + (this.showWeekNumber ? -1 : 0);
-            const time = startDate.getTime() + DAY_DURATION * (index - this.offsetDay);
+            const time = nextDate(startDate, index - this.offsetDay).getTime();
 
             cell.inRange = minDate && time >= clearHours(minDate) && time <= clearHours(maxDate);
             cell.start = minDate && time === clearHours(minDate.getTime());

+ 2 - 3
packages/date-picker/src/basic/month-table.vue

@@ -49,14 +49,13 @@
 
 <script type="text/babel">
   import Locale from 'element-ui/src/mixins/locale';
-  import { isDate, range, getDayCountOfMonth } from '../util';
+  import { isDate, range, getDayCountOfMonth, nextDate } from '../util';
   import { hasClass } from 'element-ui/src/utils/dom';
 
   const datesInMonth = (year, month) => {
     const numOfDays = getDayCountOfMonth(year, month);
     const firstDay = new Date(year, month, 1);
-    const ONE_DAY = 8.64e7;
-    return range(numOfDays).map(n => new Date(firstDay.getTime() + n * ONE_DAY));
+    return range(numOfDays).map(n => nextDate(firstDay, n));
   };
 
   export default {

+ 3 - 6
packages/date-picker/src/basic/year-table.vue

@@ -45,15 +45,12 @@
 
 <script type="text/babel">
   import { hasClass } from 'element-ui/src/utils/dom';
-  import { isDate, range } from '../util';
-
-  const isLeapYear = year => year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
+  import { isDate, range, nextDate, getDayCountOfYear } from '../util';
 
   const datesInYear = year => {
-    const numOfDays = isLeapYear(year) ? 366 : 365;
+    const numOfDays = getDayCountOfYear(year);
     const firstDay = new Date(year, 0, 1);
-    const ONE_DAY = 8.64e7;
-    return range(numOfDays).map(n => new Date(firstDay.getTime() + ONE_DAY));
+    return range(numOfDays).map(n => nextDate(firstDay, n));
   };
 
   export default {

+ 18 - 15
packages/date-picker/src/util/index.js

@@ -61,25 +61,37 @@ export const getDayCountOfMonth = function(year, month) {
   return 31;
 };
 
+export const getDayCountOfYear = function(year) {
+  const isLeapYear = year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
+  return isLeapYear ? 366 : 365;
+};
+
 export const getFirstDayOfMonth = function(date) {
   const temp = new Date(date.getTime());
   temp.setDate(1);
   return temp.getDay();
 };
 
-export const DAY_DURATION = 86400000;
+// see: https://stackoverflow.com/questions/3674539/incrementing-a-date-in-javascript
+// {prev, next} Date should work for Daylight Saving Time
+// Adding 24 * 60 * 60 * 1000 does not work in the above scenario
+export const prevDate = function(date, amount = 1) {
+  return new Date(date.getFullYear(), date.getMonth(), date.getDate() - amount);
+};
+
+export const nextDate = function(date, amount = 1) {
+  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
+};
 
 export const getStartDateOfMonth = function(year, month) {
   const result = new Date(year, month, 1);
   const day = result.getDay();
 
   if (day === 0) {
-    result.setTime(result.getTime() - DAY_DURATION * 7);
+    return prevDate(result, 7);
   } else {
-    result.setTime(result.getTime() - DAY_DURATION * day);
+    return prevDate(result, day);
   }
-
-  return result;
 };
 
 export const getWeekNumber = function(src) {
@@ -90,6 +102,7 @@ export const getWeekNumber = function(src) {
   // January 4 is always in week 1.
   const week1 = new Date(date.getFullYear(), 0, 4);
   // Adjust to Thursday in week 1 and count number of weeks from date to week 1.
+  // Rounding should be fine for Daylight Saving Time. Its shift should never be more than 12 hours.
   return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
 };
 
@@ -208,13 +221,3 @@ export const nextYear = function(date, amount = 1) {
   const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
   return modifyDate(date, year, month, monthDate);
 };
-
-// {prev, next} Date works for daylight saving time
-// add / subtract one day's duration does not work
-export const prevDate = function(date, amount = 1) {
-  return new Date(date.getFullYear(), date.getMonth(), date.getDate() - amount);
-};
-
-export const nextDate = function(date, amount = 1) {
-  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
-};

+ 84 - 43
test/unit/specs/date-picker.spec.js

@@ -1076,64 +1076,105 @@ describe('DatePicker', () => {
     });
   });
 
-  it('type:daterange', done => {
-    vm = createTest(DatePicker, {
-      type: 'daterange'
-    }, true);
-    const input = vm.$el.querySelector('input');
+  describe('type:daterange', () => {
+    it('works', done => {
+      vm = createVue({
+        template: '<el-date-picker type="daterange" v-model="value" ref="compo" />',
+        data() {
+          return {
+            value: ''
+          };
+        }
+      }, true);
 
-    input.click();
+      const rangePicker = vm.$refs.compo;
+      const inputs = rangePicker.$el.querySelectorAll('input');
+      inputs[0].focus();
 
-    setTimeout(_ => {
-      const panels = vm.picker.$el.querySelectorAll('.el-date-range-picker__content');
+      setTimeout(_ => {
+        const panels = rangePicker.picker.$el.querySelectorAll('.el-date-range-picker__content');
+        expect(Array.prototype.slice.call(panels)).to.length(2);
+        panels[0].querySelector('td.available').click();
+        setTimeout(_ => {
+          panels[1].querySelector('td.available').click();
+          setTimeout(_ => {
+            inputs[0].focus();
+            setTimeout(_ => {
+              // correct highlight
+              const startDate = rangePicker.picker.$el.querySelectorAll('.start-date');
+              const endDate = rangePicker.picker.$el.querySelectorAll('.end-date');
+              const inRangeDate = rangePicker.picker.$el.querySelectorAll('.in-range');
+              expect(startDate.length).to.equal(1);
+              expect(endDate.length).to.equal(1);
+              expect(inRangeDate.length).to.above(0);
+              // value is array
+              expect(vm.value).to.be.an.instanceof(Array);
+              // input text is something like date string
+              expect(inputs[0].value.length).to.equal(10);
+              expect(inputs[1].value.length).to.equal(10);
+              done();
+            }, DELAY);
+          }, DELAY);
+        }, DELAY);
+      }, DELAY);
+    });
 
-      expect(Array.prototype.slice.call(panels)).to.length(2);
+    it('unlink panels', done => {
+      vm = createTest(DatePicker, {
+        type: 'daterange',
+        unlinkPanels: true
+      }, true);
+      const input = vm.$el.querySelector('input');
+
+      input.click();
 
-      panels[0].querySelector('td.available').click();
       setTimeout(_ => {
-        panels[1].querySelector('td.available').click();
-
-        const {
-          minDate,
-          maxDate
-        } = vm.picker;
-        expect(minDate).to.exist;
-        expect(maxDate).to.exist;
-        expect(maxDate > minDate).to.true;
-        done();
-      }, DELAY);
-    }, DELAY);
-  });
+        const panels = vm.picker.$el.querySelectorAll('.el-date-range-picker__content');
 
-  it('type:daterange with unlink-panels', done => {
-    vm = createTest(DatePicker, {
-      type: 'daterange',
-      unlinkPanels: true
-    }, true);
-    const input = vm.$el.querySelector('input');
+        expect(Array.prototype.slice.call(panels)).to.length(2);
 
-    input.click();
+        panels[1].querySelector('.el-icon-d-arrow-right').click();
+        panels[1].querySelector('.el-icon-arrow-right').click();
 
-    setTimeout(_ => {
-      const panels = vm.picker.$el.querySelectorAll('.el-date-range-picker__content');
+        setTimeout(_ => {
+          const left = panels[0].querySelector('.el-date-range-picker__header');
+          const right = panels[1].querySelector('.is-right .el-date-range-picker__header');
+          const leftText = left.textContent.match(/\d+/g);
+          const rightText = right.textContent.match(/\d+/g);
 
-      expect(Array.prototype.slice.call(panels)).to.length(2);
+          expect(rightText[0] - leftText[0]).to.equal(1);
+          expect((rightText[1] <= 2 ? rightText[1] + 12 : rightText[1]) - leftText[1]).to.equal(2);
 
-      panels[1].querySelector('.el-icon-d-arrow-right').click();
-      panels[1].querySelector('.el-icon-arrow-right').click();
+          done();
+        }, DELAY);
+      }, DELAY);
+    });
 
-      setTimeout(_ => {
-        const left = panels[0].querySelector('.el-date-range-picker__header');
-        const right = panels[1].querySelector('.is-right .el-date-range-picker__header');
-        const leftText = left.textContent.match(/\d+/g);
-        const rightText = right.textContent.match(/\d+/g);
+    it('daylight saving time highlight', done => {
+      // Run test with environment variable TZ=Australia/Sydney
+      // The following test uses Australian Eastern Daylight Time (AEDT)
+      // AEST -> AEDT shift happened on 2016-10-02 02:00:00
+      vm = createVue({
+        template: '<el-date-picker type="daterange" v-model="value" ref="compo" />',
+        data() {
+          return {
+            value: [new Date(2016, 9, 1), new Date(2016, 9, 3)]
+          };
+        }
+      }, true);
 
-        expect(rightText[0] - leftText[0]).to.equal(1);
-        expect((rightText[1] <= 2 ? rightText[1] + 12 : rightText[1]) - leftText[1]).to.equal(2);
+      const rangePicker = vm.$refs.compo;
+      const inputs = rangePicker.$el.querySelectorAll('input');
+      inputs[0].focus();
 
+      setTimeout(_ => {
+        const startDate = rangePicker.picker.$el.querySelectorAll('.start-date');
+        const endDate = rangePicker.picker.$el.querySelectorAll('.end-date');
+        expect(startDate.length).to.equal(1);
+        expect(endDate.length).to.equal(1);
         done();
       }, DELAY);
-    }, DELAY);
+    });
   });
 
   describe('type:datetimerange', () => {