What is Date & Time Localization?
Date and time localization is rendering temporal values the way each locale expects: component order (day before month or the reverse), separators, translated month and day names, 12-hour versus 24-hour clocks, and which day starts the week. The same instant renders as 07/09/2026 for a US user and 09/07/2026 for a French one, which is exactly why raw format strings are dangerous.
The robust approach is to store instants in UTC and localize only at the presentation edge, combining two independent transformations: time zone conversion for the user's location and locale-aware formatting for their language. PHP's IntlDateFormatter draws patterns from CLDR, and Carbon offers a more ergonomic route, backed by its own locale files, with locale-aware output via isoFormat() and translatedFormat(). The two steps are independent: a Spanish speaker in New York needs Spanish formatting applied to New York local time.
Prefer semantic format selection, "a long date", "a short date with time", over hardcoded patterns, so each locale supplies its own convention. Relative formats ("3 days ago") also need translation, and Carbon localizes those through the same locale machinery.
use Carbon\Carbon;
$date = Carbon::parse('2026-07-09 14:30', 'UTC');
$date->locale('es')->isoFormat('LL'); // "9 de julio de 2026"
$date->locale('de')->isoFormat('LLLL'); // "Donnerstag, 9. Juli 2026 14:30"
$date->locale('fr')->diffForHumans(); // "il y a ..." style output
Localized dates in Laravel
Laravel's date objects are Carbon instances, and Carbon follows the application locale: App::setLocale('de') also switches Carbon to German, through the service provider Carbon registers with Laravel. What decides whether the output is actually localized is the method you format with.
format() uses PHP's date() tokens and always prints English month and day names, whatever the locale. translatedFormat() takes the same tokens and translates the names. isoFormat() takes moment.js-style tokens and adds macro formats: L, LL, LLL and LLLL produce the short and long patterns each locale defines, so the order of day and month is never hardcoded in your views.
use Illuminate\Support\Carbon;
$date = Carbon::parse('2026-07-09 14:30', 'UTC')->locale('fr');
$date->format('l j F Y'); // "Thursday 9 July 2026"
$date->translatedFormat('l j F Y'); // "jeudi 9 juillet 2026"
$date->isoFormat('LL'); // "9 juillet 2026"
$date->locale('en_US')->isoFormat('L LT'); // "07/09/2026 2:30 PM"
$date->locale('en_GB')->isoFormat('L LT'); // "09/07/2026 14:30"
$date->locale('de')->isoFormat('L LT'); // "09.07.2026 14:30"
Time zones: convert first, then format
Keep config('app.timezone') at UTC and store every timestamp in UTC. Convert to the user's zone only when rendering, with setTimezone(), and format after that. The two steps answer different questions: the time zone decides which wall-clock time to show, the locale decides how to write it.
Converting at render time also keeps daylight saving time right, because the conversion applies the zone's rules for that specific date. Storing local times, or a fixed offset such as -05:00, breaks twice a year. Store each user's zone as an IANA identifier like America/New_York, never as an offset.
Laravel's defaults support this. now() and the timestamps Eloquent writes use the zone from config('app.timezone'), so leaving it at UTC keeps stored values consistent wherever your servers and users happen to be.
$date = Carbon::parse('2026-07-09 14:30', 'UTC');
$date->copy()->setTimezone('America/New_York')->locale('es')->isoFormat('LLL');
// "9 de julio de 2026 10:30"
Common mistakes
Using format() in views and wondering why month names stay in English is the first. Hardcoding patterns like m/d/Y, which is only right for the US, is the second. Assuming the week starts on Monday is the third: Carbon 3 uses the locale's first day of the week in startOfWeek(), so en_US starts on Sunday and de on Monday, and calendar views built on a fixed assumption shift by a day for some users.
Two failures are silent. Carbon falls back to English for a locale it does not know, so a typo in a locale code produces English dates rather than an error. And a queued mail or notification does not automatically render in the locale of the request that sent it: call locale() on the mailable or notification, or implement HasLocalePreference on the notifiable model, so dates and strings come out in the recipient's language.
When output has to match CLDR exactly, for example to agree with documents generated by other systems, use IntlDateFormatter instead of Carbon: IntlDateFormatter::formatObject($date, [IntlDateFormatter::LONG, IntlDateFormatter::SHORT], 'fr_FR') returns 9 juillet 2026 à 14:30 for 2026-07-09 14:30 UTC.
$date = Carbon::parse('2026-07-09');
$date->copy()->locale('en_US')->startOfWeek()->toDateString(); // "2026-07-05", a Sunday
$date->copy()->locale('de')->startOfWeek()->toDateString(); // "2026-07-06", a Monday
$date->copy()->locale('xx')->getLocale(); // "en"