Skip to content

Commit 822b821

Browse files
authored
Merge pull request #1203 from code-corps/1202-fix-modified-at-comparisons
Fix modified_at comparisons to use seconds granularity
2 parents 315f995 + 592e28b commit 822b821

2 files changed

Lines changed: 33 additions & 13 deletions

File tree

lib/code_corps/validators/time_validator.ex

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,32 @@ defmodule CodeCorps.Validators.TimeValidator do
77

88
@doc """
99
Validates the new time is not before the previous time.
10+
11+
Works at second-level accuracy by truncating both timestamps to the second.
1012
"""
1113
def validate_time_not_before(%{data: data} = changeset, field) do
1214
previous_time = Map.get(data, field)
13-
current_time = Changeset.get_change(changeset, field)
14-
case current_time do
15+
new_time = Changeset.get_change(changeset, field)
16+
case new_time do
1517
nil -> changeset
16-
_ -> do_validate_time_not_before(changeset, field, previous_time, current_time)
18+
_ -> do_validate_time_not_before(changeset, field, previous_time, new_time)
1719
end
1820
end
1921

20-
defp do_validate_time_not_before(changeset, field, previous_time, current_time) do
21-
case Timex.before?(current_time, previous_time) do
22+
defp do_validate_time_not_before(changeset, field, previous_time, new_time) do
23+
previous_time = previous_time |> truncate(:second)
24+
new_time = new_time |> truncate(:second)
25+
case Timex.before?(new_time, previous_time) do
2226
true -> Changeset.add_error(changeset, field, "cannot be before the last recorded time")
2327
false -> changeset
2428
end
2529
end
30+
31+
# TODO: Replace this with DateTime.truncate/2 when Elixir 1.6 releases
32+
@spec truncate(DateTime.t, :microsecond | :millisecond | :second) :: DateTime.t
33+
def truncate(%DateTime{microsecond: microsecond} = datetime, precision) do
34+
%{datetime | microsecond: do_truncate(microsecond, precision)}
35+
end
36+
37+
defp do_truncate(_, :second), do: {0, 0}
2638
end

test/lib/code_corps/validators/time_validator_test.exs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,40 @@ defmodule CodeCorps.Validators.TimeValidatorTest do
88
describe "validate_time_not_before/2" do
99
test "when the time happened before" do
1010
# set the time to 1 day before the previous (recorded) time
11-
current_time = @previous_time |> Timex.shift(days: -1)
12-
changeset = cast_times(@previous_time, current_time, :modified_at)
11+
new_time = @previous_time |> Timex.shift(days: -1)
12+
changeset = cast_times(@previous_time, new_time, :modified_at)
1313
changeset = changeset |> validate_time_not_before(:modified_at)
1414
refute changeset.valid?
1515
end
1616

1717
test "when the time happened at the same time" do
18-
current_time = @previous_time
19-
changeset = cast_times(@previous_time, current_time, :modified_at)
18+
new_time = @previous_time
19+
changeset = cast_times(@previous_time, new_time, :modified_at)
20+
changeset = changeset |> validate_time_not_before(:modified_at)
21+
assert changeset.valid?
22+
end
23+
24+
test "when the time happened at the same second but with microseconds of difference" do
25+
previous_time = @previous_time |> Timex.shift(milliseconds: 500)
26+
new_time = previous_time |> truncate(:second)
27+
changeset = cast_times(previous_time, new_time, :modified_at)
2028
changeset = changeset |> validate_time_not_before(:modified_at)
2129
assert changeset.valid?
2230
end
2331

2432
test "when the time happened after" do
2533
# set the time to 1 day after the previous (recorded) time
26-
current_time = @previous_time |> Timex.shift(days: 1)
27-
changeset = cast_times(@previous_time, current_time, :modified_at)
34+
new_time = @previous_time |> Timex.shift(days: 1)
35+
changeset = cast_times(@previous_time, new_time, :modified_at)
2836
changeset = changeset |> validate_time_not_before(:modified_at)
2937
assert changeset.valid?
3038
end
3139
end
3240

33-
defp cast_times(previous_time, current_time, field) do
41+
defp cast_times(previous_time, new_time, field) do
3442
data = Map.put(%{}, field, previous_time)
3543
fields = Map.put(%{}, field, :utc_datetime)
36-
params = Map.put(%{}, field, current_time)
44+
params = Map.put(%{}, field, new_time)
3745
Ecto.Changeset.cast({data, fields}, params, [field])
3846
end
3947
end

0 commit comments

Comments
 (0)