phattarachai/thaidate
phattarachai/thaidate is a Laravel/PHP package for presenting dates in Thai format. It converts Carbon dates to Thai month names and Buddhist Era years, supports Thai numerals and localization, and offers handy helpers for Blade views and everyday Thai date formatting.
Install
composer require phattarachai/thaidate
The package is a thin wrapper around Carbon that exposes a global thaidate() helper. In Laravel it is auto-discovered (service provider + files autoload), so there is nothing to register by hand. In plain PHP projects just require vendor/autoload.php and the function is available.
First thing to check before writing any code
Open vendor/phattarachai/thaidate/src/ and confirm the helper's signature and return type. Most packages in this family return a formatted string when a format is passed and a Carbon instance when it isn't, but you should verify this rather than assume — the distinction changes every call site you write. Also check composer.json for the nesbot/carbon and php constraints; that tells you whether you can use Carbon 3 idioms.
60-second smoke test
php artisan tinker
>>> thaidate('2024-01-01')
>>> thaidate() // today
>>> thaidate('2024-01-01', 'j F Y')
A known date like 2024-01-01 should render the year as 2567 (Buddhist Era = Gregorian + 543). If it shows 2024, you are looking at a locale-only package and need to add the era offset yourself.
First real use case
Replace raw Carbon formatting in a Blade view:
{{-- before --}}
{{ $post->published_at->format('d/m/Y') }}
{{-- after --}}
{{ thaidate($post->published_at) }}
Confirm the Thai month names and the day-of-week ordering match what your designers expect before rolling it out across templates — abbreviations differ between F, M, and l tokens and there is no universal convention.
Blade presentation layer
Keep the helper in views and keep raw ISO values in your data layer. The helper is presentation, not storage.
<td>{{ thaidate($invoice->issued_at) }}</td>
<td>{{ thaidate($invoice->due_at, 'j F Y') }}</td>
Accessors for a stable display string
If the same Thai format appears in mail, PDFs, and Blade, wrap it once so a future format change is a one-line edit:
protected function issuedAtThai(): Attribute
{
return Attribute::get(fn () => thaidate($this->issued_at, 'j F Y'));
}
Do not make this the attribute you sort or filter on — see Gotchas.
API responses: never replace the machine field
return [
'issued_at' => $this->issued_at->toIso8601String(),
'issued_at_thai' => thaidate($this->issued_at, 'j F Y'),
];
Clients that parse dates need ISO 8601; the Thai string is a convenience field. Mixing the two into one key breaks sorting and client-side date math.
Parsing user input back
Thai-facing forms often accept 31/12/2567. Normalise explicitly rather than relying on the helper:
$gregorian = Carbon::createFromFormat('d/m/Y', $input)->subYears(543);
Validate the year range in a FormRequest first; createFromFormat happily produces nonsense for 31/02.
Grouping and reporting
$byMonth = $orders->groupBy(fn ($o) => thaidate($o->created_at, 'F Y'));
Because this is a string key, the collection sorts lexicographically. Sort the underlying query by created_at before grouping if ordering matters.
Testing
Freeze time so assertions are deterministic:
Carbon::setTestNow('2024-01-01 09:00:00');
$this->assertSame('1 มกราคม 2567', thaidate(now(), 'j F Y'));
Carbon::setTestNow(); // always reset
Prefer asserting on substrings (assertStringContainsString('2567', ...)) if the package's spacing or locale output is not guaranteed stable across Carbon minor versions.
Timezone discipline
Store UTC, convert at the boundary:
thaidate($order->created_at->copy()->setTimezone('Asia/Bangkok'))
Thailand is UTC+7 with no DST, so the main risk is a late-evening UTC timestamp rendering as the next day locally.
->addYears(543) and also use a format token the package already converts, you get 3110. Decide once per call site whether the package or your code owns the conversion.Y vs y. In PHP's format string, Y is the four-digit year and y the two-digit one. After adding 543 the difference is very visible. Never hand-add 543 to a string; add it to the Carbon object.format() vs translatedFormat(). If the package only swaps month names via locale, format('F') may silently stay English. translatedFormat() is the Carbon API that honours the locale — check which one the helper uses internally.th and not restoring it leaks into the next request. Pass the locale explicitly or set-and-restore.timestamp column for anything you filter, sort, or diff on; treat the Thai string as output-only.whereBetween on formatted strings. Once the value is a Thai string it loses chronological ordering (2567 sorts fine, but มี.ค. vs เม.ย. does not). Always query on the raw column.thaidate() function is a real risk in large apps or when another package defines the same name — a redeclare fatal surfaces at autoload time, not at your call site. If you hit it, alias with use function in a namespace or wrap it in your own service class.composer show nesbot/carbon.scanFiles/bootstrapFiles or call it fully qualified from one wrapper class and inject that.php artisan config:cache in deploy and remember that changes to the published file require a re-cache. env() calls inside package config break once cached.register()/boot() before assuming anything is configurable.composer.json and keep a one-class wrapper around the helper. If the package stalls or breaks on a future Carbon major, swapping the implementation is then a single-file change rather than a codebase-wide search.How can I help you explore Laravel packages today?