OpenHours is an Elixir package aimed to help with time calculations using business hours.
It's inspired by the amazing ruby gem biz developed by Zendesk.
The package can be installed by adding open_hours to your list of dependencies in mix.exs:
def deps do
[
{:open_hours, "~> 1.0"}
]
endOpenHours does not install a time zone database for you. It is database-agnostic and works with any library that implements the Calendar.TimeZoneDatabase protocol (e.g. tz, tzdata, or a custom implementation).
You must bring your own time zone database and configure it. Add the database package to your mix.exs dependencies and set it in your config:
# mix.exs
def deps do
[
{:open_hours, "~> 1.0"},
{:tz, "~> 0.28.2"} # or tzdata, or any Calendar.TimeZoneDatabase impl
]
end
# config/config.exs
config :elixir, :time_zone_database, Tz.TimeZoneDatabaseNote: OpenHours lists
tzas an optional dependency in its ownmix.exsfor development and testing only. Optional dependencies are not installed transitively — you must explicitly add your chosen time zone database to your application's dependencies.
In order to use OpenHours functions you first need a Schedule config:
schedule = %OpenHours.Schedule{
hours: %{
mon: [{~T[09:00:00], ~T[14:00:00]}, {~T[15:00:00], ~T[20:00:00]}],
tue: [{~T[09:00:00], ~T[14:00:00]}, {~T[15:00:00], ~T[20:00:00]}],
wed: [{~T[09:00:00], ~T[14:00:00]}, {~T[15:00:00], ~T[20:00:00]}],
thu: [{~T[09:00:00], ~T[14:00:00]}, {~T[15:00:00], ~T[20:00:00]}]
},
holidays: [
~D[2019-01-14]
],
shifts: [
{~D[2019-01-15], [{~T[10:00:00], ~T[15:00:00]}]}
],
breaks: [
{~D[2019-01-16], [{~T[17:00:00], ~T[20:00:00]}]}
],
time_zone: "Europe/Madrid"
}There are five settings to configure in a schedule:
hours: Map containing all the open hours intervals for a regular week.holidays: List of dates in which the business is closed.shifts: Special dates where the business has a different hour schedule.breaks: Special dates where the business has interruption intervals.time_zone: Time zone of the schedule.
OpenHours offers the following functionalities.
> at = DateTime.from_naive!(~N[2019-01-15 14:00:00], "Europe/Madrid")
#DateTime<2019-01-15 14:00:00+01:00 CET Europe/Madrid>
> OpenHours.Schedule.in_hours?(schedule, at)
true
> at = DateTime.from_naive!(~N[2019-01-14 12:00:00], "Europe/Madrid")
#DateTime<2019-01-14 12:00:00+01:00 CET Europe/Madrid>
> OpenHours.Schedule.in_hours?(schedule, at)
false> starts_at = DateTime.from_naive!(~N[2019-01-14 12:00:00], "Europe/Madrid")
#DateTime<2019-01-14 12:00:00+01:00 CET Europe/Madrid>
> ends_at = DateTime.from_naive!(~N[2019-01-16 22:00:00], "Europe/Madrid")
#DateTime<2019-01-16 22:00:00+01:00 CET Europe/Madrid>
> OpenHours.TimeSlot.between(schedule, starts_at, ends_at)
[
%OpenHours.TimeSlot{
ends_at: #DateTime<2019-01-15 15:00:00+01:00 CET Europe/Madrid>,
starts_at: #DateTime<2019-01-15 10:00:00+01:00 CET Europe/Madrid>
},
%OpenHours.TimeSlot{
ends_at: #DateTime<2019-01-16 14:00:00+01:00 CET Europe/Madrid>,
starts_at: #DateTime<2019-01-16 09:00:00+01:00 CET Europe/Madrid>
},
%OpenHours.TimeSlot{
ends_at: #DateTime<2019-01-16 17:00:00+01:00 CET Europe/Madrid>,
starts_at: #DateTime<2019-01-16 15:00:00+01:00 CET Europe/Madrid>
}
]Use OpenHours.Duration.between/3 to calculate the amount of business time (in seconds) between two DateTimes. Non-working hours, weekends, holidays, and breaks are excluded.
> starts_at = DateTime.from_naive!(~N[2019-01-15 10:00:00], "Europe/Madrid")
#DateTime<2019-01-15 10:00:00+01:00 CET Europe/Madrid>
> ends_at = DateTime.from_naive!(~N[2019-01-16 11:00:00], "Europe/Madrid")
#DateTime<2019-01-16 11:00:00+01:00 CET Europe/Madrid>
> OpenHours.Duration.between(schedule, starts_at, ends_at)
25200Use OpenHours.TimeSlot.next/3 and OpenHours.TimeSlot.previous/3 to find upcoming or past time slots from a given DateTime.
> at = DateTime.from_naive!(~N[2019-01-14 12:00:00], "Europe/Madrid")
#DateTime<2019-01-14 12:00:00+01:00 CET Europe/Madrid>
> OpenHours.TimeSlot.next(schedule, at, limit: 2)
[
%OpenHours.TimeSlot{
starts_at: #DateTime<2019-01-15 10:00:00+01:00 CET Europe/Madrid>,
ends_at: #DateTime<2019-01-15 15:00:00+01:00 CET Europe/Madrid>
},
%OpenHours.TimeSlot{
starts_at: #DateTime<2019-01-16 09:00:00+01:00 CET Europe/Madrid>,
ends_at: #DateTime<2019-01-16 14:00:00+01:00 CET Europe/Madrid>
}
]
> OpenHours.TimeSlot.previous(schedule, at, limit: 1)
[
%OpenHours.TimeSlot{
starts_at: #DateTime<2019-01-10 15:00:00+01:00 CET Europe/Madrid>,
ends_at: #DateTime<2019-01-10 20:00:00+01:00 CET Europe/Madrid>
}
]Use OpenHours.Offset.shift/3 to shift a DateTime forward or backward by a given amount of business time. The result skips over non-working hours, weekends, holidays, and breaks.
> at = DateTime.from_naive!(~N[2019-01-15 14:00:00], "Europe/Madrid")
#DateTime<2019-01-15 14:00:00+01:00 CET Europe/Madrid>
> OpenHours.Offset.shift(schedule, at, {2, :hour})
#DateTime<2019-01-16 10:00:00+01:00 CET Europe/Madrid>
> OpenHours.Offset.shift(schedule, at, {-3, :hour})
#DateTime<2019-01-15 11:00:00+01:00 CET Europe/Madrid>This software is licensed under the MIT license.