dateutils icon indicating copy to clipboard operation
dateutils copied to clipboard

dateadd: How to get result in local timezone without explicitly specifying it?

Open antibot4navalny opened this issue 6 years ago • 13 comments

I need to get a timestamp few minutes from now, in local time, like this:

dateadd --format '%H.%M.%S' now +3m

However the result is given in UTC. How can I make dateadd default its output to the local timezone (or at least what is easiest way to auto-detect user's current timezone without explicitly specifying it?)

antibot4navalny avatar Mar 01 '19 20:03 antibot4navalny

Hey, thanks for the question. Unfortunately the answer is that this is out of dateutils' scope. By my design choice everything has to be explicit, there's no magic background variables or anything that changes the behaviour apart from the given command line options.

However, if you're on Linux and got /etc/localtime set to the system-wide local timezone (note: this is not necessarily the current user's local timezone), you can try:

$ dateadd --format '%H.%M.%S' now +3m --zone `readlink /etc/localtime`

that is, use shell substitution and pass the result to the --zone option.

I'm not sure if there's a (consistent) way for a user to change the timezone, many commands pay heed to the TZ environment variable (echo $TZ) but dateutils requires a specific tz file, as found under e.g. /usr/share/zoneinfo, you could try using $TZ as argument to the --zone parameter but dateadd will silently ignore it if there's no corresponding tz file, i.e.

 $ TZ=MadeUpZoneName dateadd --format '%H.%M.%S' now +3m --zone $TZ

will give you UTC time again, pretty much what GNU date and friends would do too.

hroptatyr avatar Mar 01 '19 21:03 hroptatyr

