Skip to content

Ecto.Type.cast/2 drops non-UTC DateTime offsets when casting to naive types #4774

Description

@lukaszsamson

Elixir version

any

Database and Version

any

Ecto Versions

3.14.1

Database Adapter and Versions (postgrex, myxql, etc)

any

Current behavior

Ecto.Type.cast/2 has no explicit %DateTime{} handling for :naive_datetime, :naive_datetime_usec, or :date. These values fall through to the generic map handling which reconstructs a value from its wall-clock fields and implicitly discard utc_offset, std_offset, and time_zone.

Wall time preservation is consistent with DateTime.to_naive/1 but here it's undocumented and seems accidental. It is inconsistent with other ecto paths that either normalize or reject non-UTC.

Repro:

berlin = %DateTime{
  calendar: Calendar.ISO,
  year: 2020,
  month: 6,
  day: 1,
  hour: 0,
  minute: 30,
  second: 7,
  microsecond: {8000, 6},
  utc_offset: 3600,
  std_offset: 3600,
  time_zone: "Europe/Berlin",
  zone_abbr: "CEST"
}

Ecto.Type.cast(:naive_datetime, berlin)
# => {:ok, ~N[2020-06-01 00:30:07]}

Ecto.Type.cast(:naive_datetime_usec, berlin)
# => {:ok, ~N[2020-06-01 00:30:07.008000]}

Ecto.Type.cast(:date, berlin)
# => {:ok, ~D[2020-06-01]}

Ecto.Type.cast(:utc_datetime_usec, berlin)
# => {:ok, ~U[2020-05-31 22:30:07.008000Z]}

Other Ecto datetime paths establish stronger UTC expectations:

  • Casting a non-UTC DateTime to :utc_datetime normalizes it to UTC.
  • Loading a DateTime as a naive datetime requires its timezone to be "Etc/UTC".
  • Dumping a DateTime directly as a naive datetime is rejected.

Expected behavior

Three solutions possible:

  1. Normalize to UTC before removing timezone information
  2. Reject non-UTC DateTime values, consistent with the stricter load/dump paths
  3. Preserve local wall time intentionally, but add explicit %DateTime{} clauses, documentation, and tests establishing that contract

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions