Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Timezone Bundle Laravel Package

lunetics/timezone-bundle

Symfony bundle that detects a visitor's timezone from their IP address and exposes it through a simple service, with configurable fallbacks for timezone-aware applications.

View on GitHub
Deep Wiki
Context7

Getting Started

lunetics/timezone-bundle is still a Symfony bundle — no service provider, no facade, no auto-discovery, no config publishing. What changed: as of the 4.0.x line it is no longer tied to Symfony 2. The 3.0 line remains Symfony 2.x only (symfony/validator ~2.x), while 4.0.x targets a modern Symfony release — but per the corrected 4.0.1 notes, no released version of this bundle supports Symfony 6.4, and 4.0.0's own documentation wrongly told 6.4 users to pin 3.0, producing an unsatisfiable resolution.

Practical consequence: composer require lunetics/timezone-bundle in a Laravel 10/11 app may now succeed where it previously failed, and install a dependency that does nothing. The old hard failure is gone; do not read a green install as support.

First step is still a compatibility interrogation, in this order:

composer require lunetics/timezone-bundle --dry-run
composer why-not lunetics/timezone-bundle 3.0.0   # confirm what 3.x demands; never install it
composer show lunetics/timezone-bundle --all      # read the real 4.x constraints, not the README

Trust composer.json / Packagist constraints over the bundle's own prose — the 4.0.0 docs were demonstrably wrong about version compatibility, and 4.0.1 only fixed the prose.

Where to look first:

  1. composer.json — which Symfony line the 4.x branch actually declares. This decides everything.
  2. DependencyInjection/Configuration.php — authoritative config tree (nested keys, defaults, required values); READMEs lag.
  3. Resources/config/services.xml — real service IDs and constructor arguments.
  4. The detector class under Timezone/ — small, and still the only part genuinely worth copying.

First use case, unchanged in substance: take the client IP, resolve it to an IANA timezone, and apply it as the display timezone for the request — with a guaranteed fallback for private, unknown, or proxied addresses. On Laravel, the realistic first use case is porting the detector into a small TimezoneResolver service behind your own interface, then running it on geoip2/geoip2. If you are genuinely in a legacy Symfony app, register the bundle in Kernel/AppKernel and inject the detector service.

Implementation Patterns

Request-scoped resolution, applied early. The core pattern is middleware/listener that runs before anything formats a date:

// Laravel equivalent of the bundle's request listener
public function handle($request, Closure $next)
{
    $tz = $request->user()?->timezone
        ?? $this->resolver->fromIp($request->ip())
        ?? config('app.timezone');

    config(['app.timezone' => $tz]);
    date_default_timezone_set($tz);
    Carbon::setTimezone($tz); // if you rely on Carbon's default

    return $next($request);
}

Register it near the top of the web group, after TrustProxies.

A fallback hierarchy, not a single lookup. IP detection should be the weakest source in the chain:

  1. Explicit user preference (profile column / session).
  2. IP geolocation.
  3. Accept-Language → country → representative timezone (small static map).
  4. App default (UTC).

The bundle gives you step 2 only; the surrounding chain is yours.

Cache IP → timezone aggressively. A GeoIP lookup per request is wasteful, and with file-based databases under php-fpm every worker loads the dataset.

$tz = Cache::remember("tz:{$ip}", now()->addDay(), fn () => $resolver->fromIp($ip));

Key by IP, not by user, and normalize IPv6 — the same visitor can present a rotated address.

Use detection to prefill, not to decide. Detect on first visit, preselect the timezone dropdown, persist what the user confirms. After that the stored value wins and detection is only a fallback. This removes almost every "my meeting showed up at the wrong hour" report.

Keep detection out of CLI and queues. Jobs, scheduled commands, and notifications should receive an explicit timezone (or a user ID to load it from). Never let a queue worker resolve from request()->ip() — there is no request.

Bind behind an interface when porting.

$this->app->singleton(TimezoneResolver::class, fn () => new GeoIpResolver(
    databasePath: config('services.geoip.path'),
    fallback: config('app.timezone'),
));

That gives you a test double and a one-file swap path to geoip2/geoip2 or a hosted API.

Testing pattern. Assert the fallback, not the geolocation: 127.0.0.1, 10.0.0.1, and ::1 must all return the configured default. Then stub the resolver in feature tests so date assertions are deterministic.

Gotchas and Tips

  • 4.0.1 changed no runtime code. It is docs plus CI gates on top of 4.0.0. Bump for accurate documentation, expect zero behaviour change, and do not schedule a regression pass over it. The only migration ever worth noting was 3.x → 4.x, not 4.0.0 → 4.0.1.
  • The bundle's own version advice has been wrong. 4.0.0 pointed Symfony 6.4 users at the legacy 3.0 line, which needs symfony/validator ~2.1 and cannot resolve. Never pin 3.x in anything modern, and verify compatibility from composer.json, not from README prose or release notes claiming a Symfony version.
  • A successful install no longer proves anything. With 4.x, composer require can resolve inside a Laravel app because the Symfony component constraints overlap — the bundle still registers nothing. Check for a service provider before believing it works, and expect config()/container access from bundle classes to fail if you call them directly.
  • The GeoIP database is not shipped. You supply the .dat yourself, and the legacy MaxMind GeoLiteCity format is deprecated — downloads and updates are effectively gone. This is the biggest reason the bundle is hard to run at all today. Plan on GeoIP2/MMDB plus an adapter, or a hosted service.
  • REMOTE_ADDR is frequently wrong. Behind a load balancer, CDN, or Docker ingress it is the proxy's IP. Configure trusted proxies (Laravel's TrustProxies, Symfony's Request::setTrustedProxies) before detection, or every user resolves to the same datacenter timezone.
  • Private and reserved ranges must short-circuit. 10/8, 172.16/12, 192.168/16, 127/8, and IPv6 ULA addresses have no meaningful geolocation. Return the fallback explicitly rather than letting the database return garbage.
  • IP geolocation is approximate by nature. VPNs, mobile carriers, and satellite ISPs routinely map to the wrong country, and a single country can span multiple timezones (US, Russia, Australia, Brazil). Never present a detected timezone as authoritative.
  • Set the timezone before parsing anything. Calling date_default_timezone_set() mid-request after dates exist produces subtle off-by-one-hour bugs. Note config(['app.timezone' => ...]) does not retroactively fix already-resolved Carbon instances.
  • Symfony-isms leak everywhere. Config uses %kernel.root_dir%-style parameters, services are XML/YAML without autowiring, constructors assume the old container. Strip these before porting into Laravel — do not try to bridge the container.
  • Extension point is decoration. The detector is a thin class; the clean change is to decorate or subclass and re-register the alias — a compiler pass or service override in Symfony, a container binding in Laravel.
  • Log what you detect. One line per request (ip, detected_tz, source, fallback_used) turns "times are wrong for one user" into a grep. Sample in production if volume matters.
  • If you fork, vendor, or mirror the repo, 4.0.1 tightened two gates: the test suite now fails on PHPUnit risky tests (so a teardown that removes PHPUnit's error handler can no longer exit green), and the distribution check requires the profiler template — a real runtime dependency of the data collector that the old checkout-based gate missed. Running their suite in your CI will surface both.
  • Maintenance risk, revised. The 4.x line is actively gated — changelog, tests, dist checks — which meaningfully softens the "abandoned Symfony 2 bundle" concern in the previous read. It is still not a Laravel package and still ships no geolocation data. Use it as a reference implementation of IP→timezone detection and a source of the fallback-hierarchy design, not as a runtime dependency in a new Laravel app.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr