Date Formatting
What Is Date Formatting? In simple terms: Date formatting turns a date into text in a specific layout — 15/10/2026 for an Indian invoice, Thursday, 15 October 2026 for an event page, or 2026-10-15T14:
What Is Date Formatting?
In simple terms: Date formatting turns a date into text in a specific layout — 15/10/2026 for an Indian invoice, Thursday, 15 October 2026 for an event page, or 2026-10-15T14:05:09+05:30 for an API.
Technically: date() (for timestamps) and DateTimeInterface::format() (for date objects) accept a format string in which certain letters are replaced by parts of the date. Both use the same format characters.
date('d/m/Y', $timestamp); // procedural
$dateTime->format('d/m/Y'); // object-oriented
All examples below use this date:
$d = new DateTimeImmutable('2026-10-15 14:05:09', new DateTimeZone('Asia/Kolkata'));
Format Characters
Day
| Character | Meaning | Example |
|---|---|---|
d | Day of the month, two digits | 15 (05 for the 5th) |
j | Day of the month, no leading zero | 15 (5 for the 5th) |
D | Short weekday name | Thu |
l (lowercase L) | Full weekday name | Thursday |
N | ISO weekday number (1 = Monday … 7 = Sunday) | 4 |
w | Weekday number (0 = Sunday … 6 = Saturday) | 4 |
S | English ordinal suffix for the day | th (with j: 15th) |
z | Day of the year, starting at 0 | 287 |
Week, Month, Year
| Character | Meaning | Example |
|---|---|---|
W | ISO week number | 42 |
m | Month, two digits | 10 |
n | Month, no leading zero | 10 |
M | Short month name | Oct |
F | Full month name | October |
t | Number of days in the month | 31 |
L | Leap year (1 or 0) | 0 |
Y | Four-digit year | 2026 |
y | Two-digit year | 26 |
o | ISO week-numbering year | 2026 |
Time
| Character | Meaning | Example |
|---|---|---|
H | Hour, 24-hour format, two digits | 14 |
G | Hour, 24-hour format, no leading zero | 14 |
h | Hour, 12-hour format, two digits | 02 |
g | Hour, 12-hour format, no leading zero | 2 |
i | Minutes, two digits | 05 |
s | Seconds, two digits | 09 |
A / a | AM/PM or am/pm | PM |
v / u | Milliseconds / microseconds | 000 / 000000 |
Time Zone and Full Formats
| Character | Meaning | Example |
|---|---|---|
e | Time zone identifier | Asia/Kolkata |
T | Abbreviation | IST |
P | Offset with colon | +05:30 |
O | Offset without colon | +0530 |
c | ISO 8601 date and time | 2026-10-15T14:05:09+05:30 |
r | RFC 2822 date | Thu, 15 Oct 2026 14:05:09 +0530 |
U | Unix timestamp | 1792053309 |
Characters that are not format letters (spaces, -, /, :, ,) appear in the output unchanged.
Common Formats
<?php
$d = new DateTimeImmutable('2026-10-15 14:05:09', new DateTimeZone('Asia/Kolkata'));
$formats = [
'd/m/Y',
'd-m-Y',
'D, d M Y',
'l, jS F Y',
'h:i A',
'g:i a',
'Y-m-d H:i:s',
DATE_ATOM,
DATE_RFC2822,
];
foreach ($formats as $format) {
echo str_pad($format, 18) . $d->format($format) . "\n";
}
Output
d/m/Y 15/10/2026
d-m-Y 15-10-2026
D, d M Y Thu, 15 Oct 2026
l, jS F Y Thursday, 15th October 2026
h:i A 02:05 PM
g:i a 2:05 pm
Y-m-d H:i:s 2026-10-15 14:05:09
Y-m-d\TH:i:sP 2026-10-15T14:05:09+05:30
D, d M Y H:i:s O Thu, 15 Oct 2026 14:05:09 +0530
The last two lines show what the predefined constants contain: DATE_ATOM is Y-m-d\TH:i:sP (ISO 8601), and DATE_RFC2822 is D, d M Y H:i:s O.
Which Format for Which Purpose
| Purpose | Format | Example |
|---|---|---|
| Display in India | d/m/Y or d M Y | 15/10/2026, 15 Oct 2026 |
Database DATETIME columns | Y-m-d H:i:s | 2026-10-15 14:05:09 |
APIs, JSON, HTML <time> | DATE_ATOM / c (ISO 8601) | 2026-10-15T14:05:09+05:30 |
| Email headers | DATE_RFC2822 / r | Thu, 15 Oct 2026 14:05:09 +0530 |
HTTP headers (Expires, cookies) | DATE_RFC7231 (in UTC) | Thu, 15 Oct 2026 08:35:09 GMT |
| File names, sortable keys | Ymd_His | 20261015_140509 |
Displaying dates as 15/10/2026 is fine for Indian users, but never store or exchange dates in local display formats: 05/06/2026 means 5 June in India and 6 May in the United States. ISO 8601 (2026-06-05) is unambiguous and sorts correctly as text.
Escaping Letters in the Format
Every format letter is replaced — even inside words. To print a letter literally, put a backslash before it:
<?php
$d = new DateTimeImmutable('2026-10-15 14:05:09', new DateTimeZone('Asia/Kolkata'));
echo $d->format('l the jS of F') . "\n";
echo $d->format('l \t\h\e jS \o\f F') . "\n";
echo $d->format('\D\a\t\e: d/m/Y') . "\n";
Output
Thursday 3102Asia/Kolkata 15th 2026f October
Thursday the 15th of October
Date: 15/10/2026
In the first line, the word "the" became 3102Asia/Kolkata: t is the number of days in the month (31), h the 12-hour hour (02), and e the time zone identifier. Likewise, o in "of" became the ISO year 2026, while f is not a format letter and stayed unchanged. Escaping each letter (\t\h\e, \o\f) prints the words as written. Use single quotes for format strings, so PHP does not interpret \t as a tab character.
Ordinal Suffixes
<?php
foreach ([1, 2, 3, 4, 11, 12, 13, 21, 22, 23] as $day) {
$date = new DateTimeImmutable("2026-10-$day");
echo $date->format('jS') . ' ';
}
echo "\n";
Output
1st 2nd 3rd 4th 11th 12th 13th 21st 22nd 23rd
S handles the special cases (11th, 12th, 13th) correctly. It produces English suffixes only.
Localized Dates With IntlDateFormatter
date() and format() always produce English names (Thursday, October). For other languages, use IntlDateFormatter from the intl extension, which uses the internationalization data of the ICU library:
<?php
$d = new DateTimeImmutable('2026-10-15 14:05:09', new DateTimeZone('Asia/Kolkata'));
foreach (['en_IN', 'ta_IN', 'hi_IN'] as $locale) {
$formatter = new IntlDateFormatter(
$locale,
IntlDateFormatter::NONE,
IntlDateFormatter::NONE,
'Asia/Kolkata',
IntlDateFormatter::GREGORIAN,
'd MMMM y'
);
echo $locale . ': ' . $formatter->format($d) . "\n";
}
Output (exact text can vary slightly between ICU versions)
en_IN: 15 October 2026
ta_IN: 15 அக்டோபர் 2026
hi_IN: 15 अक्तूबर 2026
- The pattern
d MMMM yuses ICU pattern letters, which differ from PHP'sdate()letters (MMMMis the full month name,ythe year). - Instead of a pattern, predefined styles such as
IntlDateFormatter::LONGproduce the conventional format for each locale. - The
intlextension must be installed (it is included in most PHP distributions and Docker images).
Relative Formatting: "5 Minutes Ago"
Social feeds and comment sections often show relative times. A small function can produce them:
<?php
function timeAgo(DateTimeInterface $then, DateTimeInterface $now): string
{
$seconds = $now->getTimestamp() - $then->getTimestamp();
if ($seconds < 60) {
return 'just now';
}
if ($seconds >= 7 * 86400) {
return 'on ' . $then->format('d M Y');
}
foreach ([['day', 86400], ['hour', 3600], ['minute', 60]] as [$unit, $size]) {
if ($seconds >= $size) {
$count = intdiv($seconds, $size);
return $count . ' ' . $unit . ($count === 1 ? '' : 's') . ' ago';
}
}
return 'just now';
}
$now = new DateTimeImmutable('2026-10-15 14:05:09', new DateTimeZone('Asia/Kolkata'));
foreach (['-30 seconds', '-1 minute', '-5 minutes', '-3 hours', '-2 days', '-10 days'] as $offset) {
echo str_pad($offset, 12) . timeAgo($now->modify($offset), $now) . "\n";
}
Output
-30 seconds just now
-1 minute 1 minute ago
-5 minutes 5 minutes ago
-3 hours 3 hours ago
-2 days 2 days ago
-10 days on 05 Oct 2026
How It Works
- The difference in seconds is calculated from the timestamps.
- Anything under a minute is "just now"; anything older than a week shows the actual date, because "47 days ago" is less useful than a date.
- The loop picks the largest fitting unit and adds an "s" for plurals.
$nowis a parameter instead of being created inside the function, which makes the function testable with fixed dates — as in this example.
When displaying relative times, include the exact time in the HTML for accessibility and tooltips: <time datetime="2026-10-15T14:05:09+05:30" title="15 Oct 2026, 2:05 PM">5 minutes ago</time>.
Common Mistakes
Confusing m and i
Effect: H:m prints the month instead of minutes (14:10).
Fix: Minutes are i; month is m.
Confusing l and L
Effect: L prints 0/1 (leap year) instead of the weekday.
Fix: The weekday name is lowercase l.
Unescaped Letters in Text
Effect: Words inside the format are replaced by date values.
Fix: Escape literal letters with a backslash, and use single-quoted strings.
Storing Display Formats
Effect: Ambiguous dates (05/06/2026) and incorrect sorting.
Fix: Store Y-m-d / ISO 8601; format only for display.
Expecting date() to Translate
Effect: English month names on a Tamil or Hindi website.
Fix: Use IntlDateFormatter.
Best Practices
- Use
DateTimeImmutable::format()with explicit time zones for all display formatting. - Use ISO 8601 for storage and data exchange, and local formats only for display.
- Use the predefined constants (
DATE_ATOM,DATE_RFC2822,DATE_RFC7231) for standard formats. - Escape literal letters and keep format strings in single quotes.
- Use
IntlDateFormatterfor non-English output, and keep relative-time functions testable by passing "now" in.
Official reference: https://www.php.net/manual/en/datetime.format.php