What is the best way to get a specific time, tomorrow?

What is the best way to get a specific time, tomorrow?

I have a list of times a job may be enqueued for: [~T[07:00:00.000], ~T[11:00:00.000], ~T[14:00:00.000]]. If the time has past (ie. It’s 15:00:00), we want to enqueue the job to run tomorrow at 7:00:00. (To further complicate things this is in mountain time).

In order to do this, this is what I’ve come up with:

 now = "America/Denver" |> Timex.now()
times = [~T[07:00:00.000], ~T[11:00:00.000], ~T[14:00:00.000]]
# find the closest time in the future/ or default to the first time in the day
[first | _] = times
run_at = Enum.find(times, fn x -> Time.compare(x, now) == :gt end) || first 

if Time.diff(perform_time, now, :second) > 0 do
    # ... this is the easy part 
else
      {run_at_sec, _} = Time.to_seconds_after_midnight(run_at)

     #this is the part that feels overly complex but I don't know how to simplify it
      tomorrow =
        Timex.now()
        |> Timex.shift(days: 1)
        |> Timex.beginning_of_day()
        |> DateTime.to_unix()
        |> Kernel.+(run_at_sec)
        |> DateTime.from_unix()
        |> elem(1)
        |> DateTime.to_naive()
        |> Timex.to_datetime("America/Denver")

      DateTime.diff(tomorrow, now, :seconds)
    end

That pipeline feels insane but from my trial and error of this it’s the only thing I could find that works.

  1. Start with the beginning of the day, tomorrow.
  2. Convert to unix so we can add seconds
  3. Add seconds from the beginning of the day to the run time
  4. Convert back to datetime
  5. Make naive so we can apply a timezone before running the comparison

Is there a simple way/function that does this I’m missing?

1 Like

You actually don’t even need Timex for this. You can achieve your goal with the Elixir standard library functions:

defmodule TimeFinder do
  @timezone "America/Denver"
  @times [~T[07:00:00.000], ~T[11:00:00.000], ~T[14:00:00.000]]

  def find_datetime() do
    now = DateTime.now!(@timezone)

    potential_datetimes()
    |> ensure_valid_datetimes()
    |> Enum.find(fn datetime ->
      # Find the first potential datetime in the future
      DateTime.compare(datetime, now) == :gt
    end)
  end

  # Get all potential datetimes today and tomorrow
  defp potential_datetimes() do
    today = DateTime.now!(@timezone) |> DateTime.to_date()
    tomorrow = Date.add(today, 1)
    dates = [today, tomorrow]

    for date <- dates, time <- @times do
      DateTime.new(date, time, @timezone)
    end
  end

  # Filter out potential invalid datetimes
  defp ensure_valid_datetimes(datetimes) do
    datetimes
    |> Enum.filter(fn tuple -> elem(tuple, 0) == :ok end)
    |> Enum.map(fn {:ok, datetime} -> datetime end)
  end
end
4 Likes