Skip to content

duration should implement to_string/1 #14516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
matt-beanland opened this issue May 22, 2025 · 15 comments
Closed

duration should implement to_string/1 #14516

matt-beanland opened this issue May 22, 2025 · 15 comments

Comments

@matt-beanland
Copy link

matt-beanland commented May 22, 2025

Elixir and Erlang/OTP versions 1.18.3 / Erlang/OTP 27

Given other Calendar types implement String.Chars, Duration should too.

This is very trivial, as would just call Duration.to_string/2 with no opts.

`iex(2)> to_string(Duration.new!(hour: 1))
** (Protocol.UndefinedError) protocol String.Chars not implemented for type Duration (a struct)

Got value:

%Duration{hour: 1}

(elixir 1.18.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir 1.18.3) lib/string/chars.ex:22: String.Chars.to_string/1
iex:2: (file)

iex(2)> Duration.to_string(Duration.new!(hour: 1))
"1h"
iex(3)> `

Operating system

MacOS

Current behavior

`iex(2)> to_string(Duration.new!(hour: 1))
** (Protocol.UndefinedError) protocol String.Chars not implemented for type Duration (a struct)

Got value:

%Duration{hour: 1}

(elixir 1.18.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir 1.18.3) lib/string/chars.ex:22: String.Chars.to_string/1
iex:2: (file)

`

Expected behavior

iex(2)> to_string(Duration.new!(hour: 1)) "1h"

@sabiwara
Copy link
Contributor

FYI this has been proposed and we decided not to implement it, you can find the conversation here.

@josevalim
Copy link
Member

Thanks @sabiwara. Things have changed since that issue, as we know have a Duration.to_string function, but that follows ISO80000-3 for units. In particular, I am worried this will be confusing:

  By default, this function uses ISO 80000-3 units, which uses "a" for years.
  But you can customize all units via the units option:

      iex> Duration.to_string(Duration.new!(year: 3))
      "3a"
      iex> Duration.to_string(Duration.new!(year: 3), units: [year: "y"])
      "3y"

So we need to decide if we want to raise and force folks to pick their units or have something that works out of the box (and then perhaps they are surprised once they see the default units).

Although one could argue that NaiveDateTime.to_string is also confusing (if you are not familiar with the ISO notation). Thoughts @sabiwara @tfiedlerdejanze @kipcole9?

@kipcole9
Copy link
Contributor

kipcole9 commented May 22, 2025

I'm a bit behind the conversation so apologies if my question is naive, but was there an issue using ISO8601 time unit abbreviations as the default unit designator? It feels to me like the default should be round-trippable (parse-to_string-parse) but perhaps that too is overly simplistic?

@josevalim
Copy link
Member

@kipcole9 to_string is meant to be human readable and I think there are very few humans that would define " P40DT12H42M12S" as human readable. Which is why we picked a more common format but following ISO 80000-3 units.

@sabiwara
Copy link
Contributor

sabiwara commented May 22, 2025

Personally leaning towards the camp of "raise and force folks to pick their units", since it's not so common anyway, and it seems reasonable to have people converting to a string the way they see fit? (ISO, human with ISO unit, human with human unit...)
But I don't feel strongly about this issue, either way is fine.

@kipcole9
Copy link
Contributor

@kipcole9 to_string is meant to be human readable and I think there are very few humans that would define " P40DT12H42M12S" as human readable. Which is why we picked a more common format but following ISO 80000-3 units.

Got it, and understood. You're right about the readability unless you spent too much time reading iso8601 (guilty!)

@josevalim
Copy link
Member

@kipcole9 Ha, I didn’t mean to directly imply you but I think I could, to a certain extent, say the same about myself on dates and the ISO format. Then the question is: do we include a default rendering or not?

@kipcole9
Copy link
Contributor

I think defaults would be helpful, but not essential, personally.

I wonder if using the symbols from Calendar.strftime/2 would be an option since they are already established in the standard lib? Just spitballing. The idea of a for year seems quite hard to fit into the "human readable" expectation.

@josevalim
Copy link
Member

Do you mean using the same symbols as in "%Y-%m-%d %H:%M:%S"?

@kipcole9
Copy link
Contributor

Yessir, that's what jumped into my mind (not including the %)

@josevalim
Copy link
Member

We would need to come up with an entry for weeks and milliseconds is not the best (%f), I also think people would struggle to know when you have minutes or months if there is nothing between years and seconds. :(

@kipcole9
Copy link
Contributor

All true. So in which case, no defaults seems like the pragmatic solution.

@matt-beanland
Copy link
Author

matt-beanland commented May 22, 2025

I've caused some debate. Thinking about this some more perhaps there should be a sigil for Duration and to_string would simply return that consistently with Date, Time, DateTime, NaiveDateTime.

I propose ~P (for period):

'~P[PT1H]' where we are using ISO8601 with the P on the sigil and in the 'payload' although perhaps redundant and could be just '~P[T1H]'

Maybe not everyone loves ISO8601 duration but would be nice IMHO to have to_string, sigils, parse roundtrip on duration out of the box consistent with other temporal modules.

@matt-beanland
Copy link
Author

I've updated the PR #14517 with sigil ~P

@josevalim
Copy link
Member

@matt-beanland we also discussed ~P in the past. If we remove the prefix, then we are not really following ISO standards and, per above, unless you are familiar with ISO, it will be unclear what ~P[1MT2M] actually means. The truth is that there is no standardized and commonly readable string representation of durations, which makes providing one in Elixir quite hard. Therefore, we don't plan to merge it right now, we may revisit it later.

@josevalim josevalim closed this as not planned Won't fix, can't repro, duplicate, stale May 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants