DEV Community

Cover image for We had a date bug that happened two times a year, and we didn't know, you might have it too 😱

We had a date bug that happened two times a year, and we didn't know, you might have it too 😱

Zac_A_Clifton on September 11, 2023

TL;DR Novu's team encountered a significant bug affecting date calculations in their CI/CD pipelines, hindering all deployments. The i...
Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I'm sorry for the off-topic, but the joke just has to be made:

Two dates a year is a problem? I'm lucky if I even get that many... 🤭

Collapse
 
nevodavid profile image
Nevo David

🤣

Collapse
 
empe profile image
Emil Pearce

Two key takeaways:

Never Assume: Even popular libraries can have hidden pitfalls. Always question, regardless of its reputation.

Testing is Crucial: Your robust tests highlighted a significant issue, emphasizing the need for thorough testing in software development.

Thanks for sharing this valuable lesson! Here's to more bug-free coding! 🥂

Collapse
 
cliftonz profile image
Zac_A_Clifton

These are great takeaways, thank you for sharing!

Collapse
 
johnatpheasantville profile image
John Barrow

I think that the issue is one of definition rather than coding around the end of the month. We write software that calculates rental periods. When the rent is monthly, the start date could fall on (say) the 31st or 30th of the month. Advancing these through the year gives an ambiguity. When you get to (e.g.) February, both charges are adjusted to the 28th / 29th. What day should they be in March? They should be back to 31st or 30th. You can't know this without storing an additional peice of information, in this case the regular billing day. However, without needing to support both methods, most people expect the last day of the month to be dominant so, once the day is the last date of the month it stays there when next move a month. To easily spoort this dominant definition, you add one day to the date (for all calculations), then add / subtract the required number of months, then subtract one day. This makes the end of the month dominant. Remember that, if advancing a date monthly through the year using the dominant method, then any date with a day greater than or equal to 28 will eventually end up being the 31st or last day of the month (without also knowing the regular billing day to make the distinction).

Collapse
 
cezarytomczyk profile image
Cezary Tomczyk

From my experience, I'd advise using always fixed version of the npm package. The most common usage I've seen is to use a tilde. Using tilde (~) gives you bug-fix releases. However, on CI/CD, npm may fetch a really tiny next version, and that may fail your build. You'll spend a whole day investigating the root cause. Even more confusion comes from the fact that package.json contains the same version, e.g., 3.4.0 in the repository and on your computer, but in fact on the server there might be 3.4.1 installed. Hence, fixed versions ensure that npm dependencies will always be on the same version.

Collapse
 
niklaspor profile image
Niklas

It's fine using dependency constraints like ^ and ~ as long as you are locking your dependencies with a lock file and only use npm ci inside your pipeline instead of npm i as this will always take the resolves versions from the lockfile. If any commit breaks the pipeline you can just check if the lock file was updated 👍

Collapse
 
cezarytomczyk profile image
Cezary Tomczyk

@niklaspor I don't recall exactly, but I think I had a past problem with package-lock.json. There were unresolvable conflicts. You can use the keywords "package lock json problem" to find out what others are struggling with.

One of the points npm documentation says is "install exactly the same dependencies". That's what you can exactly achieve without ^ and ~.

From my personal side, I have never found any real practical usage of package-lock.json.

Thread Thread
 
niklaspor profile image
Niklas

npm i / npm install will bump any package to the latest matching package version. npm ci or pnpm --frozen-lockfile will keep exactly the versions which were resolves in the last npm i which was executes.

Always use npm ci inside your pipelines, otherwise you risk getting different packages from your local installation, even if you don't use any ranged but just plain veesions. Also any package deeper in your dependency tree might specify a version range, which might lead to a newer resolves dependency, if you execute npm i instead of npm ci. Same for pnpm and yarn.

I would even suggest you use npm ci on your local machine, when your working with any teams of bigger size and you don't want to update dependencies. Otherwise the code on your machine might differ from the one one your colleagues.

stackoverflow.com/questions/524996...

support.deploybot.com/article/131-....

Thread Thread
 
cezarytomczyk profile image
Cezary Tomczyk

@niklaspor Let me put this into a different perspective. The most important thing here is to ask, what kind of problem is being solved here? So the problem is: how to keep npm package dependencies consistent? If using a fixed version works, any other solution will simply be unnecessary. And so far, the solution is working excellently.

As for CI/CD, it is organised in very different ways, and there is no single approach. So if npm ci solves specific problems for someone, then using this approach is the right solution.

Collapse
 
cliftonz profile image
Zac_A_Clifton

Have you had a bug with dates before?

Collapse
 
combarnea profile image
Tomer Barnea

The question should be if you ever wrote something with dates with no bugs at all????

Collapse
 
rockykev profile image
Rocky Kev

Two hardest things in programming: Cache invalidation and naming things.

I think we should add time, timezones and dates.

Collapse
 
cliftonz profile image
Zac_A_Clifton

That is so true, even the best of us get tripped up by this.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Not quite dates, but arguably even more strange: Lua is a very small language, with a relatively simple model for handling dates, and sometimes you need to get a bit hackey to deal with time zones.

Well, luckily it just uses the C time/date functions, which are clearly defined, so you can at least rely on your hacks to work everywhe— hold up a second, you didn't consider windows.

Turns out microsoft's fuck-up of a C compiler knows better than the spec, so on any Lua version compiled using it, you cannot get the current time zone as a numeric offset, because the library function just returns some other short string instead.

This took way too long to debug, and when I figured out the cause was that microsoft simply does what microsoft wants, I felt like throwing my PC out the window.

Collapse
 
cliftonz profile image
Zac_A_Clifton

I think most people feel the same way about microsoft.

and, by the way, I enjoyed reading this story!

Collapse
 
unicodeveloper profile image
Prosper Otemuyiwa • Edited

The good old date time problem.

We have two popular problems in programming:

  • Naming things
  • Cache Invalidation

Now with this exposé, we have a third - date/time problems!

Thanks for writing this u @cliftonz . Good lessons learned

Collapse
 
thorstenhirsch profile image
Thorsten Hirsch

I wouldn't consider this a bug.

const startDate = new Date("2023-08-31");
const oneMonthAhead = addMonths(startDate, 1);
const result = subMonths(oneMonthAhead, 1);
console.log(result);  // 30th of August
Enter fullscreen mode Exit fullscreen mode

This works as I expect it to work.

Collapse
 
cliftonz profile image
Zac_A_Clifton

I think your thought is very true, while in our perspective it was a bug I agree the actually logic is not a bug.
However, I do think that there should be a warning about this edge case as not everyone would be able to see it.

Collapse
 
poosham profile image
Jean-Philippe Green

Doesn't this happen for Mars, May and October too? So 5 times a year rather than 2

Collapse
 
husseinkizz profile image
Hussein Kizz

The fact that the bug was happening 2 times a year brough me all the way here, like have you ever encountered a bug that only manifests say after 5 years under very specific scenarios? Damn it was working all long you say!!!

Collapse
 
taikedz profile image
TaiKedz

Date-math is non-trivial, month-math especially so.

What does "add one month" (or subtract) actually mean? In the case of this library, it's "jump to the same day number ahead/back". Adding/subtracting 30 would also be error-prone.

So your takeaways are spot on, though I'd also add this caveat:

When something isn't of a given fixed quantity, beware abstractions that treat them as if they were...!

Collapse
 
cliftonz profile image
Zac_A_Clifton

Great Insight!

Collapse
 
1link profile image
1Link.Fun

The picture has no link to your repo

Collapse
 
nevodavid profile image
Nevo David

Fixed :)

Collapse
 
robinamirbahar profile image
Robina

Awesome

Collapse
 
cliftonz profile image
Zac_A_Clifton

How worried are you about date bugs coming up in your product?

Collapse
 
artxe2 profile image
Yeom suyun

The bug does not occur in the Date object of v8.
I like to leverage the basic features of the JS engine.

Collapse
 
cliftonz profile image
Zac_A_Clifton

That is very true, I do not know why we choose this library but it does provide a lot of good utilities to leverage.

Collapse
 
oculus42 profile image
Samuel Rouse

The native Date object has plenty of complexity to be aware of, too. Entering "equivalent" date strings do not always produce the same result:

// ISO Assumes UTC Timezone, so we get Midnight UTC
const dateFromISO = new Date('2023-09-14');
// 'Wed Sep 13 2023 20:00:00 GMT-0400 (Eastern Daylight Time)'

