Packages
exosql
0.2.74
0.2.88
0.2.87
0.2.86
0.2.85
0.2.84
0.2.83
0.2.82
0.2.81
0.2.80
0.2.79
0.2.78
0.2.77
0.2.76
0.2.75
0.2.74
0.2.73
0.2.72
0.2.71
0.2.70
0.2.69
0.2.68
0.2.67
0.2.65
0.2.64
0.2.63
0.2.62
0.2.60
0.2.59
0.2.58
0.2.57
0.2.56
0.2.55
0.2.54
0.2.53
0.2.51
0.2.50
0.2.49
0.2.48
0.2.47
0.2.46
0.2.45
0.2.44
0.2.43
0.2.42
0.2.41
0.2.40
0.2.39
0.2.38
0.2.37
0.2.36
0.2.35
0.2.33
0.2.32
0.2.31
0.2.30
0.2.29
0.2.28
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
Universal SQL engine for Elixir. This library implements the SQL logic to perform queries on user provided databases using a simple interface based on Foreign Data Wrappers from PostgreSQL.
Current section
Files
Jump to
Current section
Files
lib/datetime.ex
require Logger
defmodule ExoSQL.DateTime do
@moduledoc """
Helpers for datetime
"""
def strftime(dt, format) do
dt = to_datetime(dt)
# Simplifications, %i from sqlite is supported
format =
format
|> String.replace("%i", "%FT%T%z")
Timex.format!(dt, format, :strftime)
|> String.replace("+0000", "Z")
end
def to_datetime(n) when is_number(n) do
{:ok, dt} = DateTime.from_unix(n)
dt
end
def to_datetime(n) when is_binary(n) do
if String.contains?(n, "-") do
case String.length(n) do
10 ->
n = "#{n} 00:00:00Z"
{:ok, td, 0} = DateTime.from_iso8601(n)
td
16 ->
n = "#{n}:00Z"
{:ok, td, 0} = DateTime.from_iso8601(n)
td
19 ->
{:ok, td, 0} = DateTime.from_iso8601(n <> "Z")
td
20 ->
{:ok, td, 0} = DateTime.from_iso8601(n)
td
24 ->
{:ok, td, _} = DateTime.from_iso8601(n)
td
25 ->
{:ok, td, _} = DateTime.from_iso8601(n)
td
end
else
{:ok, unixtime} = ExoSQL.Utils.to_number(n)
# Logger.debug("To datetime #{inspect unixtime}")
to_datetime(unixtime)
end
end
def to_datetime(%DateTime{} = d), do: d
def to_datetime(other) do
raise ArgumentError, message: "cant convert #{inspect(other)} to date"
end
def to_datetime(dt, "-" <> _mod = orig) do
dt = to_datetime(dt)
ExoSQL.DateTime.Duration.datetime_add(dt, orig)
end
def to_datetime(dt, "+" <> _mod = orig) do
dt = to_datetime(dt)
ExoSQL.DateTime.Duration.datetime_add(dt, orig)
end
def to_datetime(dt, tz) do
dt = to_datetime(dt)
Timex.to_datetime(dt, tz)
end
@units %{
"minutes" => 60,
"hours" => 60 * 60,
"days" => 24 * 60 * 60,
"weeks" => 7 * 24 * 60 * 60
}
def datediff({:range, {start, end_}}), do: datediff(start, end_, "days")
def datediff({:range, {start, end_}}, units), do: datediff(start, end_, units)
def datediff(start, end_), do: datediff(start, end_, "days")
def datediff(%DateTime{} = start, %DateTime{} = end_, "years") do
if DateTime.compare(start, end_) == :gt do
-datediff(end_, start, "years")
else
years = end_.year - start.year
if end_.month < start.month do
years - 1
else
years
end
end
end
def datediff(%DateTime{} = start, %DateTime{} = end_, "months") do
if DateTime.compare(start, end_) == :gt do
-datediff(end_, start, "months")
else
years = datediff(start, end_, "years")
months =
if start.month <= end_.month do
end_.month - start.month
else
end_.month + 12 - start.month
end
months = years * 12 + months
# If start day is > end day means that it counted one month more.
if start.day > end_.day do
months - 1
else
months
end
end
end
def datediff(%DateTime{} = start, %DateTime{} = end_, "seconds") do
DateTime.diff(end_, start, :second)
end
def datediff(%DateTime{} = start, %DateTime{} = end_, unit) do
case Map.get(@units, unit) do
nil ->
raise "Unknown unit #{inspect unit} for datediff #{inspect @units}"
divider ->
div(DateTime.diff(end_, start, :second), divider)
end
end
def datediff(start, end_, unit) do
start = to_datetime(start)
end_ = to_datetime(end_)
datediff(start, end_, unit)
end
end
defmodule ExoSQL.DateTime.Duration do
@moduledoc """
Parses and manipulates 8601 durations
https://en.wikipedia.org/wiki/ISO_8601#Durations
"""
defstruct seconds: 0, days: 0, months: 0, years: 0
def parse(<<?P, rest::binary>>) do
parse(rest)
end
def parse(<<?+, rest::binary>>) do
parse(rest)
end
def parse(<<?-, rest::binary>>) do
case parse(rest) do
{:ok, res} ->
{:ok,
%ExoSQL.DateTime.Duration{
seconds: -res.seconds,
days: -res.days,
months: -res.months,
years: -res.years
}}
other ->
other
end
end
# no need for P
def parse(rest) do
case parse_split(rest) do
{:ok, parsed} ->
{:ok, parse_date(parsed, %ExoSQL.DateTime.Duration{})}
other ->
other
end
end
def parse!(str) do
case parse(str) do
{:ok, ret} -> ret
other -> throw(other)
end
end
defp parse_split(str) do
case Regex.run(~r/^(\d+[YMWDHS]|T)+$/, str) do
nil ->
{:error, :invalid_duration}
_other ->
ret =
Regex.scan(~r/(\d+)([YMWDHS])|(T)/, str)
|> Enum.map(fn
[_, a, b] ->
{n, ""} = Integer.parse(a)
{n, b}
["T" | _rest] ->
:t
end)
{:ok, ret}
end
end
defp parse_date([], acc), do: acc
defp parse_date([{n, "Y"} | rest], acc),
do: parse_date(rest, Map.update(acc, :years, n, &(&1 + n)))
defp parse_date([{n, "M"} | rest], acc),
do: parse_date(rest, Map.update(acc, :months, n, &(&1 + n)))
defp parse_date([{n, "W"} | rest], acc),
do: parse_date(rest, Map.update(acc, :days, n, &(&1 + n * 7)))
defp parse_date([{n, "D"} | rest], acc),
do: parse_date(rest, Map.update(acc, :days, n, &(&1 + n)))
defp parse_date([:t | rest], acc), do: parse_time(rest, acc)
defp parse_time([], acc), do: acc
defp parse_time([{n, "H"} | rest], acc),
do: parse_time(rest, Map.update(acc, :seconds, n * 60 * 60, &(&1 + n * 60 * 60)))
defp parse_time([{n, "M"} | rest], acc),
do: parse_time(rest, Map.update(acc, :seconds, n * 60, &(&1 + n * 60)))
defp parse_time([{n, "S"} | rest], acc),
do: parse_time(rest, Map.update(acc, :seconds, n, &(&1 + n)))
def is_negative(%ExoSQL.DateTime.Duration{
seconds: seconds,
days: days,
months: months,
years: years
}) do
seconds < 0 or days < 0 or months < 0 or years < 0
end
def datetime_add(date, duration) when is_binary(duration) do
datetime_add(date, parse!(duration))
end
def datetime_add(date, %ExoSQL.DateTime.Duration{} = duration) do
date =
case duration.seconds do
0 ->
date
seconds ->
Timex.shift(date, seconds: seconds)
end
date =
case duration.days do
0 ->
date
days ->
Timex.shift(date, days: days)
end
date =
case duration.months do
0 ->
date
months ->
Timex.shift(date, months: months)
end
date =
case duration.years do
0 ->
date
years ->
Timex.shift(date, years: years)
end
date
end
end