Skip to content

<flux:time-picker time-format="24-hour"> throws Invalid time string when <html lang> is Hebrew (locales whose day-period text isn't ASCII) #2636

Description

@ankitkabra-bacancy

Flux version

v2.13.2

Livewire version

v4.2.4

Tailwind version

v4.2.4

Browser and Operating System

Samsung Internet 29.0 (Android 10)

What is the problem?

User-visible behavior: Users on <flux:time-picker time-format="24-hour"> with (Hebrew page locale) experience an unhandled JavaScript error during normal interaction with the picker (focus → blur on the hour input, or keyboard navigation). The error reaches the browser's global handler and the picker stops responding.

Error thrown:

Error: Invalid time string: 16:27 לפנה״צ
(The exact prefix varies — 16:27, 21:19, 09:49, etc. — and the meridiem is either לפנה״צ (AM) or אחה״צ (PM), depending on the picker's selected hour.)

Why this is unexpected: The component is explicitly configured with time-format="24-hour". The documented contract is that no meridiem should be involved at all — neither displayed nor parsed — so an "Invalid time string" containing a day-period substring shouldn't be reachable. In practice on the affected browser it is reachable.

Where it surfaces: processTime() in UITimePickerTrigger reads this.meridiemInput?.value and passes the value to getTime() → convertTimeStringTo24HourFormat(). The vendor regex /^(\d{1,2}):(\d{2})(?:\s*(am|pm|AM|PM))?$/i rejects any non-ASCII meridiem and throws.

Impact: The error is unhandled at the runtime level — the action promise rejects, Sentry captures it under auto.browser.global_handlers.onerror, and the user is left with a non-functional time picker. In our production deployment this resulted in 6 recorded events from real users over ~2 weeks (likely undercount, since only those whose sessions still had Sentry connectivity made it through).

Configuration that triggers it: time-format="24-hour" on the component, page locale that produces a non-ASCII dayPeriod from Intl.DateTimeFormat (he, and likely others — ar, zh, etc., though we haven't observed those in production).

Code snippets to replicate the problem

Minimal Blade setup that exhibits the bug in production:

{{-- layouts/app.blade.php (or any layout) --}}

<!DOCTYPE html>
<html lang="he">
<head>
    <meta charset="utf-8">
    @livewireStyles
</head>
<body>
    <livewire:time-picker-repro />

    @livewireScripts
    {!! app('flux')->scripts() !!}
</body>
</html>

{{-- resources/views/livewire/time-picker-repro.blade.php --}}

<div>
    <flux:time-picker
        wire:model.live="t"
        type="input"
        time-format="24-hour"
    />
</div>

// app/Livewire/TimePickerRepro.php

<?php

namespace App\Livewire;

use Livewire\Component;

class TimePickerRepro extends Component
{
    public ?string $t = null;

    public function render()
    {
        return view('livewire.time-picker-repro');
    }
}

Open the page on Samsung Internet 29.0 / Android 10 (or any browser exhibiting the boot-time race). Focus the hour input, type/scroll any value, blur. The console throws Invalid time string: HH:MM .

Force-trigger in any browser (proves the regex throws on a locale day-period):

This bypasses the boot-time race and directly exercises the broken parser. Useful for verifying the fix in CI without needing the affected device:

// In DevTools on any page, with livewire/flux-pro 2.13.x loaded:
const trigger = document.querySelector('ui-time-picker-trigger');

// Force a Hebrew meridiem regardless of how boot() resolved:
const fakeMeridiem = document.createElement('input');
fakeMeridiem.value = 'לפנה״צ';
trigger.meridiemInput = fakeMeridiem;
trigger.hourInput.value = '16';
trigger.minuteInput.value = '27';

trigger.processTime();
// → Uncaught Error: Invalid time string: 16:27 לפנה״צ
//   at convertTimeStringTo24HourFormat
//   at getTime
//   at processTime
Standalone reproducer for the regex itself (no Flux needed):

This is the exact line that throws once a non-ASCII meridiem reaches the parser:


// Copied verbatim from flux.module.js (livewire/flux-pro 2.13.2):
function convertTimeStringTo24HourFormat(time) {
    let match = time.trim().match(/^(\d{1,2}):(\d{2})(?:\s*(am|pm|AM|PM))?$/i);
    if (!match) throw new Error(`Invalid time string: ${time}`);
    // ...
}

convertTimeStringTo24HourFormat('16:27 לפנה״צ'); // throws
convertTimeStringTo24HourFormat('17:02 אחה״צ'); // throws
convertTimeStringTo24HourFormat('16:27 AM'); // ok
The regex's optional capture group only matches am|pm (case-insensitive ASCII). Any locale that produces a non-ASCII dayPeriod from Intl.DateTimeFormat(locale, { hour12: true }) — Hebrew, Arabic, Chinese, Japanese, Korean, etc. — will trigger this if it ever reaches the parser.

Screenshots/ screen recordings of the problem

Here's an honest block for that section — since the bug is on a device class we don't physically have, we can't produce a direct recording. Acknowledge that and substitute the evidence we do have:

Screenshots / screen recordings
We do not have a direct screen recording — the bug is reproducible only on Samsung Internet 29.0 / Android 10 with a Hebrew system locale, which we don't have physical access to. All affected users are in production.

Substituting the evidence we have:

Production console error (as captured by Sentry across 6 events):

Uncaught Error: Invalid time string: 16:27 לפנה״צ
    at convertTimeStringTo24HourFormat (flux.min.js)
    at getTime (flux.min.js)
    at processTime (flux.min.js)
    at HTMLInputElement.<anonymous> (flux.min.js)

How do you expect it to work?

With time-format="24-hour" explicitly set, the picker should treat the meridiem concept as completely off — neither rendered, nor read, nor parsed — regardless of or browser-specific timing of when picker.config.timeFormat becomes available.

Specifically:

No JavaScript errors should reach the global handler during any interaction with the picker, on any browser, with any page locale.
The picker should accept "HH:mm" input and emit "HH:mm" to wire:model, with no AM/PM logic involved at any point in the data path.
The fact that Intl.DateTimeFormat(locale, { hour12: true }) returns a non-ASCII dayPeriod for some locales should be irrelevant when time-format="24-hour" is configured. (Or, ideally, irrelevant in the 12-hour case too — the parser should accept locale day-periods or only operate on the internal 24-hour value — but that's a larger change.)
In other words, the behavior on Samsung Internet 29.0 with Hebrew locale should be the same as it is today on Chrome desktop with English locale: the picker works, no console errors, no Sentry events.

Smallest possible fix that achieves this: call this.use24HourFormat() lazily inside processTime() (or in the meridiem-read path) instead of relying on the boot-time decision in this.meridiemInput = null. Any browser timing race in boot() then stops mattering.

Please confirm (incomplete submissions will not be addressed)

  • I have provided easy and step-by-step instructions to reproduce the bug.
  • I have provided code samples as text and NOT images.
  • I understand my bug report will be closed if I haven't met the criteria above.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions