Current problem
I have a situation where I have two parameters that are a
date_str
= "20230115"
time_str
= "25:30"
.
The time_str
when it goes above 24
implies the next day.
My goal
I want to take these two parameters and convert them into datetime that returns it into unix seconds as it would be easier to manage. I have a lot of time_str
that are "24:30"
, "25:30"
, even "26:30"
.
My Solution
I have something working but I feel like I’m not doing it in an Elixir way or maybe my approach could be done in a better way.
defmodule Helper do
@doc """
Parses date and time strings into a unix seconds.
The `time_str` can go beyond midnight and into the next day and this will return the correct
#Parameters
- `date_str`: String and should follow this format "20230225". Meaning year, month, day
- `time_str`: String and should follow this format "18:00:00". Meaning hour, min and sec
## Example
iex> Helper.parse_date_time_to_unix_seconds("20230225", "18:00:00")
1677371400
## Example with time_str is `25:30:00` and goes into the next day
iex> Helper.parse_date_time_to_unix_seconds("20230225", "25:30:00")
1677375000
"""
def parse_date_time_to_unix_seconds(date_str, time_str) do
with {:ok, noon} <- to_naive_datetime(date_str),
{:ok, total_seconds} <- parsing_time_string_to_seconds(time_str) do
(noon
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_unix()
) + total_seconds
else
{:error, message} ->
IO.puts(message)
raise "#{message}"
_ ->
IO.puts("parse_to_datetime error")
end
end
def to_naive_datetime(<<yyyy::binary-4, mm::binary-2, dd::binary-2>>) do
[yyyy, mm, dd] = for i <- [yyyy, mm, dd], do: String.to_integer(i)
NaiveDateTime.new(yyyy, mm, dd, 0, 0, 0)
end
def parsing_time_string_to_seconds(time_str) do
[hr, min, sec] = String.split(time_str, ":")
case hr do
"24" -> {:tomorrow, "00:#{min}:#{sec}"}
"25" -> {:tomorrow, "01:#{min}:#{sec}"}
"26" -> {:tomorrow, "02:#{min}:#{sec}"}
"27" -> {:tomorrow, "03:#{min}:#{sec}"}
"28" -> {:tomorrow, "04:#{min}:#{sec}"}
_ -> {:today, time_str}
end
|> format_string_to_seconds()
end
def format_string_to_seconds({:tomorrow, str}) do
{time_after_midnight_in_seconds, _} = Time.from_iso8601!(str) |> Time.to_seconds_after_midnight
{in_seconds, _} = Time.from_seconds_after_midnight(-1) |> Time.to_seconds_after_midnight
full_day_of_seconds = in_seconds + 1 # need to add one second back
total_seconds = full_day_of_seconds + time_after_midnight_in_seconds
{:ok, total_seconds}
end
def format_string_to_seconds({:today, str}) do
{:ok, offset} = Time.from_iso8601(str)
{total_seconds, _ } = Time.to_seconds_after_midnight(offset)
{:ok, total_seconds}
end
end
Now if you try this out
iex> Helper.parse_date_time_to_unix_seconds("20230225", "18:00:00")
1677348000
iex> DateTime.from_unix(1677348000)
{:ok, ~U[2023-02-25 18:00:00Z]}
# And works with dates that go beyond midnight.
# We can see in this example the date is coming back for the next day at 1:30 in the morning.
iex> Helper.parse_date_time_to_unix_seconds("20230225", "25:30:00")
1677348000
iex> DateTime.from_unix(1677375000)
{:ok, ~U[2023-02-26 01:30:00Z]} # see the next day it works
Looking for Feedback
My solution is working but I have a feeling like I might not be writing this in the most elixir way. Just looking for any feedback, tips or gotchas.