From 473bb02abb2c85b587990b90998cbfbfe6c9a3cb Mon Sep 17 00:00:00 2001 From: Santiago Gimeno Date: Sat, 8 Oct 2016 14:04:05 +0200 Subject: [PATCH] src: workaround moment issue with DST transitions On DST end transition, setting the date to the `startOf` `hour`, `minute` or `second` after having added 1 `hour`, `minute` or `second` to the date, would result in the date set to the initial value causing an infinite loop. Example: ```js 'use strict'; const moment = require('moment-timezone'); var m = moment(new Date('Sun Oct 30 2016 02:00:00 GMT+0200')); console.log(m.toString(), '<-- Just 1 hour before the DST end @ Europe/Madrid'); m.add(1, 'hours'); console.log(m.toString(), '<-- DST end transition @ Europe/Madrid works correctly'); m.startOf('hour'); console.log(m.toString(), '<-- Smthing wrong?? It goes back to the previous hour'); ``` Fixes: https://github.com/harrisiirak/cron-parser/issues/72 Ref: https://github.com/moment/moment/issues/2749 --- lib/date.js | 12 ++++++ test/timezone.js | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/lib/date.js b/lib/date.js index 16247ff7..34f36d77 100644 --- a/lib/date.js +++ b/lib/date.js @@ -15,15 +15,27 @@ CronDate.prototype.addDay = function() { }; CronDate.prototype.addHour = function() { + const prev = this.getTime(); this._date.add(1, 'hour').startOf('hour'); + if (this.getTime() <= prev) { + this._date.add(1, 'hour'); + } }; CronDate.prototype.addMinute = function() { + const prev = this.getTime(); this._date.add(1, 'minute').startOf('minute'); + if (this.getTime() < prev) { + this._date.add(1, 'hour'); + } }; CronDate.prototype.addSecond = function() { + const prev = this.getTime(); this._date.add(1, 'second').startOf('second'); + if (this.getTime() < prev) { + this._date.add(1, 'hour'); + } }; CronDate.prototype.getDate = function() { diff --git a/test/timezone.js b/test/timezone.js index 87966b84..bd5a4d97 100644 --- a/test/timezone.js +++ b/test/timezone.js @@ -264,6 +264,113 @@ test('It works on DST end', function(t) { t.throws(function() { date = interval.next(); }); + + options = { + currentDate : new Date('Sun Oct 29 2016 01:00:00 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 29, '29th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); + + options = { + currentDate : new Date('Sun Oct 29 2016 02:59:00 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 29, '29th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); + + options = { + currentDate : new Date('Sun Oct 29 2016 02:59:59 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 29, '29th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); + + options = { + currentDate : new Date('Sun Oct 30 2016 01:00:00 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); + + options = { + currentDate : new Date('Sun Oct 30 2016 01:59:00 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); + + options = { + currentDate : new Date('Sun Oct 30 2016 01:59:59 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); + + options = { + currentDate : new Date('Sun Oct 30 2016 02:59:00 GMT+0200') + } + + interval = CronExpression.parse('0 12 * * *', options); + t.ok(interval, 'Interval parsed'); + + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 30, '30th'); + date = interval.next(); + t.equal(date.getHours(), 12, '12'); + t.equal(date.getDate(), 31, '31st'); } catch (err) { t.ifError(err, 'Interval parse error'); }