Just tried your snippet dateadd --format '%H.%M.%S' now +3m --zone readlink /etc/localtime`, and it gives different results between MacOS and CentOS Linux: correct local-timezone date on Mac; UTC-implying one on CentOS.

Which defeats the purpose of portability across OSes which I hoped dateutils will help with in the first place.

antibot4navalny avatar Mar 01 '19 21:03 antibot4navalny

Well, yes, that's what I warned about. Dateutils are portable in the sense that they produce the same result on any machine regardless of underlying operating system or local settings, let's call this result-oriented portability. What you want is code-portability: Using the same snippet everywhere, producing different results depending on operating system and local tweaks.

Unlike UTC (and English), locales and timezone files are highly system-specific: the means to set them on a system-wide or per-user basis are different on every system, even the naming is not standardised in any way, EST for instance on an Australian machine could refer to Eastern Standard Time (UTC+10), or it could just mean anything really seeing as it's just a filename somewhere in /usr/share.

To solve your issue however, you could try GNU date (probably gdate on MacOS):

$ date -d '+3 mins' +%H.%M.%S

they will surely use the current user's setting, or if that's not set, the system-wide default.

hroptatyr avatar Mar 01 '19 22:03 hroptatyr

Thanks, that's what I was also thinking of as an alternative approach.

antibot4navalny avatar Mar 01 '19 23:03 antibot4navalny

@hroptatyr So the problem is that you are against environment variables?

I found it very confusing that TZ is not respected.

I would not use this software until that changes.

$ date
Wed 24 Feb 19:39:37 PST 2021
$ datediff 'now' '2021-02-24T00:00:00' -f '%H'
-27
$ datediff 'now' '2021-02-24T00:00:00' --from-zone=America/Los_Angeles -f '%H'
-19

If you are worried about reproducibility, why not just list the environment variables you use? In the manual pages like most packages? Why make everyone type it out every time? But I think I've encountered this disagreement before.

Incidentally, I see it as a severe flaw that GNU's date reverts silently to UTC when $TZ is not recognized... and it looks like you've also missed an opportunity to fix this bug. Speaking of reliable software design!

And what shell is this? Are you missing a semicolon before dateadd?

TZ=MadeUpZoneName dateadd --format '%H.%M.%S' now +3m --zone $TZ

Hope this helps, it looks like it could be a useful project.

For me, R is currently more useful (and produces decimal fractional values, which is generally what I prefer when doing arithmetic):

$ echo 'difftime(strptime("24.02.2021",format="%d.%m.%Y"), Sys.time(), units="hours")' | R
...
Time difference of -19.94412 hours

archenemies avatar Feb 25 '21 04:02 archenemies

@archenemies No, I'm not against environment variables, I'm against implicit magic. Explicitly stating the zone in the command-line beats using context derived somehow from the system or the environment, in my eyes.

Try debugging a script that behaves differently on two machines (or for two users on the same machine) and uses settings from /etc, the environment, some stuff from ~/.local, in a difficult-to-follow precedence of course, and you might understand what I'm trying to address.

Incidentally, I see it as a severe flaw that GNU's date reverts silently to UTC when $TZ is not recognized... and it looks like you've also missed an opportunity to fix this bug.

Well, yes. It is mandated by POSIX:

Determine the timezone in which the time and date are written, unless the −u option
is specified. If the TZ variable is unset or null and −u is not specified, an unspecified
system default timezone is used.

Apparently, it is more important to produce any output than being pedantic about settings in the environment. Imagine setting LC_ALL to GO_BBLE_DY_GOOK and all programs suddenly turn sour, and error messages to point out about this very problem are forbidden, too, seeing as they're not in the requested language.

I can see their point but I agree with you here, and I might be tempted to change that because, as we established earlier, I'm not using environment variables anyway.

Are you missing a semicolon before dateadd?

Yes.

For me, R is currently more useful (and produces decimal fractional values, which is generally what I prefer when doing arithmetic):

Use whatever suits you. I regularly deal with non-standard date and time problems. And I'm a heavy R user, too. Even wrote my own Rdateutils package to address my needs.

hroptatyr avatar Feb 25 '21 05:02 hroptatyr

Thanks for the reply. Good to come across a fellow R user.

From what you quoted, it looks like POSIX specifies the behavior of date when TZ is empty or unset, not when its value unrecognized.

I know that debugging is hard. What would you consider to be a situation where environment variables or configuration files should be used? If your users find themselves entering the same argument into almost every command, would that not be one?

archenemies avatar Feb 25 '21 13:02 archenemies

Yes sorry you're right. POSIX leaves that case unspecified, both for date(1) and tzname(3), only guarantees tzset() shall succeed, not mentioning gmtime, localtime, etc.

If your users find themselves entering the same argument into almost every command, would that not be one?

I'd say, I can clearly see the intention. If it's just for you and really a convenience thing, just like you'd alias ls -l you could alias dateadd --from-zone XXX --zone XXX.

Having said that, I've just noticed the ambiguity: Is TZ meant for --zone, --from-zone, or both as in my example? What's the precedence if both --zone is set, --from-zone is left out but TZ is set? It's just complexity in my eyes.

hroptatyr avatar Feb 25 '21 14:02 hroptatyr

Thank you.

If I am being asked to suggest how $TZ should behave in this software, it would simply be as a default for any time when the timezone is not explicitly specified. Users who want to be explicit about the timezone can put it into their dates.

I guess the current behavior of this software is to assume UTC when a zone is not specified? Which is great if you live in England, not so great if you live somewhere else and had to spend a half-hour walking through the apparent arithmetic mistakes because you couldn't figure out why you were getting obviously wrong answers. With not even a warning message to help you.

Why would a user in California want England to be the default on a system that clearly specifies a different default in a familiar and user-friendly manner? With no warning message saying "you forgot to specify the timezone"?

If it's just for you and really a convenience thing

No it's not just for "me", it's for anyone in the next hundred years who mistakenly assumes that a package with a general name like "dateutils", originating in the early 21st century, should be well-designed. But maybe there are so many other problems that it's not worth salvaging, for example that I can't seem to request fractional output in decimals as I mentioned earlier. I hope these points are helpful - I don't have any popular projects of my own, but I assume that if I did then I'd want them to be as awesome as possible.

archenemies avatar Feb 25 '21 16:02 archenemies

If I am being asked to suggest how $TZ should behave in this software, it would simply be as a default for any time when the timezone is not explicitly specified. Users who want to be explicit about the timezone can put it into their dates.

That's pretty much date(1) semantics with -u for UTC convenience. Alas, I cannot change that because it breaks every single script where the user hadn't been explicit.

Why would a user in California want England to be the default on a system that clearly specifies a different default in a familiar and user-friendly manner?

That's not what's happened. A Californian user that doesn't care about ISO 8601 dates is meant to use POSIX or GNU date or gdate or any of the alternatives. I specifically target conversions and arithmetics between/for ISO dates, conversions between zones and explicit input and output formatting, while avoiding calls to libc functionality (except for the strptime tool). You mistake the convergence over the years for its initial design.

No it's not just for "me", it's for anyone in the next hundred years who mistakenly assumes that a package with a general name like "dateutils", ...

So it's down to the name, and I accept that.

I can't seem to request fractional output in decimals as I mentioned earlier.

There's simply not enough space for that (in 64 bits). However, I'm considering subsecond resolution within the scope of my tools but it's not a simple realm (think of smear/leap seconds).

hroptatyr avatar Feb 25 '21 17:02 hroptatyr

To solve your issue however, you could try GNU date (probably gdate on MacOS): $ date -d '+3 mins' +%H.%M.%S

I wouldn't recommend GNU date for this kind of calculations. I just tried in different scenarios, and got surprised:

$ date --date="20:00:00 + 1 hour 2 min 3 sec" +%H:%M:%S
22:02:03
$ date --date="20:00:00   1 hour 2 min 3 sec" +%H:%M:%S
21:02:03

rdiez avatar Apr 16 '23 17:04 rdiez

I would like note that the date -u design makes it possible to portably use the localtime but request UTC as needed. Because dateutils defaults to UTC it makes things rather difficult to use localtime as there is no portable way to determine it.

Obviously changing dateutils to default to localtime will break its current usage in scripts.

Instead would it be acceptable to add a flag to specify localtime or possibly reuse the existing -z option to accept a string such as localtime without stepping on zoneinfo's toes too much?

Edit: Another thought might be to include a flag to datezone that can print the current zone name in a way compatible with the -z option, then date* -z "$(datezone -p)" ... might be a possible UI.

Earnestly avatar Nov 25 '23 17:11 Earnestly

You know what, that's actually a good compromise. The /etc/localtime magic is well documented for Linux, Solaris, and Darwin. I'll give that a go.

hroptatyr avatar Nov 27 '23 09:11 hroptatyr