Repository: adobe/elixir-styler
Pull request #62 (2023-08-01 18:02:26)

Improve Date/DateTime interactions
This PR includes some improvements when interacting with `Date` or `DateTime`.

* Elixir v1.15 introduced `{Date,DateTime,NaiveDateTime,Time}.{after?/2,before?/2}`. This means that we could improve the readability of some Date/Time comparisons.
* I also took the chance to transform some `Timex` `defdelegate` to their native equivalent.

Finally, most of these additions only apply if we're running on Elixir v1.15, so I added new entries for Elixir and OTP matrix for CI purposes.

- [x] Please [sign Adobe's CLA](http://opensource.adobe.com/cla.html) if this is your first time contributing to an Adobe open source repo. Thanks!



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c2b3bb5..93df109 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ jobs:
     name: Ex${{matrix.elixir}}/OTP${{matrix.otp}}
     strategy:
       matrix:
-        elixir: ['1.14.2']
+        elixir: ['1.14.2', '1.15.0']
         otp: ['25.1.2']
     steps:
       - uses: actions/checkout@v3
diff --git a/lib/style/single_node.ex b/lib/style/single_node.ex
index 713043f..3447a0c 100644
--- a/lib/style/single_node.ex
+++ b/lib/style/single_node.ex
@@ -95,6 +95,23 @@ defmodule Styler.Style.SingleNode do
   defp style({{:., dm, [{:__aliases__, am, [:Logger]}, :warn]}, funm, args}),
     do: {{:., dm, [{:__aliases__, am, [:Logger]}, :warning]}, funm, args}
 
+  # Transform Timex defdelegates
+  defp style({{:., dm, [{:__aliases__, am, [:Timex]}, :today]}, funm, args}),
+    do: {{:., dm, [{:__aliases__, am, [:Date]}, :utc_today]}, funm, args}
+
+  defp style({{:., dm, [{:__aliases__, am, [:Timex]}, :now]}, funm, args}),
+    do: {{:., dm, [{:__aliases__, am, [:DateTime]}, :utc_now]}, funm, args}
+
+  if Version.match?(System.version(), ">= 1.15.0-dev") do
+    # {DateTime,NaiveDateTime,Time,Date}.compare(a, b) == :lt -> {DateTime,NaiveDateTime,Time,Date}.before?(a, b)
+    # {DateTime,NaiveDateTime,Time,Date}.compare(a, b) == :gt -> {DateTime,NaiveDateTime,Time,Date}.after?(a, b)
+    defp style({:==, _, [{{:., dm, [{:__aliases__, am, [mod]}, :compare]}, funm, args}, {:__block__, _, [result]}]})
+         when mod in ~w[DateTime NaiveDateTime Time Date]a and result in [:lt, :gt] do
+      fun = if result == :lt, do: :before?, else: :after?
+      {{:., dm, [{:__aliases__, am, [mod]}, fun]}, funm, args}
+    end
+  end
+
   # Remove parens from 0 arity funs (Credo.Check.Readability.ParenthesesOnZeroArityDefs)
   defp style({def, dm, [{fun, funm, []} | rest]}) when def in ~w(def defp)a and is_atom(fun),
     do: style({def, dm, [{fun, Keyword.delete(funm, :closing), nil} | rest]})


Test patch:
diff --git a/test/style/single_node_test.exs b/test/style/single_node_test.exs
index c231dcc..a3d8c23 100644
--- a/test/style/single_node_test.exs
+++ b/test/style/single_node_test.exs
@@ -21,6 +21,30 @@ defmodule Styler.Style.SingleNodeTest do
     assert_style("Logger.warn(foo, bar)", "Logger.warning(foo, bar)")
   end
 
+  test "Timex.now -> DateTime.utc_now" do
+    assert_style("Timex.now()", "DateTime.utc_now()")
+  end
+
+  test "Timex.today -> Date.utc_today" do
+    assert_style("Timex.today()", "Date.utc_today()")
+  end
+
+  if Version.match?(System.version(), ">= 1.15.0-dev") do
+    test "{DateTime,NaiveDateTime,Time,Date}.compare to {DateTime,NaiveDateTime,Time,Date}.before?" do
+      assert_style("DateTime.compare(foo, bar) == :lt", "DateTime.before?(foo, bar)")
+      assert_style("NaiveDateTime.compare(foo, bar) == :lt", "NaiveDateTime.before?(foo, bar)")
+      assert_style("Time.compare(foo, bar) == :lt", "Time.before?(foo, bar)")
+      assert_style("Date.compare(foo, bar) == :lt", "Date.before?(foo, bar)")
+    end
+
+    test "{DateTime,NaiveDateTime,Time,Date}.compare to {DateTime,NaiveDateTime,Time,Date}.after?" do
+      assert_style("DateTime.compare(foo, bar) == :gt", "DateTime.after?(foo, bar)")
+      assert_style("NaiveDateTime.compare(foo, bar) == :gt", "NaiveDateTime.after?(foo, bar)")
+      assert_style("Time.compare(foo, bar) == :gt", "Time.after?(foo, bar)")
+      assert_style("Time.compare(foo, bar) == :gt", "Time.after?(foo, bar)")
+    end
+  end
+
   describe "def / defp" do
     test "0-arity functions have parens removed" do
       assert_style("def foo(), do: :ok", "def foo, do: :ok")