Skip to content

Frontend Dates and Timezones

This guide explains how Easy8 frontend code works with dates, datetimes, user timezone settings, and browser-local timezone behavior.

Why This Is Tricky

Frontend date handling in Easy8 has to reconcile three different contexts:

  1. Storage and API payloads.
  2. User timezone configured in Easy8.
  3. Browser-local timezone used by the JavaScript Date object.

Those contexts do not always match, and older endpoints do not always return datetime values in one uniform format. Because of that, frontend code should use the shared helper layer instead of parsing API strings ad hoc.

Some backend responses still mix values with timezone, without timezone, and date-only strings. When the semantic meaning of a value matters, do not rely on raw new Date(...) parsing alone.

Current Behavior

Date-only values

Canonical backend date-only format is YYYY-MM-DD.

Date-only values should stay date-only. They are not equivalent to "midnight in some timezone". In modern frontend code, use parseDateOnly when you need a Date instance from a date-only value.

parseDateOnly intentionally normalizes parsed values to local noon so that timezone conversion does not unexpectedly shift the calendar day.

Datetime values

Datetime values are more complicated:

  • some values arrive with an explicit timezone,
  • some arrive as UTC-like strings without a timezone suffix,
  • some older code paths still depend on legacy parsing rules.

The legacy normalization layer is EASY.utils.parseDate in app/frontend/src/easy_legacy_js/easy_extensions/utils/extensions_utils.js.

That helper:

  • validates the incoming ISO-like string,
  • uses the current user timezone from EASY.currentUser.time_zone_identifier,
  • normalizes the value through moment-timezone,
  • returns a JavaScript Date with the correct user-facing moment in time.

Modern frontend code should prefer the wrappers from app/frontend/src/shared/utils/date.ts instead of calling EASY.utils.parseDate directly.

Display formatting

Displayed formats come from global Easy settings:

  • window.EasySettings.getGlobalDateFormat.value
  • window.EasySettings.getGlobalDateTimeFormat.value

The frontend initializes dayjs and moment locale/week-start behavior in app/frontend/src/shared/utils/initDateLibs.ts.

Sending values back to the backend

When preparing values for requests, use the shared formatting helpers instead of hand-built strings.

Canonical backend formats are defined in app/frontend/src/shared/constants/date.ts:

  • BE_DATE_FORMAT = "YYYY-MM-DD"
  • BE_DATE_TIME_FORMAT = "YYYY-MM-DD HH:mm"
  • BE_TIME_FORMAT = "HH:mm"

Preferred Helpers

The shared frontend entrypoint for date work is app/frontend/src/shared/utils/date.ts.

Helper Use when
formatDate(date, withTime) You need a user-facing string formatted according to Easy settings.
formatDateForRequest(date, type) You need to send date, datetime, or time back to the backend.
getDateOrDateTimeFormat(withTime) You need the current global frontend display format.
crossBrowserISOString(date, withTime) You need a safer normalized ISO-like value before parsing.
parseDate(date) You need to parse a general date or datetime value through the shared helper layer.
parseDatetime(date) You explicitly work with datetime input and want shared parsing behavior.
parseDateOnly(date) You work with date-only values and want to preserve the calendar day safely.
dateISOStringParseZone(date) You need an ISO string while preserving zone semantics with dayjs.utc(true).
moveDate(date, time) You need to join a date and a time into one backend-formatted datetime value.

Prefer these patterns in application code:

import { formatDate, formatDateForRequest, parseDateOnly, parseDatetime } from "@/src/shared/utils/date";

const dueDate = parseDateOnly(issue.due_date);
const startedAt = parseDatetime(activity.started_at);

const dueDateLabel = formatDate(dueDate);
const startedAtLabel = formatDate(startedAt, true);

const payload = {
  due_date: formatDateForRequest(dueDate, "date"),
  started_at: formatDateForRequest(startedAt, "datetime"),
};

Practical Rules

  • Keep date-only and datetime code paths separate.
  • Prefer shared helpers over direct Date parsing.
  • Prefer explicit request formatting over manual string concatenation.
  • Use the global Easy date format helpers for user-facing output.
  • If you are fixing a timezone bug, inspect whether the value is date-only, datetime-with-zone, or datetime-without-zone before changing code.