ADR: Date Formatting and Timezones
Status
Accepted (May 2024)
This has been merged into master and released as part of the timezone/due-date-to-end-date release. All future handling of dates and times MUST follow this ADR.
Context
The Gantt feature has led to more awareness of our currently handling of dates/times and a greater need for consistency.
Previously, formatting of dates and times was done in different ways in different places in the app. The app also used timezones inconsistently. Some places used the browser's timezone, others used the server timezone, others used the member setting.
Decision
API
Times in API responses:
- MUST be ISO 8601 formatted
- MUST be in UTC and formatted in Zulu time, e.g. 2022-11-13T08:51:02Z
Times in API parameters:
- MUST be ISO 8601 formatted
- MAY be in UTC and formatted in Zulu time, e.g. 2022-11-13T08:51:02Z (milliseconds are optional)
Dates in API parameters:
- MUST be ISO 8601 formatted excluding the time, i.e. Y-m-d (e.g. 2022-01-03)
Dates in API responses: - MUST be ISO 8601 formatted excluding the time
Backend
Database storage: - Times in the database MUST be stored as timestamps in UTC
Function parameters & return types:
- Function parameter & return types that are a date MUST be CarbonImmutable (not Carbon, int, or string)
Output formatting: - Whenever a time is output (e.g. in a template or email), it MUST be done so specifying a timezone (that is, the developer must explicitly specify the timezone, not that the timezone must be visible to the user). It MUST NOT default to the server timezone - Formatting of dates SHOULD be done on the frontend or in phtml templates, not in models/controllers/API responses
DateTimeFormatter methods:
There are four formatting methods in DateTimeFormatter to help that will output the date/time to the correct timezone and format. These methods SHOULD be used whenever a date is formatted:
// Member has timezone set to "Australia/Perth"
// Organisation has timezone set to "America/New_York"
$time = CarbonImmutable::parse("2024-01-01T00:00:00Z")->utc();
$dateTimeFormatter->memberDate($time);
// 2024-01-01
$dateTimeFormatter->memberDateTime($time);
// 2024-01-01 08:00
$dateTimeFormatter->organisationDate($time);
// 2023-12-31
$dateTimeFormatter->organisationDateTime($time);
// 2023-12-31 19:00
Note that $dateTimeFormatter is always available in phtml templates, you will almost never have to instantiate DateTimeFormatter manually.
TypedController parsing:
On the backend, TypedController handles parsing ISO 8601 dates and date-times automatically, e.g.:
A request with ISO 8601 params:
Controller:
class SomeController extends Controller
{
use TypedController;
public function invoke(CarbonImmutable $dateWithTime, DateOnly $dateWithoutTime): void
{
// Both $dateWithTime and $dateWithoutTime will be set by
// TypedController based on their type without any manual parsing
...
}
}
Frontend
Output formatting: - Whenever a time is output, it MUST be done so specifying a timezone (that is, the developer must explicitly specify the timezone, not that the timezone must be visible to the user). They MUST NOT default to the browser timezone
dateHelper methods:
There are four formatting methods in dateHelper to help that will output the date/time to the correct timezone and format. These methods SHOULD be used whenever a date is formatted:
import { formatDate } from "@js/utils/dateHelper";
// Member has timezone set to "Australia/Perth"
// Organisation has timezone set to "America/New_York"
// const date = dayjs("2024-01-01T00:00:00.000Z");
formatDate.memberDate(date);
// 2024-01-01
formatDate.memberDateTime(date);
// 2024-01-01 08:00
formatDate.organisationDate(date);
// 2023-12-31
formatDate.organisationDateTime(date);
// 2023-12-31 19:00
These helper methods all accept dayjs objects, date objects, timestamps (with or without milliseconds), ISO8601 strings, or strings in the user's preferred format. E.g. all of the following will work:
// Timestamp in seconds
formatDate.memberDateTime(1710858000);
// Timestamp in milliseconds
formatDate.memberDateTime(1710858000 * 1000);
// Date object
formatDate.memberDateTime(new Date(1710858000 * 1000));
// Dayjs object
formatDate.memberDateTime(dayjs(1710858000 * 1000));
// ISO8601 string
formatDate.memberDateTime("2024-03-19T14:20:00.000Z");
// User format string
formatDate.memberDateTime("19 Mar 2024");
Consequences
This allows developers to format dates and times in a consistent way, without having to reinvent the wheel every time we need to format a date or convert it into a specific timezone.
Impact
Medium
Driver
@Marc North
Contributors
[Team]
Accepted Date
May 2024
Resources
- ISO 8601 — Date and time format
- Release Grid