Skip to content

Fix datetime-local timezone shift by replacing Flatpickr with native picker#2581

Open
mpscholten wants to merge 1 commit intomasterfrom
worktree-fix-datetime-local-timezone
Open

Fix datetime-local timezone shift by replacing Flatpickr with native picker#2581
mpscholten wants to merge 1 commit intomasterfrom
worktree-fix-datetime-local-timezone

Conversation

@mpscholten
Copy link
Copy Markdown
Member

Summary

  • Removes Flatpickr for datetime-local inputs, uses the native browser picker instead
  • Adds JS (initDateTimeLocal()) to convert server-rendered UTC values to the user's local timezone on page load
  • On form submit, converts local time back to UTC before FormData reads the values
  • Flatpickr for input[type='date'] fields is unchanged

Root cause: PR #2491 changed dateTimeField to render UTCTime as YYYY-MM-DDTHH:MM (no Z suffix) to match the HTML5 datetime-local spec. Flatpickr's dateFormat: 'Z' mode expects a Z-suffixed UTC string. Without it, Flatpickr interprets the UTC wall-clock time as local time, causing a timezone offset shift on every save. PR #2570 tried appending Z in JS, but the browser's datetime-local input sanitization interferes, causing a double offset.

This fix: Instead of fighting between the datetime-local spec and Flatpickr's UTC mode, we remove Flatpickr for these inputs entirely. The native picker handles display, and simple JS Date math handles the UTC↔local conversion:

Step Value Explanation
Server renders value="12:00" UTC wall-clock time
JS page load el.value = "14:00" Converted to local (UTC+2 example)
User sees 14:00 in native picker Correct local time
User saves (no change) JS converts back to 12:00 Local→UTC before form submit
Server receives 12:00 Unchanged ✓

Fixes #2565

Test plan

  • Open a record with a UTCTime datetime field in a non-UTC timezone — verify the native picker shows local time
  • Save without changes — verify the stored time is unchanged
  • Edit the time, save — verify the correct UTC value is stored
  • Test with JS disabled — form still works (shows UTC times as graceful degradation)
  • Verify input[type='date'] fields still use Flatpickr

🤖 Generated with Claude Code

…picker

Remove Flatpickr for datetime-local inputs and use the native browser
picker instead. Add JS to convert server-rendered UTC values to local
time on page load, and convert back to UTC on form submit.

This fixes the double timezone offset bug where Flatpickr's dateFormat: 'Z'
mode misinterpreted datetime-local values (which have no Z suffix per HTML5
spec) as local times, causing the stored time to shift on every save.

Fixes #2565

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@github-actions
Copy link
Copy Markdown

Core Size & Compile Allocations Benchmark

Metric Baseline (master) This PR Change
Core size 16060452 bytes 16060454 bytes 0.0%
Compile allocations 40139491984 bytes 40139744464 bytes 0.0%

Core size within threshold
Compile allocations within threshold

HTTP Latency (GET /, 200 runs)

Metric Baseline (master) This PR Change
Mean 12.58ms 13.69ms 8.8%
Median 11.36ms 11.71ms
Stddev 9.18ms 9.28ms
Min 5.95ms 7.35ms
Max 125.84ms 86.85ms

HTTP latency within threshold

Top 10 modules (this PR)

Module Size (bytes)
Web.Controller.Comments.thr 892983
Web.Controller.Threads.thr 860564
Web.Controller.Users.thr 820982
Admin.Controller.Admins.thr 653948
Admin.Controller.UserBadges.thr 587281
Web.FrontController.thr 562385
Web.Types.thr 547347
Web.View.Layout.thr 472428
Web.View.Threads.Show.thr 428464
Web.Routes.thr 423680

@avitkauskas
Copy link
Copy Markdown
Contributor

avitkauskas commented Mar 24, 2026

I think I commented on that, but don't see this comment here for some reason. Let me try again.

If we display local time to the users in datetime input fields, then we would also want to display local time everywhere else in UI. At least I do that in my app, and I use this helper in JS:

function formatLocalISO(date) {
    const pad = (n) => String(n).padStart(2, '0');

    const year = date.getFullYear();
    const month = pad(date.getMonth() + 1);
    const day = pad(date.getDate());

    const hours = pad(date.getHours());
    const minutes = pad(date.getMinutes());
    const seconds = pad(date.getSeconds());

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

function localizeTimes(root = document) {
    root.querySelectorAll(".local-time").forEach(el => {
        if (el.dataset.localized) return;

        let timeStr = el.dataset.time;
        timeStr = timeStr.replace(" UTC", "Z").replace(" ", "T");

        const date = new Date(timeStr);
        if (isNaN(date)) return; // safety guard

        el.textContent = formatLocalISO(date);
        el.dataset.localized = "true";
    });
}

and then this in Haskell code:

renderTime :: UTCTime -> Html
renderTime time =
    [hsx|
        <span class="local-time" data-time={tshow time}></span>
    |]

How do you do this? Could this be supported by the framework itself?
Also, what about the formatting of the datetime? Locales have their own formatting style, but sometimes you want (I do) to display it in ISO standard formatting style. Could IHP let developer to easily choose that?

Can all these questions be solved in one place?
And if we use native browser datetime-local, then should we still keep using Flatpickr for date fields?

@mpscholten
Copy link
Copy Markdown
Member Author

I think this is mostly handled by IHP.View.TimeAgo.dateTime and IHP.View.TimeAgo.time see https://ihp.digitallyinduced.com/api-docs/IHP-View-TimeAgo.html#v:dateTime

Likely we need to support more customization of the output formatting

@avitkauskas
Copy link
Copy Markdown
Contributor

Oh, good. I didn't notice them before. Then yes, just a param for the formatting for flexibility. That's a separate improvement then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broken Flatpickr integration

2 participants