Skip to content

Conversation

@mackeyguenther
Copy link

fixes #1400

  1. update datetime field (UI component & validation) to serialize/deserialize the value in UTC time, and render the value in the user's local timezone
  2. update datetime documentation to make it clear that datetime stores its value in UTC time, and displays it in the user's local timezone in the editing interface

…UTC, display in user's local time); update `datetime` documentation
@changeset-bot
Copy link

changeset-bot bot commented Feb 9, 2025

⚠️ No Changeset found

Latest commit: 5b3a070

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@larssont
Copy link

larssont commented Dec 5, 2025

This would be really nice to see 👍
Is there any update on this PR?

@kylejrp
Copy link

kylejrp commented Dec 29, 2025

@BarnabyBishop @emmatown @JedWatson Happy holidays, hope you all are doing well. 🎄 Would love to see this reviewed and get merged into the next release!

This is causing headaches for getting the local timezone right. If I write the date 2025-12-28 22:20 (10:20PM) in the Keystatic UI while in pacific time, it gets written in the frontmatter as 2025-12-28T22:20:00.000Z which then incorrectly gets rendered by my site as December 28th 2025, 2:20:00pm which is off by an entire 8 hours. 😞

@kylejrp
Copy link

kylejrp commented Dec 29, 2025

I tried writing a script to properly convert Z datetimes to local times (eg. -08:00 at the end instead after converting from UTC to Pacific), but Keystatic can't read times that look like this (eg. 2025-12-29T08:46:00.000-08:00 throws an error RangeError: Invalid time value when viewing the content in Keystatic's UI)

Instead, I'm parsing these incorrect Z dates as local dates in Astro in its Zod config:

// Keystatic's datetime field writes local time with a "Z" suffix (treating 
// it as UTC), but the value is actually local time. This preprocessor 
// interprets "Z" suffix dates as local time in the site's timezone.
import { site } from "@/data/site"; // { timeZone: "America/Los_Angeles" }
function parseInTimeZone(val: unknown) {
  let strVal = "";
  if (val instanceof Date) {
    // If midnight UTC, likely a date-only YAML string
    if (val.getUTCHours() === 0 && val.getUTCMinutes() === 0 && val.getUTCSeconds() === 0) {
      strVal = val.toISOString().split("T")[0];
    } else {
      return val;
    }
  } else if (typeof val === "string") {
    strVal = val;
  } else {
    return val;
  }
  // If it already has a proper timezone offset (not Z), keep it as-is
  if (strVal.includes("T") && /[+-]\d{2}(?::?\d{2})?$/.test(strVal)) {
    return strVal;
  }
  // WORKAROUND: Treat Z suffix as local time (Keystatic bug)
  // Convert "2025-12-29T12:00:00.000Z" -> "2025-12-29T12:00:00" + site offset
  if (strVal.endsWith("Z")) {
    strVal = strVal.slice(0, -1); // Remove the Z
    // Strip milliseconds if present (for cleaner canonical format)
    strVal = strVal.replace(/\.\d+$/, "");
  }
  // Handle date-only strings "YYYY-MM-DD"
  let iso = strVal;
  if (/^\d{4}-\d{2}-\d{2}$/.test(strVal)) {
    iso = `${strVal}T00:00:00`;
  }
  // Find the offset for this date in the target timezone
  try {
    const date = new Date(iso);
    if (isNaN(date.getTime())) return val;
    const parts = new Intl.DateTimeFormat("en-US", {
      timeZone: site.timeZone,
      timeZoneName: "shortOffset",
    }).formatToParts(date);
    const offsetPart = parts.find((p) => p.type === "timeZoneName")?.value; // "GMT-8"
    if (offsetPart?.startsWith("GMT")) {
      let offset = offsetPart.slice(3);
      if (!offset) offset = "Z";
      else {
        // Normalize "-8" to "-08:00"
        const sign = offset.startsWith("-") ? "-" : "+";
        const absOffset = offset.replace(/[+-]/, "");
        const [hours, minutes = "00"] = absOffset.split(":");
        offset = `${sign}${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}`;
      }
      return iso + offset;
    }
  } catch {
    // Fallback to default parsing
  }
  return val;
}
// Use in your Zod schema:
const timezoneDate = z.preprocess(parseInTimeZone, z.coerce.date());

Note that date-only dates like 2025-12-29 don't have any timezone attached, so they're correctly interpreted as local dates and need to be special cased.

As an aside, fixing this kind of seems like a breaking change - dates will start being written differently after this gets fixed, which might break some sites' assumptions on how dates are written by Keystatic.

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.

datetime field: incorrect UTC conversion behavior

3 participants