defmodule Runbox.StateStore.ScheduleUtils do
@moduledoc """
Module contains functions to compute savepoint timestamps.
Savepoint timestamps are calculated as multiples of `schedule`.
"""
@typedoc "Timestamp defined as unix epoch in milliseconds"
@type epoch_ms :: non_neg_integer
@typedoc "Difference between two unix epoch timestamps in milliseconds"
@type epoch_ms_interval :: non_neg_integer
@typedoc "Interval (in seconds) between savepoint timestamps."
@type schedule :: epoch_ms_interval | :none
@doc """
Returns next savepoint based on given `schedule` and `timestamp`.
Next savepoint is calculated as next multiple of `schedule` larger or equal to given
`timestamp`.
## Examples
```
iex> Runbox.StateStore.ScheduleUtils.next_savepoint(60_000, 0)
60_000
iex> Runbox.StateStore.ScheduleUtils.next_savepoint(60_000, 10_000)
60_000
iex> Runbox.StateStore.ScheduleUtils.next_savepoint(60_000, 60_000)
120_000
```
"""
@spec next_savepoint(schedule(), epoch_ms()) :: epoch_ms()
def next_savepoint(schedule, timestamp) do
previous_savepoint(schedule, timestamp) + schedule
end
@doc """
Returns previous savepoint based on given `schedule` and `timestamp`.
Next savepoint is calculated as previous multiple of `schedule` lower or equal to given
`timestamp`.
## Examples
```
iex> Runbox.StateStore.ScheduleUtils.previous_savepoint(60_000, 30_000)
0
iex> Runbox.StateStore.ScheduleUtils.previous_savepoint(60_000, 60_000)
60_000
iex> Runbox.StateStore.ScheduleUtils.previous_savepoint(60_000, 90_000)
60_000
iex> Runbox.StateStore.ScheduleUtils.previous_savepoint(60_000, 120_000)
120_000
iex> Runbox.StateStore.ScheduleUtils.previous_savepoint(60_000, 150_000)
120_000
```
"""
@spec previous_savepoint(schedule(), epoch_ms()) :: epoch_ms()
def previous_savepoint(schedule, timestamp) do
previous_savepoint = div(timestamp, schedule) * schedule
if previous_savepoint <= 0 do
0
else
previous_savepoint
end
end
@doc """
Returns count of unreached savepoints.
## Examples
```
iex> Runbox.StateStore.ScheduleUtils.unreached_savepoints_count(60_000, 0, 120_000)
2
iex> Runbox.StateStore.ScheduleUtils.unreached_savepoints_count(60_000, 60_000, 120_000)
1
iex> Runbox.StateStore.ScheduleUtils.unreached_savepoints_count(1, 0, 120_000)
120_000
```
"""
def unreached_savepoints_count(schedule, from, to) do
((to - from) / schedule)
|> Float.floor(0)
|> Kernel.round()
end
@doc """
Returns unreached savepoints based on given `schedule` in range of given timestamps `(from; to>`
## Examples
```
iex> Runbox.StateStore.ScheduleUtils.unreached_savepoints(60_000, 0, 120_000)
[60_000, 120_000]
iex> Runbox.StateStore.ScheduleUtils.unreached_savepoints(60_000, 60_000, 120_000)
[120_000]
iex> Runbox.StateStore.ScheduleUtils.unreached_savepoints(60_000, 60_000, 180_000)
[120_000, 180_000]
```
"""
@spec unreached_savepoints(schedule(), epoch_ms(), epoch_ms()) :: [
epoch_ms()
]
def unreached_savepoints(schedule, from, to) do
get_unreached_savepoints(schedule, from, to, [])
end
@spec get_unreached_savepoints(
schedule(),
epoch_ms(),
epoch_ms(),
[epoch_ms()]
) :: [
epoch_ms()
]
defp get_unreached_savepoints(schedule, from, to, savepoints) do
from_next = next_savepoint(schedule, from)
if from_next <= to do
get_unreached_savepoints(schedule, from_next, to, [from_next | savepoints])
else
Enum.reverse(savepoints)
end
end
end