Repository: commanded/commanded
Pull request #254 (2019-02-06 15:55:07)

Use `DateTime` instead of `NaiveDateTime`.
See commanded/eventstore#150 for details.


Patch:
diff --git a/guides/Aggregates.md b/guides/Aggregates.md
index a7bedd6..c09c96f 100644
--- a/guides/Aggregates.md
+++ b/guides/Aggregates.md
@@ -208,13 +208,13 @@ This is a performance optimisation for aggregates that have a long lifetime or r
 
 Use the following options to configure snapshots for an aggregate:
 
-  - `snapshot_every` - snapshot aggregate state every so many events. Use
-    `nil` to disable snapshotting, or exclude the configuration entirely.
+- `snapshot_every` - snapshot aggregate state every so many events. Use
+  `nil` to disable snapshotting, or exclude the configuration entirely.
 
-  - `snapshot_version` - a non-negative integer indicating the version of
-    the aggregate state snapshot. Incrementing this version forces any
-    earlier recorded snapshots to be ignored when rebuilding aggregate
-    state.
+- `snapshot_version` - a non-negative integer indicating the version of
+  the aggregate state snapshot. Incrementing this version forces any
+  earlier recorded snapshots to be ignored when rebuilding aggregate
+  state.
 
 ### Example
 
@@ -233,7 +233,7 @@ Aggregate state will be serialized using the configured event store serializer,
 ```elixir
 defmodule ExampleAggregate do
   @derive Jason.Encoder
-  defstruct [:name, :date]
+  defstruct [:name, :datetime]
 end
 ```
 
@@ -242,10 +242,11 @@ You can use the `Commanded.Serialization.JsonDecoder` protocol to decode the par
 ```elixir
 defimpl Commanded.Serialization.JsonDecoder, for: ExampleAggregate do
   @doc """
-  Parse the date included in the aggregate state
+  Parse the datetime included in the aggregate state
   """
-  def decode(%ExampleAggregate{date: date} = state) do
-    %ExampleAggregate{state | date: NaiveDateTime.from_iso8601!(date)}
+  def decode(%ExampleAggregate{datetime: datetime} = state) do
+    {:ok, dt, _} = DateTime.from_iso8601(datetime)
+    %ExampleAggregate{state | datetime: dt}
   end
 end
 ```
diff --git a/guides/Events.md b/guides/Events.md
index 6f7eab9..9a6bb49 100644
--- a/guides/Events.md
+++ b/guides/Events.md
@@ -162,7 +162,7 @@ In addition to the metadata key/values you provide, the following system values
 - `stream_version` - the version of the stream for the event.
 - `causation_id` - an optional UUID identifier used to identify which command caused the event.
 - `correlation_id` - an optional UUID identifier used to correlate related commands/events.
-- `created_at` - the date/time, in UTC, indicating when the event was created.
+- `created_at` - the datetime, in UTC, indicating when the event was created.
 
 These key/value metadata pairs will use atom keys to differentiate them from the user provided metadata:
 
@@ -175,7 +175,7 @@ defmodule ExampleHandler do
     # %{
     #   :causation_id => "db1ebd30-7d3c-40f7-87cd-12cd9966df32",
     #   :correlation_id => "1599630b-9c38-433c-9548-0dd793108ba0",
-    #   :created_at => ~N[2017-10-30 11:19:56.178901],
+    #   :created_at => #DateTime<2017-10-30 11:19:56.178901Z>,
     #   :event_id => "5e4a0f38-385b-4d57-823b-a1bcf705b7bb",
     #   :event_number => 12345,
     #   :stream_id => "e42a588d-2cda-4314-a471-5d008cce01fc",
diff --git a/guides/Serialization.md b/guides/Serialization.md
index cd4afe0..1f958e7 100644
--- a/guides/Serialization.md
+++ b/guides/Serialization.md
@@ -19,7 +19,7 @@ You must derive the `Jason.Encoder` protocol for all structs you plan on encodin
 ```elixir
 defmodule ExampleEvent do
   @derive Jason.Encoder
-  defstruct [:name, :date]
+  defstruct [:name, :datetime]
 end
 ```
 
