Enhance NaiveDateTime.truncate/2 to truncate to nearest :minute or :hour

57 προβολές
Παράβλεψη και μετάβαση στο πρώτο μη αναγνωσμένο μήνυμα

James Lavin

μη αναγνωσμένη,
26 Ιαν 2024, 11:27:37 π.μ.26 Ιαν
ως elixir-lang-core
Hi:

Thank you, Elixir community, for building this incredible language and ecosystem!

I just had a need to truncate a NaiveDateTime to the nearest minute and was surprised to see that option isn't available.

I'd like to propose something like the following (which I just typed out but haven't tested):

```
  @doc """
  Returns the given naive datetime truncated to the given precision
  (`:microsecond`, `:millisecond`, `:second`, `:minute` or `:hour`).

  The given naive datetime is returned unchanged if it already has lower precision
  than the given precision.

  ## Examples

      iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :microsecond)
      ~N[2017-11-06 00:23:51.123456]

      iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :millisecond)
      ~N[2017-11-06 00:23:51.123]

      iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :second)
      ~N[2017-11-06 00:23:51]

      iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :minute)
      ~N[2017-11-06 00:23:00]

      iex> NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :hour)
      ~N[2017-11-06 00:00:00]

  """
  @doc since: "1.6.0"
  @spec truncate(t(), :microsecond | :millisecond | :second | :minute | :hour) :: t()
  def truncate(%NaiveDateTime{microsecond: microsecond} = naive_datetime, :hour) do
    %{naive_datetime | minute: 0, second: 0, microsecond: Calendar.truncate(microsecond, :second)}
  end

  def truncate(%NaiveDateTime{microsecond: microsecond} = naive_datetime, :minute) do
    %{naive_datetime | second: 0, microsecond: Calendar.truncate(microsecond, :second)}
  end

  def truncate(%NaiveDateTime{microsecond: microsecond} = naive_datetime, precision) do
    %{naive_datetime | microsecond: Calendar.truncate(microsecond, precision)}
  end
```

Thank you for considering this idea.

Cheers,

James Lavin

José Valim

μη αναγνωσμένη,
26 Ιαν 2024, 11:39:12 π.μ.26 Ιαν
ως elixir-l...@googlegroups.com
This is complicated because "truncate" is about removing the precision, and we cannot have a minute precision. The way to support it properly would be to allow seconds to be nil and then we would print it as "2022-12-31 13:45", without the seconds component, but I think this would be a large change (and applications would need to deal with the fact seconds could be nil).

Perhaps we could introduce another function to "zerofy" certain fields, while perhaps keeping precision. Although, in your case, the easiest is to truncate and then directly zerofy the fields in the struct.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/19ac16f9-7f73-4b62-9ea8-8c91e1f14838n%40googlegroups.com.

Austin Ziegler

μη αναγνωσμένη,
26 Ιαν 2024, 11:49:46 π.μ.26 Ιαν
ως elixir-l...@googlegroups.com
We've implemented a Timestamp module that defines truncate/2 as zeroing.

```elixir
  @doc """
  Truncates the provided date or timestamp to the specified precision
  (`:microsecond`, `:millisecond`, `:second`, `:minute`, `:hour`, or `:day`).

  The given date or timestamp is returned unchanged if it already has lower

  precision than the given precision.

  When `t:Time.t/0` values are provided `:day`, it is the same as midnight.
  """
  def truncate(%Date{} = date, _precision), do: date

  def truncate(t, precision)
      when precision in [:microsecond, :millisecond, :second],
      do: %{t | microsecond: Calendar.truncate(t.microsecond, precision)}

  def truncate(t, :minute), do: %{t | second: 0, microsecond: {0, 0}}

  def truncate(t, :hour), do: %{t | minute: 0, second: 0, microsecond: {0, 0}}

  def truncate(t, :day), do: %{t | hour: 0, minute: 0, second: 0, microsecond: {0, 0}}
```

James Lavin

μη αναγνωσμένη,
26 Ιαν 2024, 11:59:04 π.μ.26 Ιαν
ως elixir-lang-core
You continue to astound me with your responsiveness, José. Thank you for all you have done!

Truncating and zeroing out certainly are different things, and there's no simple way to implement both perfectly. I would be thrilled to get some improvement implemented, however the community decides is best.

I recommend against making perfect the enemy of the good. My hunch is that many people seeking to zero out hours or minutes would go looking for the `truncate/2` function and not even consider the difference. The distinction between truncation (of sub-second values) and zeroing (of minutes and seconds to truncate to the nearest hour or minute) could be mentioned/explained in the function's @doc section.

Currently, `truncate/2` enables neither truncating nor zeroing out. If true truncation is indeed a "large change" with significant downstream implications for existing apps, as I'm sure it is, then it's probably not something we'll ever want to implement. Providing the zeroing out functionality inside `truncate/2` would give users the option to do so. It wouldn't satisfy all use cases, but -- as you note -- true truncation is arguably prohibitively impactful.

Cheers,

James
Απάντηση σε όλους
Απάντηση στον συντάκτη
Προώθηση
0 νέα μηνύματα