-
Notifications
You must be signed in to change notification settings - Fork 405
Expand file tree
/
Copy pathhelpers.ex
More file actions
162 lines (135 loc) · 4.74 KB
/
helpers.ex
File metadata and controls
162 lines (135 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
defmodule Timex.DateTime.Helpers do
@moduledoc false
alias Timex.{Types, Timezone, TimezoneInfo, AmbiguousDateTime, AmbiguousTimezoneInfo}
@type precision :: -1 | 0..6
@set_option_priority %{
year: 1,
month: 2,
day: 3,
hour: 4,
minute: 5,
second: 6
}
@doc """
Constructs an empty NaiveDateTime, for internal use only
"""
def empty(), do: Timex.NaiveDateTime.new!(0, 1, 1, 0, 0, 0)
@doc """
Constructs a DateTime from an Erlang date or datetime tuple and a timezone.
Intended for internal use only.
"""
@spec construct(Types.date(), Types.valid_timezone()) ::
DateTime.t() | AmbiguousDateTime.t() | {:error, term}
@spec construct(Types.datetime(), Types.valid_timezone()) ::
DateTime.t() | AmbiguousDateTime.t() | {:error, term}
@spec construct(Types.microsecond_datetime(), Types.valid_timezone()) ::
DateTime.t() | AmbiguousDateTime.t() | {:error, term}
@spec construct(Types.microsecond_datetime(), precision, Types.valid_timezone()) ::
DateTime.t() | AmbiguousDateTime.t() | {:error, term}
def construct({_, _, _} = date, timezone) do
construct({date, {0, 0, 0, 0}}, 0, timezone)
end
def construct({{_, _, _} = date, {h, mm, s}}, timezone) do
construct({date, {h, mm, s, 0}}, 0, timezone)
end
def construct({{_, _, _} = date, {_, _, _, _} = time}, timezone) do
construct({date, time}, -1, timezone)
end
def construct({{_, _, _} = date, {_, _, _, _} = time}, precision, timezone) do
construct({date, time}, precision, timezone, :wall)
end
def construct({{y, m, d} = date, {h, mm, s, us}}, precision, timezone, utc_or_wall) do
seconds_from_zeroyear = :calendar.datetime_to_gregorian_seconds({date, {h, mm, s}})
case Timezone.name_of(timezone) do
{:error, _} = err ->
err
tzname ->
case Timezone.resolve(tzname, seconds_from_zeroyear, utc_or_wall) do
{:error, _} = err ->
err
%TimezoneInfo{} = tz ->
%DateTime{
:year => y,
:month => m,
:day => d,
:hour => h,
:minute => mm,
:second => s,
:microsecond => construct_microseconds(us, precision),
:time_zone => tz.full_name,
:zone_abbr => tz.abbreviation,
:utc_offset => tz.offset_utc,
:std_offset => tz.offset_std
}
%AmbiguousTimezoneInfo{before: b, after: a} ->
bd = %DateTime{
:year => y,
:month => m,
:day => d,
:hour => h,
:minute => mm,
:second => s,
:microsecond => construct_microseconds(us, precision),
:time_zone => b.full_name,
:zone_abbr => b.abbreviation,
:utc_offset => b.offset_utc,
:std_offset => b.offset_std
}
ad = %DateTime{
:year => y,
:month => m,
:day => d,
:hour => h,
:minute => mm,
:second => s,
:microsecond => construct_microseconds(us, precision),
:time_zone => a.full_name,
:zone_abbr => a.abbreviation,
:utc_offset => a.offset_utc,
:std_offset => a.offset_std
}
%AmbiguousDateTime{before: bd, after: ad}
end
end
end
def construct_microseconds({us, p}) when is_integer(us) and is_integer(p) do
construct_microseconds(us, p)
end
def construct_microseconds(us) when is_integer(us) do
construct_microseconds(us, -1)
end
# Input precision of -1 means it should be recalculated based on the value
def construct_microseconds(0, -1), do: {0, 0}
def construct_microseconds(0, p), do: {0, p}
def construct_microseconds(n, -1), do: {n, precision(n)}
def construct_microseconds(n, p), do: {to_precision(n, p), p}
def to_precision(0, _p), do: 0
def to_precision(_, 0), do: 0
def to_precision(us, p) do
case precision(us) do
detected_p when detected_p > p ->
# Convert to lower precision
pow = trunc(:math.pow(10, detected_p - p))
Integer.floor_div(us, pow) * pow
_detected_p ->
# Already correct precision or less precise
us
end
end
def precision(0), do: 0
def precision(n) when is_integer(n) do
ns = Integer.to_string(n)
n_width = byte_size(ns)
trimmed = byte_size(String.trim_trailing(ns, "0"))
new_p = 6 - (n_width - trimmed)
if new_p >= 6 do
6
else
new_p
end
end
def sort_options(options) when is_list(options) do
Enum.sort_by(options, fn {k, _} -> Map.get(@set_option_priority, k, 99) end)
end
def sort_options(options), do: options
end