JavaScript Datetime Utilities
Table of contents
- Overview
- Setup
- Accepted Date Values
- formatTime()
- timeAgo()
- timeDifference()
- now() and today()
- timeStamps() and toISO8601()
- parseDate()
- formatPhp()
- Defaults
- Example View
- Differences from PHP
1. Overview Table of Contents
The datetime utilities bring the PHP DateTime helpers to React views. The same function names, format strings, and FORMAT_* constants work on both sides, so a date looks the same whether PHP or React renders it.
The utilities use the browser’s built-in Intl APIs and have no dependencies.
2. Setup Table of Contents
Import the DateTime namespace, or individual functions, from @chappy/utils:
import { DateTime } from '@chappy/utils';
import { formatTime, timeAgo } from '@chappy/utils';
Add these values to your .env file so React uses the same timezone and locale as PHP:
VITE_TIME_ZONE=${TIME_ZONE}
VITE_LOCALE=${LOCALE}
Without VITE_TIME_ZONE, dates display in each visitor’s own timezone.
3. Accepted Date Values Table of Contents
Every function that takes a date accepts the formats PHP sends. Each one is read the same way the PHP side reads it:
| Value | Example | Read As |
|---|---|---|
ISO 8601 with Z or an offset |
2026-01-15T20:30:45Z |
That exact moment. This is what model date casting sends. |
| Database format | 2026-01-15 20:30:45 |
UTC, the same as PHP. |
| Date only | 1990-07-04 |
That calendar date, never shifted to another day. |
| Number | 1768509045 |
A Unix timestamp in seconds, the same as PHP. |
Date object |
new Date() |
That moment. |
Passing a database-format string to new Date() reads it as the browser’s local time, and passing a date-only string reads it as midnight UTC. Both display the wrong value in most timezones. These utilities avoid both problems, so pass dates from PHP to them directly.
Empty values (null, undefined, '', and 0000-00-00 00:00:00) are treated as no date. Values that cannot be read unambiguously, such as 03/15/2026, throw a RangeError.
4. formatTime() Table of Contents
Formats a date for display. Mirrors DateTime::formatTime().
Parameters:
{Date|string|number} value- The date. See Accepted Date Values.{string} format- A PHP format string or aFORMAT_*constant. The default isFORMAT_12_HOUR.{object} options- Optional{ timeZone, locale, fromTimeZone }.
Returns:
{string}- The formatted date, or an empty string for empty values.
Example:
DateTime.formatTime(user.created_at, DateTime.FORMAT_HUMAN_READABLE);
DateTime.formatTime(user.created_at, 'H:i', { timeZone: 'Europe/Berlin' });
DateTime.formatTime(user.created_at, 'l j F Y', { locale: 'fr' });
Output for 2026-01-15T20:30:45Z with VITE_TIME_ZONE=America/New_York:
Thursday, January 15, 2026 3:30 PM
21:30
jeudi 15 janvier 2026
All FORMAT_* constants from the Date and Time page are available, either on the DateTime namespace or as named exports.
5. timeAgo() Table of Contents
Describes how long ago a date was. Mirrors DateTime::timeAgo().
Parameters:
{Date|string|number} value- The date.{object} options- Optional{ locale, short, fromTimeZone }. Setshorttotruefor the compact form.
Returns:
{string}- The elapsed time.
Example:
DateTime.timeAgo(acl.created_at);
DateTime.timeAgo(acl.created_at, { short: true });
DateTime.timeAgo(acl.created_at, { locale: 'fr' });
Output for a date two hours ago:
2 hours ago
2h ago
il y a 2 heures
Like Carbon, the result truncates instead of rounding (1 hour 59 minutes is 1 hour ago) and uses calendar months and years.
6. timeDifference() Table of Contents
Describes the difference between two dates. Mirrors DateTime::timeDifference().
Parameters:
{Date|string|number} start- The first date.{Date|string|number} end- The second date.{object} options- Optional{ locale, fromTimeZone }.
Returns:
{string}- The difference, such as7 hours before.
DateTime.timeDifference('2025-03-09 08:00:00', '2025-03-09 15:30:45'); // 7 hours before
7. now() and today() Table of Contents
Return the current time, or midnight of the current day, as a formatted string. Mirror DateTime::now() and DateTime::today().
Parameters:
{string} format- The default isFORMAT_12_HOUR.{object} options- Optional{ timeZone, locale }.
DateTime.now(); // 2026-01-15 03:30:45 PM
DateTime.now('H:i', { timeZone: 'Europe/Berlin' }); // 21:30
DateTime.today(DateTime.FORMAT_FRIENDLY_DATE); // January 15, 2026
8. timeStamps() and toISO8601() Table of Contents
timeStamps() returns the current time in the database format, in UTC. Mirrors DateTime::timeStamps().
toISO8601(value, options) returns ISO 8601 with the offset of the display timezone.
DateTime.timeStamps(); // 2026-01-15 20:30:45
DateTime.toISO8601(user.created_at); // 2026-01-15T15:30:45-05:00
9. parseDate() Table of Contents
Converts a value into a JavaScript Date using the rules in Accepted Date Values. Use it instead of new Date() whenever a value comes from PHP.
Parameters:
{Date|string|number} value- The value to convert.{object} options- Optional.timeZonesets the zone date-only strings are anchored in, andfromTimeZonesets the zone of database-format strings (the default is'UTC').
Returns:
{Date|null}- The date, or null for empty values.
Throws:
RangeError- If the value cannot be read.
const created = DateTime.parseDate(user.created_at);
const isRecent = Date.now() - created.getTime() < 24 * 60 * 60 * 1000;
10. formatPhp() Table of Contents
Formats a Date object using PHP format characters. formatTime() uses it internally; call it directly when you already have a Date.
Parameters:
{Date} date- The date to format.{string} format- A PHP format string. Escape literal characters with a backslash.{object} options- Optional{ timeZone, locale }.
Supported format characters: d D j l N S w z F M m n t L Y y a A g G h H i s u v e T P p O c r U
Example output is for 2026-01-15 20:30:45.123 UTC displayed in America/New_York with the en locale.
| Character | Description | Example Output |
|---|---|---|
| Day | ||
d |
Day of the month, 2 digits with leading zero | 15 (01 to 31) |
D |
Short day name, translated by locale | Thu |
j |
Day of the month without leading zero | 15 (1 to 31) |
l |
Full day name, translated by locale | Thursday |
N |
ISO 8601 day of the week, Monday = 1 | 4 (1 to 7) |
S |
English ordinal suffix for the day, use with j |
th (st, nd, rd, th) |
w |
Day of the week, Sunday = 0 | 4 (0 to 6) |
z |
Day of the year, starting from 0 | 14 (0 to 365) |
| Month | ||
F |
Full month name, translated by locale | January |
M |
Short month name, translated by locale | Jan |
m |
Month number with leading zero | 01 (01 to 12) |
n |
Month number without leading zero | 1 (1 to 12) |
t |
Number of days in the month | 31 (28 to 31) |
| Year | ||
L |
Leap year flag | 0 (1 if leap year) |
Y |
Full year | 2026 |
y |
2-digit year | 26 |
| Time | ||
a |
Lowercase meridiem, translated by locale | pm |
A |
Uppercase meridiem, translated by locale | PM |
g |
12-hour format without leading zero | 3 (1 to 12) |
G |
24-hour format without leading zero | 15 (0 to 23) |
h |
12-hour format with leading zero | 03 (01 to 12) |
H |
24-hour format with leading zero | 15 (00 to 23) |
i |
Minutes with leading zero | 30 (00 to 59) |
s |
Seconds with leading zero | 45 (00 to 59) |
u |
Microseconds | 123000 |
v |
Milliseconds | 123 |
| Timezone | ||
e |
Timezone identifier | America/New_York |
T |
Timezone abbreviation | EST (EDT during daylight saving time) |
P |
UTC offset with colon | -05:00 |
p |
Same as P, but returns Z for UTC |
-05:00 (Z in UTC) |
O |
UTC offset without colon | -0500 |
| Full Date/Time | ||
c |
ISO 8601 date | 2026-01-15T15:30:45-05:00 |
r |
RFC 2822 date | Thu, 15 Jan 2026 15:30:45 -0500 |
U |
Seconds since the Unix epoch (January 1, 1970 UTC) | 1768509045 |
To print one of these characters literally, escape it with a backslash. For example, '\T\o\d\a\y \i\s l' outputs Today is Thursday. In JavaScript strings, double the backslash: '\\T\\o\\d\\a\\y \\i\\s l'.
DateTime.formatPhp(new Date(), '\\T\\o\\d\\a\\y \\i\\s l'); // Today is Thursday
11. Defaults Table of Contents
The default timezone comes from VITE_TIME_ZONE, falling back to the browser’s timezone. The default locale comes from VITE_LOCALE, falling back to 'en'.
setDateDefaults({ timeZone, locale }) changes the defaults at runtime, for example to show times in the visitor’s own timezone. It throws a RangeError for an invalid timezone. getDateDefaults() returns the current values.
import { setDateDefaults } from '@chappy/utils';
setDateDefaults({ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone });
12. Example View Table of Contents
Show relative time in the table and the full date on hover:
import { DateTime } from '@chappy/utils';
function AclRow({ acl }) {
return (
<tr>
<td>{acl.acl}</td>
<td title={DateTime.formatTime(acl.created_at, DateTime.FORMAT_HUMAN_READABLE)}>
{DateTime.timeAgo(acl.created_at)}
</td>
</tr>
);
}
The controller passes models straight through. Add $casts to the model so dates arrive in ISO 8601:
$this->view->renderJsx('admindashboard.ManageAcls', [
'usedAcls' => ACLService::usedACLs(),
]);
13. Differences from PHP Table of Contents
English output matches the PHP helpers exactly. A few differences remain because Carbon and the browser use different translation data:
- Options objects. JavaScript functions take an options object, such as
{ timeZone, locale }, instead of PHP’s positional parameters. - Future times.
timeAgo()saysin 2 hourswhere Carbon says2 hours from now. - Abbreviations in other languages. Short month and weekday names, and the short form of
timeAgo(), can differ slightly. For example, GermanSoinstead ofSo., or Spanishmarinstead ofmar.. Full names and the long form oftimeAgo()match. - Ordinal suffix. The
Sformat character always uses English suffixes (st,nd,rd,th). - Timezone abbreviations. The
Tformat character uses the browser’s names, so some zones show an offset such asGMT+1instead ofCET. - timeDifference(). The amount is translated, but the
beforeandaftersuffix is always English.