Is there an inverse of DateTime.truncate
? I have a DB column with :utc_datetime_usec
type and I’m putting data coming from external system that has only precision up to a second.
I’m doing %{datetime | microsecond: {0, 6}}
but maybe there’s a better way?
Should this be handled by Ecto internally? Seems like it’s failing on purpose.
1 Like
DateTime.convert!(datetime, datetime.calendar)
Doesn’t seem to work for me on Elixir 1.10:
iex> DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.convert!(Calendar.ISO) |> Map.get(:microsecond)
{0, 0}
1 Like
I was wrong there. I wasn’t able to find anything.
Sounds like a set of extend/2
functions which take the same arguments as truncate/2
could be helpful.
1 Like
I can’t find anything (concise) either but what’s wrong with that one? You can always have a small wrapper in your app e.g. MyApp.Dates.put_usec/1
that does the same.
1 Like
…Oh. In that case I agree, an addition to the stdlib should definitely be on the radar. Especially having in mind that just doing DateTime.add
will not help in this case. So yep, extend/2
looks like a good option.
1 Like
Here’s the helper that I came up with. Only covers the use case I need, doctests included:
@doc """
Ensures that the given `DateTime` has the specified precision.
iex> ensure_precision(~U[2022-01-01 10:00:00Z], :microsecond)
~U[2022-01-01 10:00:00.000000Z]
iex> datetime = ~U[2022-01-01 10:00:00.123Z]
iex> DateTime.compare(datetime, ensure_precision(datetime, :microsecond))
:eq
"""
def ensure_precision(%DateTime{microsecond: {_, 6}} = datetime, :microsecond) do
datetime
end
def ensure_precision(%DateTime{microsecond: {value, _precision}} = datetime, :microsecond) do
%{datetime | microsecond: {value, 6}}
end
2 Likes
DateTime.add(datetime, value, :microsecond)
Edit: ignore me, this does not change the precision of the DateTime but perhaps it should.
1 Like
The issue here is also this:
iex> {:ok, dt, 0} = DateTime.from_iso8601("2022-01-01T10:00:00.123Z")
iex> dt.microsecond
{123000, 3}
But Ecto will still raise when trying to put the value in :utc_datetime_usec
:
defp check_usec!(%{microsecond: {_, 6}} = datetime, _kind), do: datetime
defp check_usec!(datetime, kind) do
raise ArgumentError,
"#{inspect(kind)} expects microsecond precision, got: #{inspect(datetime)}"
end
1 Like
I understand but note that Ecto is not wrong here. You are saying you are expecting something with usec
precision but you can’t guarantee that. Perhaps you should not be using _usec
for those cases.
At the same time, we likely should have a better API to do this in DateTime.
1 Like
Sorry, got a bit confused on how the microsecond
field works in DateTime
- it always stores microseconds; the precision “represents the number of digits that must be used when representing the microseconds to external format”. So even for datetimes with millisecond precision, the microseconds are already stored, but with precision of 3.
Agreed on that Ecto is not wrong here, but I guess the precision could be automatically increased.
I think my use case is justified - I’m tracking the timestamp of the last update in the external system in a generic way and so various external systems use different precision for this. That’s why I went with _usec
and need to “untruncate” it.
1 Like
I’ve bumped into this as well from time to time. I can see that there are cases where precision is required and therefore it’s an error if not supplied, but there’s also the case of just wanting to store the best precision available.