Anyways, the only real solution for managing time is to use a library like Luxon so you can stop thinking about it. Time is like cryptographic encryption - Don't roll your own solution if there is a battle tested library available.
Also, even if all inputs and outputs are in local time, it still makes sense to process everything in UTC. There are a lot of weird corner cases making math on datetime really complicated
However, timezone definitions change. By doing the conversion, you've got to remember to check that the offset between what you stored and the user's local hasn't changed since the time you did the conversion. How often and when you do this checking is ... not obvious.
As time went on, the jobs started taking longer and longer and eventually took longer than the whole local morning, which resulted in them being awake AND being able to complain about it. The other issue was DST... users who were used to waking up and getting their data right away... would either be delayed an hour, or be done an hour early. Sometimes, the jobs would also run 2x or not run at all.
It was an utter mess of unintended consequences.
Or maybe, “what’s the current time in this list of time zones and then I’ll send notifications to users where it’s 9am”
Or something, I don’t specifically remember. The point is that notifications aren’t generated until they’re supposed to be sent, and we didn’t want that to vary by 1 hour with daylight savings.
And this specific use case aside, I think it’s true that there’s a class of use cases where developers do need to consider DST.
Script 1: send notification to User 123 at 9:00 UTC
Script 2: It is 9:00 UTC, which users are due to have notifications that have not been sent?
This way, the timezones don't ever really come into play. The axiom I follow for dealing with time is collect and display time in user's local time but store and process in UTC.
Might change due to a DST change (which varies by time zone, and time zone DST date can change year-to-year), or the user might change time zones. Either way, your Script 1 is the logic that had the bug I inherited.