Looking to hire Laravel developers? Try LaraJobs

laravel-timezone-indonesia maintained by aldesrahim

Description
Resolve an Indonesian IANA timezone from a coordinate, offline, from bundled timezone-boundary-builder geometry.
Author
Last update
2026/08/13 12:48 (dev-main)
License
Downloads
2

Comments
comments powered by Disqus

laravel-timezone-indonesia

Turn a coordinate into an Indonesian IANA timezone, offline.

use Aldesrahim\TimezoneIndonesia\Facades\TimezoneIndonesia;

TimezoneIndonesia::at(-6.2088, 106.8456);   // 'Asia/Jakarta'
TimezoneIndonesia::at(1.3521, 103.8198);    // null — Singapore, outside Indonesia

The boundaries ship with the package: four polygons filtered from timezone-boundary-builder by aldesrahim/timezone-indonesia. No API, no database, no network call at runtime. Their union is Indonesia, so the same lookup answers both "which timezone" and "is this in Indonesia at all".

Install

composer require aldesrahim/laravel-timezone-indonesia

That is the whole setup. The config file is optional:

php artisan vendor:publish --tag=timezone-indonesia-config

Requires PHP 8.2+ and Laravel 12 or 13.

Usage

Call Returns
at(float $lat, float $lon) 'Asia/Jakarta', or null outside Indonesia
zoneAt(float $lat, float $lon) Zone enum, or null
timezoneAt(float $lat, float $lon) DateTimeZone, or null
contains(float $lat, float $lon) bool
dataVersion() '2026c' — the upstream release loaded
metadata() provenance: release, timestamp, bounding boxes, checksum
zones() the four Zone cases

Arguments are (latitude, longitude) everywhere, the order people write coordinates in. GeoJSON stores [longitude, latitude]; that inversion is handled inside the package. A coordinate that cannot exist — including latitude and longitude passed the wrong way round — throws InvalidCoordinate rather than quietly returning null, which would be indistinguishable from "outside Indonesia".

The Zone enum carries what applications usually want next:

use Aldesrahim\TimezoneIndonesia\Zone;

$zone = TimezoneIndonesia::zoneAt(-5.1477, 119.4327);   // Zone::Makassar

$zone->value;            // 'Asia/Makassar'
$zone->abbreviation();   // 'WITA'
$zone->offsetHours();    // 8
$zone->region();         // 'South/East/North Kalimantan, Sulawesi, Bali, NTB, NTT'
$zone->timezone();       // DateTimeZone

Zone::tryFromTzid('Asia/Ujung_Pandang');   // Zone::Makassar — legacy aliases resolve

Inject the class if you would rather not use the facade:

public function __construct(private TimezoneIndonesia $timezones) {}
tzid Abbreviation Offset Covers
Asia/Jakarta WIB UTC+7 Java, Sumatra
Asia/Pontianak WIB UTC+7 West and Central Kalimantan
Asia/Makassar WITA UTC+8 South/East/North Kalimantan, Sulawesi, Bali, NTB, NTT
Asia/Jayapura WIT UTC+9 Maluku, Papua

What the polygons cover

Internal seas are inside a zone; open ocean is not. Upstream uses territorial waters rather than Exclusive Economic Zones, and in an archipelagic state that puts the water between the islands inside. A coordinate from a pier or a ferry still lands in a named zone, and GPS drift near the coast does not produce a spurious "outside Indonesia".

Point Resolves to
Java Sea, mid-water Asia/Jakarta
Makassar Strait Asia/Makassar
Banda Sea Asia/Jayapura
Indian Ocean, southwest of Java null
Oecusse, Timor-Leste null
Kota Kinabalu, Kuching, Dili, Singapore, Vanimo null

Oecusse is the interesting one: it is a Timor-Leste exclave surrounded by Indonesian West Timor, and it exists in the data as an interior ring inside Asia/Makassar. Holes are subtracted, so a point inside it resolves to nothing.

Performance

Measured on PHP 8.5 with the shipped 817 KB file, four polygons, ~36,700 coordinate pairs.

First lookup in a process Memory Per lookup
Default (GeoJSON) ~33 ms 1.4 MB ~2 ms
After timezone-indonesia:compile ~0 with OPcache 1.4 MB, shared between workers ~2 ms

The geometry is read on the first lookup and kept for the life of the process, so an application that installs the package and never calls it pays nothing. Under Octane that means one parse per worker rather than one per request.

Each lookup rejects on bounding boxes first — four float comparisons — and ray casts only the survivors. The boxes overlap heavily (Asia/Jakarta reaches 116.5°E, past where Pontianak and Makassar begin), so they can reject but never select.

Optional compile step

php artisan timezone-indonesia:compile

Writes the parsed geometry to bootstrap/cache/timezone-indonesia.php as a plain PHP array, which OPcache then keeps in shared memory. A deploy step, next to config:cache. Undo it with --clear. It is worth doing when a single web request does one lookup and 33 ms matters; it is noise for a queue worker resolving thousands.

Updating the boundaries

The normal path is composer update. A scheduled workflow in this repository checks upstream monthly, verifies the checksum, runs the suite against the new data, and opens a pull request; merging it publishes a minor release.

If you need boundaries before the package ships them:

php artisan timezone-indonesia:update            # download the latest release
php artisan timezone-indonesia:update --check    # exit 1 when newer boundaries exist
php artisan timezone-indonesia:update --tag=2026c

The download is verified against the SHA-256 in its own metadata and parsed before anything is written, so a truncated or foreign file cannot replace a working one. Files land in storage/app/timezone-indonesia (configurable) and take precedence over the bundled copy; delete them to go back. If a compiled file exists it is rebuilt automatically. This command is the only network code in the package, it never runs on its own, and everything works with the network disabled.

Testing

composer test

The point fixtures are ported verbatim from the data repository's validation script: 15 coordinates that must resolve to a specific zone, 10 that must resolve to nothing. Kota Kinabalu and Christmas Island are load-bearing — both pass on the comprehensive upstream build and both fail on the merged -1970 variant — so they are what stops a future data sync from quietly shipping Malaysian territory as Indonesian.

Licensing

The code is MIT (LICENSE). Everything under resources/data/ is Open Database License 1.0 (DATA-LICENSE), because it derives from OpenStreetMap.

Timezone boundaries from timezone-boundary-builder, derived from OpenStreetMap data, © OpenStreetMap contributors, ODbL 1.0.

Keep that attribution with any copy you distribute. Returning a timezone for a coordinate in an API response is a Produced Work and needs attribution only; redistributing the boundary file itself keeps it under ODbL. The line also lives inside the GeoJSON's attribution property, so it survives being copied out of this repository.