0

I have a function that returns a Date object modified by a delta like this:

    export function getDate(delta: string = "", start?: Date): Date {
        const date = start ? new Date(start.getTime()) : new Date();
        const rel = delta.split(" ").join("");
        const [, sign, years, months, days, hours, mins, secs] = toArray(/([+-])?(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/.exec(rel) as ArrayLike<string>);
        const plus = sign !== "-"; 
        if (years) date.setFullYear(date.getFullYear() + (plus ? +years : -years));
        if (months) date.setMonth(date.getMonth() + (plus ? +months : -months));
        if (days) date.setDate(date.getDate() + (plus ? +days : -days));
        if (hours) date.setHours(date.getHours() + (plus ? +hours: -hours));
        if (mins) date.setMinutes(date.getMinutes() + (plus ? +mins: -mins));
        if (secs) date.setSeconds(date.getSeconds() + (plus ? +secs : -secs));
        return date;
    }

It seem to work just fine in node and chrome and my tests pass locally. But when I push to npm the tests fail in Travis CI, like this: Travis CI

What I am sadly blind to is why the first test pass and the second fail with exactly 1 hour. Is there some CET / UCT magic i am missing? Am I missing out on some special thing with how the Date object works in various node versions?

You can see the test code in travis (link above) but I will add it here as well:

    const now = new Date();
    let pos = Util.getDate("+1Y2M3d4h5m6s", now);
    expect(now.getTime() - pos.getTime()).toBe(-37166706000); // passes locally and in travis
    let neg = Util.getDate("-1Y2M3d4h5m6s", now);
    expect(now.getTime() - neg.getTime()).toBe(36903906000); // passes locally but fails in travis

Thankful for some wisdom.

JGoodgive
  • 1,068
  • 10
  • 20

2 Answers2

1

What time zone is the Travis CI system running in? My time zone doesn't recognize DST and I get the same result as Travis CI. Since you are crossing a time zone boundary (in the US at least, currently it is April and you are going backwards to February) then the behavior will be different depending on whether the timezone recognizes DST or not.

Pace
  • 41,875
  • 13
  • 113
  • 156
  • Excellent answer and it helped me to find this code to handle it. But i would suggest against extending Date. https://stackoverflow.com/a/11888430/2335137 – JGoodgive Apr 17 '19 at 19:21
1

By testing for differences in millisecond timestamps when your input is "now", you're assuming that all of your units will always have the same number of associated milliseconds. This is incorrect.

  • Not every year has the same number of days. Common years have 365. Leap years have 366.
  • Not every month has the same number of days. They range from 28 to 31.
  • Not every local day has 24 hours. Time zone offset transitions (such as caused by daylight saving time or by changes in standard time) can cause a given local day to be 23, 23.5, 24, 24.5, or 25 hours long.

Since you are working in local time, you will be affected by the local time zone. I assume that one of your environments has a time zone transition within the range you are testing, and the other does not.

There are some other problems with the approach you are taking to apply the interval. Consider if the input date was 2019-01-31 and the interval was +1M1d. You'd perhaps expect that to be 2019-03-01, but your code will give 2019-03-04 due to the overflow that occurs as each step is applied. The code to do this correctly is complex, and is usually delegated to libraries like Moment, Luxon, and Date-fns.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Yes i have used moment before but just needed a simple way to calculate dates for less exact scenarioa like cookie life etc and didnt want to have to add another lib. Guess i was a bit naive but i will give it a shot as its a Nice to have feature in my library. – JGoodgive Apr 17 '19 at 20:06
  • 1
    If you're going to implement yourself, then you should check for overflow after each add step and adjust accordingly. You might also want to look at Luxon's `DateTime.plus(Duration)`, or Date-fns's `addYears`, `addMonths`, `addDays`, etc. Either way, you can't test the result by milliseconds deltas. You'll have to find another way, such as testing specific inputs instead of "now", and examining properties of the outputs. – Matt Johnson-Pint Apr 17 '19 at 20:50
  • This might be a silly question but does that not mean that month and years are doing the right thing semantically since April 4 - 2month = Feb 4 nomatter what time zone we are in, and only hours and below can cause strange behavior with DST? – JGoodgive Apr 18 '19 at 08:05