JavaScript Datetime Utilities

Table of contents

  1. Overview
  2. Setup
  3. Accepted Date Values
  4. formatTime()
  5. timeAgo()
  6. timeDifference()
  7. now() and today()
  8. timeStamps() and toISO8601()
  9. parseDate()
  10. formatPhp()
  11. Defaults
  12. Example View
  13. 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 a FORMAT_* constant. The default is FORMAT_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 }. Set short to true for 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 as 7 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 is FORMAT_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. timeZone sets the zone date-only strings are anchored in, and fromTimeZone sets 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() says in 2 hours where Carbon says 2 hours from now.
  • Abbreviations in other languages. Short month and weekday names, and the short form of timeAgo(), can differ slightly. For example, German So instead of So., or Spanish mar instead of mar.. Full names and the long form of timeAgo() match.
  • Ordinal suffix. The S format character always uses English suffixes (st, nd, rd, th).
  • Timezone abbreviations. The T format character uses the browser’s names, so some zones show an offset such as GMT+1 instead of CET.
  • timeDifference(). The amount is translated, but the before and after suffix is always English.