@@ -58,7 +58,7 @@ JSON serialization configuration depends upon which event store you are using wi
 
   Note that the two EventStore serializers do not implement the `Commanded.Serialization.JsonSerializer` decoding protocol.
 
-- Greg Young's Event Store:
+- Event Store:
 
   ```elixir
   config :commanded_extreme_adapter,
@@ -74,24 +74,24 @@ JSON serialization configuration depends upon which event store you are using wi
 
 ## Decoding event structs
 
-The `Commanded.Serialization.JsonSerializer` module provides an extension point to allow additional decoding of the deserialized data. This can be used for parsing data into valid types, such as date/time parsing from a string.
+The `Commanded.Serialization.JsonSerializer` module provides an extension point to allow additional decoding of the deserialized data. This can be used for parsing data into valid types, such as datetime parsing from a string.
 
-The example event below has an implementation of the `Commanded.Serialization.JsonDecoder` protocol to parse the date into a `NaiveDateTime` struct.
+The example event below has an implementation of the `Commanded.Serialization.JsonDecoder` protocol to parse the datetime into a `DateTime` struct.
 
 ```elixir
 defmodule ExampleEvent do
   @derive Jason.Encoder
-  defstruct [:name, :date]
+  defstruct [:name, :datetime]
 end
 
 defimpl Commanded.Serialization.JsonDecoder, for: ExampleEvent do
   @doc """
-  Parse the date included in the event.
+  Parse the datetime included in the event.
   """
-  def decode(%ExampleEvent{date: date} = event) do
-    %ExampleEvent{event |
-      date: NaiveDateTime.from_iso8601!(date)
-    }
+  def decode(%ExampleEvent{datetime: datetime} = event) do
+    {:ok, dt, _} = DateTime.from_iso8601(datetime)
+    
+    %ExampleEvent{event | datetime: dt}
   end
 end
 ```
@@ -107,22 +107,19 @@ Configure your own serializer in `config/config.exs` for the event store you are
 - Postgres EventStore:
 
   ```elixir
-  config :eventstore, EventStore.Storage,
-    serializer: MyApp.MessagePackSerializer
+  config :eventstore, EventStore.Storage, serializer: MyApp.MessagePackSerializer
   ```
 
-- Greg Young's Event Store:
+- Event Store:
 
   ```elixir
-  config :commanded_extreme_adapter,
-    serializer: MyApp.MessagePackSerializer
+  config :commanded_extreme_adapter, serializer: MyApp.MessagePackSerializer
   ```
 
 - In-memory event store:
 
   ```elixir
-  config :commanded, Commanded.EventStore.Adapters.InMemory,
-    serializer: MyApp.MessagePackSerializer
+  config :commanded, Commanded.EventStore.Adapters.InMemory, serializer: MyApp.MessagePackSerializer
   ```
 
 You *should not* change serialization format once your app has been deployed to production since Commanded will not be able to deserialize any existing events or snapshot data. In this scenario, to change serialization format you would need to also migrate your event store to the new format.
diff --git a/lib/commanded/event_store/adapters/in_memory.ex b/lib/commanded/event_store/adapters/in_memory.ex
index 3544771..97e39c0 100644
--- a/lib/commanded/event_store/adapters/in_memory.ex
+++ b/lib/commanded/event_store/adapters/in_memory.ex
@@ -356,7 +356,7 @@ defmodule Commanded.EventStore.Adapters.InMemory do
     } = state
 
     initial_stream_version = length(existing_events) + 1
-    now = NaiveDateTime.utc_now()
+    now = DateTime.utc_now()
 
     new_events =
       new_events
diff --git a/lib/commanded/event_store/recorded_event.ex b/lib/commanded/event_store/recorded_event.ex
index a38bdf8..b414bca 100644
--- a/lib/commanded/event_store/recorded_event.ex
+++ b/lib/commanded/event_store/recorded_event.ex
@@ -25,7 +25,7 @@ defmodule Commanded.EventStore.RecordedEvent do
 
     - `metadata` - the serialized event metadata as binary data.
 
-    - `created_at` - the date/time, in UTC, indicating when the event was
+    - `created_at` - the datetime, in UTC, indicating when the event was
       created.
 
   """
@@ -42,7 +42,7 @@ defmodule Commanded.EventStore.RecordedEvent do
           event_type: String.t(),
           data: binary(),
           metadata: binary(),
-          created_at: NaiveDateTime.t()
+          created_at: DateTime.t()
         }
 
   defstruct [
diff --git a/lib/commanded/event_store/snapshot_data.ex b/lib/commanded/event_store/snapshot_data.ex
index f2b1a3a..d4f3a22 100644
--- a/lib/commanded/event_store/snapshot_data.ex
+++ b/lib/commanded/event_store/snapshot_data.ex
@@ -9,7 +9,7 @@ defmodule Commanded.EventStore.SnapshotData do
           source_type: String.t(),
           data: binary,
           metadata: binary,
-          created_at: NaiveDateTime.t()
+          created_at: DateTime.t()
         }
 
   defstruct [
diff --git a/lib/commanded/middleware/logger.ex b/lib/commanded/middleware/logger.ex
index 7013840..71fc52a 100644
--- a/lib/commanded/middleware/logger.ex
+++ b/lib/commanded/middleware/logger.ex
@@ -47,9 +47,7 @@ defmodule Commanded.Middleware.Logger do
   def after_failure(%Pipeline{} = pipeline), do: pipeline
 
   defp delta(%Pipeline{assigns: %{started_at: started_at}}) do
-    now_usecs = DateTime.utc_now() |> DateTime.to_unix(:microsecond)
-    started_usecs = started_at |> DateTime.to_unix(:microsecond)
-    now_usecs - started_usecs
+    DateTime.diff(started_at, DateTime.utc_now(), :microsecond)
   end
 
   defp log_context(%Pipeline{command: command}) do
diff --git a/lib/commanded/subscriptions.ex b/lib/commanded/subscriptions.ex
index d9ff9ba..57097a4 100644
--- a/lib/commanded/subscriptions.ex
+++ b/lib/commanded/subscriptions.ex
@@ -137,7 +137,7 @@ defmodule Commanded.Subscriptions do
   def handle_info({:ack_event, name, stream_uuid, stream_version}, %Subscriptions{} = state) do
     %Subscriptions{streams_table: streams_table, started_at: started_at} = state
 
-    # track insert date as seconds since the subscriptions process was started
+    # track insert time as seconds since the subscriptions process was started
     # to support expiry of stale acks
     inserted_at_epoch = monotonic_time() - started_at
 


Test patch:
diff --git a/test/aggregates/snapshotting_test.exs b/test/aggregates/snapshotting_test.exs
index 0f33a64..d886183 100644
--- a/test/aggregates/snapshotting_test.exs
+++ b/test/aggregates/snapshotting_test.exs
@@ -241,23 +241,23 @@ defmodule Commanded.Aggregates.SnapshottingTest do
       configure_snapshotting(SnapshotAggregate, snapshot_every: 1, snapshot_version: 1)
     end
 
-    test "should parse date" do
+    test "should parse datetime" do
       aggregate_uuid = UUID.uuid4()
-      now = NaiveDateTime.utc_now()
+      now = DateTime.utc_now()
 
       create_aggregate(aggregate_uuid, now)
       restart_aggregate(SnapshotAggregate, aggregate_uuid)
 
       # Aggregate state should be decoded
-      expected_state = %SnapshotAggregate{name: "Example", date: now}
+      expected_state = %SnapshotAggregate{name: "Example", datetime: now}
 
       assert_aggregate_state(SnapshotAggregate, aggregate_uuid, expected_state)
       assert_aggregate_version(SnapshotAggregate, aggregate_uuid, 1)
     end
 
-    defp create_aggregate(aggregate_uuid, %NaiveDateTime{} = date) do
+    defp create_aggregate(aggregate_uuid, %DateTime{} = datetime) do
       execution_context = %ExecutionContext{
-        command: %Create{name: "Example", date: date},
+        command: %Create{name: "Example", datetime: datetime},
         handler: SnapshotAggregate,
         function: :execute
       }
diff --git a/test/aggregates/support/snapshot_aggregate.ex b/test/aggregates/support/snapshot_aggregate.ex
index cc45ab6..2dd65b2 100644
--- a/test/aggregates/support/snapshot_aggregate.ex
+++ b/test/aggregates/support/snapshot_aggregate.ex
@@ -1,18 +1,18 @@
 defmodule Commanded.Aggregates.SnapshotAggregate do
   @moduledoc false
   @derive Jason.Encoder
-  defstruct [:name, :date]
+  defstruct [:name, :datetime]
 
   defmodule Commands do
     defmodule Create do
-      defstruct [:name, :date]
+      defstruct [:name, :datetime]
     end
   end
 
   defmodule Events do
     defmodule Created do
       @derive Jason.Encoder
-      defstruct [:name, :date]
+      defstruct [:name, :datetime]
     end
   end
 
@@ -23,17 +23,17 @@ defmodule Commanded.Aggregates.SnapshotAggregate do
   # Command functions
 
   def execute(%SnapshotAggregate{name: nil}, %Create{} = command) do
-    %Create{name: name, date: date} = command
+    %Create{name: name, datetime: datetime} = command
 
-    %Created{name: name, date: date}
+    %Created{name: name, datetime: datetime}
   end
 
   # State mutators
 
   def apply(%SnapshotAggregate{} = state, %Created{} = event) do
-    %Created{name: name, date: date} = event
+    %Created{name: name, datetime: datetime} = event
 
-    %SnapshotAggregate{state | name: name, date: date}
+    %SnapshotAggregate{state | name: name, datetime: datetime}
   end
 end
 
@@ -41,11 +41,12 @@ alias Commanded.Aggregates.SnapshotAggregate
 
 defimpl Commanded.Serialization.JsonDecoder, for: SnapshotAggregate do
   @doc """
-  Parse the date included in the aggregate state
+  Parse the datetime included in the aggregate state
   """
   def decode(%SnapshotAggregate{} = state) do
-    %SnapshotAggregate{date: date} = state
+    %SnapshotAggregate{datetime: datetime} = state
 
-    %SnapshotAggregate{state | date: NaiveDateTime.from_iso8601!(date)}
+    {:ok, dt, _} = DateTime.from_iso8601(datetime)
+    %SnapshotAggregate{state | datetime: dt}
   end
 end
diff --git a/test/event/handle_event_test.exs b/test/event/handle_event_test.exs
index f449e13..e80c9e7 100644
--- a/test/event/handle_event_test.exs
+++ b/test/event/handle_event_test.exs
@@ -103,7 +103,7 @@ defmodule Commanded.Event.HandleEventTest do
         assert Map.get(metadata, :event_number) == 2
         assert Map.get(metadata, :stream_id) == stream_uuid
         assert Map.get(metadata, :stream_version) == 2
-        assert %NaiveDateTime{} = Map.get(metadata, :created_at)
+        assert %DateTime{} = Map.get(metadata, :created_at)
 
         assert GenServer.call(handler, :last_seen_event) == 2
       end)
@@ -132,7 +132,7 @@ defmodule Commanded.Event.HandleEventTest do
 
         Enum.each(received_metadata, fn metadata ->
           assert Map.get(metadata, :stream_id) == stream_uuid
-          assert %NaiveDateTime{} = Map.get(metadata, :created_at)
+          assert %DateTime{} = Map.get(metadata, :created_at)
         end)
       end)
     end
diff --git a/test/event_store/support/snapshot_test_case.ex b/test/event_store/support/snapshot_test_case.ex
index 2aa097d..50ac225 100644
--- a/test/event_store/support/snapshot_test_case.ex
+++ b/test/event_store/support/snapshot_test_case.ex
@@ -57,12 +57,12 @@ defmodule Commanded.EventStore.SnapshotTestCase do
         source_type: "#{__MODULE__}.BankAccountOpened",
         data: %BankAccountOpened{account_number: account_number, initial_balance: 1_000},
         metadata: nil,
-        created_at: DateTime.to_naive(DateTime.utc_now())
+        created_at: DateTime.utc_now()
       }
     end
 
     defp snapshot_timestamps_within_delta?(snapshot, other_snapshot, delta_seconds) do
-      NaiveDateTime.diff(snapshot.created_at, other_snapshot.created_at, :second) < delta_seconds
+      DateTime.diff(snapshot.created_at, other_snapshot.created_at, :second) < delta_seconds
     end
   end
 end
diff --git a/test/helpers/event_factory.ex b/test/helpers/event_factory.ex
index 50b2f51..86d2e1c 100644
--- a/test/helpers/event_factory.ex
+++ b/test/helpers/event_factory.ex
@@ -22,10 +22,8 @@ defmodule Commanded.Helpers.EventFactory do
         event_type: event.event_type,
         data: event.data,
         metadata: event.metadata,
-        created_at: now()
+        created_at: DateTime.utc_now()
       }
     end)
   end
-
-  defp now, do: DateTime.utc_now() |> DateTime.to_naive()
 end
diff --git a/test/serialization/json_decoder_test.exs b/test/serialization/json_decoder_test.exs
index a8486e1..8d53c30 100644
--- a/test/serialization/json_decoder_test.exs
+++ b/test/serialization/json_decoder_test.exs
@@ -6,28 +6,31 @@ defmodule Commanded.Serialization.JsonDecoderTest do
 
   defmodule ExampleEvent do
     @derive Jason.Encoder
-    defstruct [:name, :date]
+    defstruct [:name, :datetime]
   end
 
   defimpl JsonDecoder, for: ExampleEvent do
     @doc """
-    Parse the date included in the event.
+    Parse the datetime included in the event.
     """
-    def decode(%ExampleEvent{date: date} = event) do
-      %ExampleEvent{event | date: NaiveDateTime.from_iso8601!(date)}
+    def decode(%ExampleEvent{datetime: datetime} = event) do
+      {:ok, dt, _} = DateTime.from_iso8601(datetime)
+      %ExampleEvent{event | datetime: dt}
     end
   end
 
-  @serialized_event_json "{\"date\":\"2016-09-20T20:01:02\",\"name\":\"Ben\"}"
+  @serialized_event_json "{\"datetime\":\"2016-09-20T20:01:02Z\",\"name\":\"Ben\"}"
 
   test "should serialize value to JSON" do
-    event = %ExampleEvent{name: "Ben", date: ~N[2016-09-20 20:01:02]}
+    {:ok, dt, _} = DateTime.from_iso8601("2016-09-20 20:01:02Z")
+    event = %ExampleEvent{name: "Ben", datetime: dt}
 
     assert JsonSerializer.serialize(event) == @serialized_event_json
   end
 
   test "should allow decoding of deserialized value from JSON" do
-    event = %ExampleEvent{name: "Ben", date: ~N[2016-09-20 20:01:02]}
+    {:ok, dt, _} = DateTime.from_iso8601("2016-09-20 20:01:02Z")
+    event = %ExampleEvent{name: "Ben", datetime: dt}
     type = Atom.to_string(event.__struct__)
 
     assert JsonSerializer.deserialize(@serialized_event_json, type: type) == event