// Other formats assume local time zone so I get Midnight Eastern
const dateFromMDY = new Date('9/14/2023');
const dateFromElements = new Date(2023, 8, 14);
// 'Thu Sep 14 2023 00:00:00 GMT-0400 (Eastern Daylight Time)'
Enter fullscreen mode Exit fullscreen mode

This can be a real problem when parsing user-entered dates... some regions/people use the YYYY-MM-DD format for dates, which means you can be many hours off from the expected time.

Collapse
 
artxe2 profile image
Yeom suyun

The default Date object is indeed complex, and this is also a problem of ambiguity that exists throughout JavaScript.
However, I think the confusion that JavaScript's Date object gives to developers mostly comes from time zones and the conversion between strings and Dates.
If only these three problems could be solved, wouldn't we be able to get a lot of benefits from the basic features of JS?
For example, you can use a simple string_to_date function to solve the problems you mentioned, as follows.

function string_to_date(date, format = "YYYY-MM-DDTHH:mm:ss.sss") => {
    let x = format.indexOf("YYYY")
    const year = x < 0 ? "0000" : date.slice(x, x + 4)
    x = format.indexOf("MM")
    const month = x < 0 ? "00" : date.slice(x, x + 2)
    x = format.indexOf("DD")
    const day = x < 0 ? "00" : date.slice(x, x + 2)
    x = format.indexOf("HH")
    const hours = x < 0 ? "00" : date.slice(x, x + 2)
    x = format.indexOf("mm")
    const minutes = x < 0 ? "00" : date.slice(x, x + 2)
    x = format.indexOf("ss")
    const seconds = x < 0 ? "00" : date.slice(x, x + 2)
    x = format.indexOf("sss")
    const milliseconds = x < 0 ? "000" : date.slice(x, x + 3)
    return new Date(`${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}`)
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

Now, also try to deal with timezones and daylight saving, and leap seconds as well. There is a huge rabbit hole to go down and no right or wrong, but expectations and solutions on a case by case basis.

Collapse
 
cliftonz profile image
Zac_A_Clifton

Absolutely, we are looking at implementing timezone aware notifications for our users and it adds a plethora of complexity.

Collapse
 
sang profile image
Sang

If you have ever had a problem with timezone, espescially DST and timezone stuff. You can try this: dev.to/sang/javascript-zoned-date-...

Collapse
 
sang profile image
Sang

A bit off-topic. Recently I published zoned-date library.

dev.to/sang/javascript-zoned-date-...

The library has a sophisticated DST support (better than date-fns) and very elegant API interfaces. If you have ever had a problem with DST or timezone-related, I would highly recommend it. Of course, feedbacks are always recommended.

Collapse
 
cliftonz profile image
Zac_A_Clifton

Thanks for letting us know!

Collapse
 
soanvig profile image
Mateusz Koteja

Funny and insightful video on related topic - timezones:
youtube.com/watch?v=-5wpm-gesOY

I didn't have such problems like in the video, but I was working with transforming dates for aggregation into timezones (for example: night means something different in Japan and France, and it was important whether data point happened at night or not. I had date from one part in the world mapped to date in another part of the world).

It looks I finally made this working, but the worst part: whenever client was not sure about the results I had to go back to that bullshit code and data in database, and figure out whether there is a bug or not, and then explain to the client why his data produces such results. I discovered that human mind is absolutely terrible in such calculations or making assumptions about this topic. It's one of the most unintuitive things I worked with.

Collapse
 
jasonmishi profile image
Jason Mishike

Month math and math with leap years is weird, I'm sure there is some sort of reason things are the way they are in calendars, but it is annoying that the stuff isn't very math, and consequentially computer, friendly...

Collapse
 
voltra profile image
Voltra

Was a pull-request to date-fns created as a result?

Collapse
 
cliftonz profile image
Zac_A_Clifton

Absolutely, its linked in the bottom of the article, but you can go directly to issue with this link.
github.com/date-fns/date-fns/issue...

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

Well, Dates are hard...

Collapse
 
cliftonz profile image
Zac_A_Clifton

Exactly

Collapse
 
freddyhm profile image
Freddy Hidalgo-Monchez

Great catch! This is part of the 10% of bugs that make you question the fabric of nature 😁

What was the test about that helped you find the bug? If you feel comfortable sharing :)

Collapse
 
cliftonz profile image
Zac_A_Clifton

The was ensuring that the date to send a notification was correct.
Such a simple thing turned into something much bigger.