# Date-only YYYY-MM-DD in JSON-LD trips Search Console — the ISO 8601 fix

Schema.org accepts Date or DateTime on dateCreated, dateModified, datePublished. Google Search Console&#39;s strict validator emits &#39;Invalid datetime value&#39; for the date-only form. Convert to ISO 8601 datetime with timezone offset. Here is how and why.

Author: J.A. Watte
Published: May 16, 2026
Source: https://jwatte.com/blog/blog-tool-jsonld-datetime-iso8601/

---

A second Search Console error landed on the same /about page that triggered the [ProfilePage mainEntity bug](/blog/blog-tool-profilepage-mainentity-typed-reference/). This one is shorter to explain and easier to fix.

> Invalid datetime value for dateModified

The value Search Console did not like:

```json
"dateModified": "2026-05-13"
```

Schema.org accepts both `Date` and `DateTime` on the `dateModified`, `dateCreated`, and `datePublished` fields. The schema validator passes the date-only form. The Rich Results Test passes it. Search Console's strict validator emits the error above and marks the rich result ineligible.

If you set those fields by hand in a template, or by pasting today's date in a Markdown frontmatter that flows into JSON-LD, this bug is probably on your site too.

## The fix

Convert the value to a full ISO 8601 datetime with timezone offset:

```json
"dateModified": "2026-05-13T00:00:00-06:00"
```

That is the same calendar date with three additional pieces of information added. A `T` separator. A time component (here, midnight). A timezone offset (here, Mountain Daylight Time).

Each piece serves a purpose for the strict validator. The `T` separator says "this is an ISO 8601 datetime, not a date." The time component says "this is the moment within that calendar day." The timezone offset says "this is which clock that moment refers to." Together they make the value unambiguous to a machine that has no other context, which is what Search Console wants.

## Why Search Console is stricter than schema.org

Schema.org defines `dateModified` as accepting either `Date` or `DateTime`. The schema validator answers the question "does this value conform to either type?" and passes the date-only form because `Date` accepts it.

Google Search Console's rich-result validator has a different job. It decides whether the field is unambiguous enough to surface in a search-result feature. A date without a time component is ambiguous about which moment on that day the modification occurred. For some rich-result types where freshness is the ranking signal, the ambiguity matters.

I do not love that two validators give different answers on the same schema, but I do not control that. The pragmatic move is to satisfy the stricter one, because the stricter one is what decides whether the page actually appears as a rich result in search.

## Pick the right timezone offset

The timezone offset is the part most people get wrong. The two common patterns I see:

**Pattern A: use the local business timezone.** If the site is for a business in Idaho, use Mountain Time. Mountain Daylight Time is `-06:00`. Mountain Standard Time is `-07:00`. Idaho observes DST so the offset shifts twice a year. Use the offset that applies on the date the field actually represents. A date in May uses `-06:00`. A date in December uses `-07:00`.

**Pattern B: use UTC.** Add a `Z` suffix instead of an offset: `"2026-05-13T00:00:00Z"`. Some build tooling defaults to UTC because it is unambiguous. Both forms are valid. UTC is fine if you do not care about clock-local interpretation.

Mountain Time offsets:
- Mountain Daylight Time, March to November: `-06:00`
- Mountain Standard Time, November to March: `-07:00`
- Arizona (no DST): `-07:00` year round

Other common US offsets:
- Pacific: `-07:00` summer, `-08:00` winter
- Central: `-05:00` summer, `-06:00` winter
- Eastern: `-04:00` summer, `-05:00` winter
- Hawaii (no DST): `-10:00` year round

If you do not care which clock the time refers to (and for `dateModified` you usually do not, because Google reads it for freshness ranking and the date component is what matters), midnight in any consistent offset is fine. Pick one and use it everywhere.

## Where the date-only bug usually originates

Three common sources, in order of frequency:

**Markdown frontmatter.** Eleventy, Hugo, Astro, Jekyll, and Next.js MDX all use `date: 2026-05-13` in YAML frontmatter for blog posts. When that value flows into a JSON-LD `datePublished` field through a template, the YAML parser hands it back as a date object that serializes to `"2026-05-13"`. Fix it at the template level by converting to ISO 8601 datetime on output.

**`new Date().toISOString().slice(0,10)`.** A common one-liner for "today's date" in JavaScript build scripts. The `.slice(0,10)` chops the time component off, which is exactly what you do not want. Use `new Date().toISOString()` without the slice for a UTC datetime, or use a date library that emits the local-offset form.

**Hand-edited dates in HTML.** Static-site authors sometimes paste today's date into a JSON-LD block by hand. Without the time component because typing it is tedious. This is where the [date-drift problem](/blog/blog-tool-sitemap-lastmod-truthfulness/) lives: hand-written dates fall out of sync with the actual content and stop being useful as freshness signals. Build-time injection is the structural fix.

## Build-time injection in Nunjucks (Eleventy)

The cleanest pattern is a single helper that emits the current build time as a properly formatted ISO datetime. In Eleventy's `.eleventy.js`:

```js
const { DateTime } = require('luxon');

module.exports = (config) => {
  config.addShortcode('buildIso', () => {
    return DateTime.now().setZone('America/Denver').toISO({suppressMilliseconds: true});
  });
};
```

Then in the layout:

{% raw %}
```nunjucks
"dateModified": "{% buildIso %}"
```
{% endraw %}

Result on every deploy:

```json
"dateModified": "2026-05-16T14:23:00-06:00"
```

The Luxon library handles DST transitions correctly. The shortcode fires at build time, so the value reflects the moment the page was rendered.

For Hugo, the equivalent template helper:

{% raw %}
```go
{{ now.Format "2006-01-02T15:04:05-07:00" }}
```
{% endraw %}

For Next.js (server-side render):

```jsx
const dateModified = new Date().toISOString();
```

For Astro:

```astro
const dateModified = new Date().toISOString();
```

All emit ISO 8601 datetimes that satisfy Search Console.

## What about hand-written canonical dates

Some pages have a real, manually-curated `datePublished` that should not be the build time. A blog post that was published last March should have `datePublished: "2026-03-12"` in frontmatter and stay there forever, separately from the build-time `dateModified`.

Pattern for that case. Keep the frontmatter date-only. Convert it to ISO 8601 datetime in the template:

{% raw %}
```nunjucks
"datePublished": "{{ page.date | date('yyyy-MM-dd\'T\'HH:mm:ssXXX') }}"
```
{% endraw %}

If your template engine does not have a date-format filter, write one that wraps Luxon or `date-fns`. Hand-rolling the format string is fragile because of DST.

The shape of the rule. Frontmatter holds the canonical date. The template renders it as a full ISO 8601 datetime at build time. The strict validator gets what it wants. The freshness signal stays accurate.

## Combine with the build-time `dateModified`

The standard convention I use on sites with editorial content:

- `datePublished`: derived from the frontmatter `date`, formatted as ISO 8601 datetime with timezone, at template render time
- `dateModified`: emitted by a {% raw %}`{% buildIso %}`{% endraw %} shortcode that fires on every build

The build pipeline runs on every deploy, so `dateModified` reflects the actual last-modified moment for the page's source. The `datePublished` reflects the original publication. Both are ISO 8601 datetime so Search Console is happy.

For sites where the content rarely changes and you want `dateModified` to track only meaningful edits, store an explicit `dateModifiedIso` in the page frontmatter and reformat it through the same template filter. That gives you control over when the freshness signal moves.

## Why this pairs with the ProfilePage bug

The two errors landed in Search Console on the same day for the same /about page because they both descend from the same pattern. A template author put the canonical bio on the homepage, then built a thin ProfilePage on /about that referenced the Person by `@id` and added a couple of editorial dates. The schema validator passed. The Rich Results Test passed. Search Console flagged both fields on the next crawl, three days later.

Both are strict-validator errors. Both can be fixed at the template level in one PR. Together they fix the ProfilePage rich result for the /about page and restore the page's eligibility to appear with the dedicated profile UI in search.

## How to find every date-only field

Walk every JSON-LD block in your rendered HTML and grep every node for fields ending in `Created`, `Modified`, or `Published`. Any value matching `^\d{4}-\d{2}-\d{2}$` (exactly ten characters, year-month-day, no `T` separator) is what Search Console flags.

In DevTools:

```js
const blocks = [...document.querySelectorAll('script[type="application/ld+json"]')];
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
const FIELDS = ['dateCreated', 'dateModified', 'datePublished'];
const hits = [];

function walk(n) {
  if (!n || typeof n !== 'object') return;
  if (Array.isArray(n)) return n.forEach(walk);
  FIELDS.forEach(f => {
    if (typeof n[f] === 'string' && DATE_ONLY.test(n[f])) {
      hits.push({type: n['@type'], field: f, value: n[f]});
    }
  });
  Object.values(n).forEach(v => { if (v && typeof v === 'object') walk(v); });
}

blocks.forEach(b => {
  try { walk(JSON.parse(b.textContent)); } catch (e) {}
});

console.log(hits);
```

Anything in the `hits` array is what to fix.

## Related reading

- [The companion ProfilePage mainEntity post](/blog/blog-tool-profilepage-mainentity-typed-reference/) — the other Search Console strict-validator error that shipped on the same page
- [Nine AI Mode entity-binding bugs that pass every schema validator](/blog/blog-ai-mode-binding-bugs-validators-miss/) — the predecessor post on validator-blind binding bugs
- [Why Sitemap lastmod truthfulness matters more than you think](/blog/blog-tool-sitemap-lastmod-truthfulness/) — the date-drift problem in a different form
- [Schema validator vs Rich Results vs Search Console — three validators, three different verdicts](/blog/blog-tool-rich-results-eligibility-audit/) — broader context on validator divergence
- [The Mega Analyzer](/blog/blog-tool-mega-analyzer/) — where this detector lives in the audit chain

## Fact-check notes and sources

- Schema.org Date and DateTime spec: [schema.org/Date](https://schema.org/Date), [schema.org/DateTime](https://schema.org/DateTime)
- Schema.org `dateModified` definition: [schema.org/dateModified](https://schema.org/dateModified)
- ISO 8601:2019 specification (paid, but the Wikipedia summary is accurate): [en.wikipedia.org/wiki/ISO_8601](https://en.wikipedia.org/wiki/ISO_8601)
- Google Search Console rich-result error reference: [support.google.com/webmasters/answer/7552505](https://support.google.com/webmasters/answer/7552505)
- Luxon date library docs (for build-time helpers): [moment.github.io/luxon](https://moment.github.io/luxon/)
- Mountain Time UTC offsets (DST schedule): [www.timeanddate.com/time/zone/usa/denver](https://www.timeanddate.com/time/zone/usa/denver)

---

*This post is informational, not legal or SEO-consulting advice. The Search Console error described here is documented in Google's rich-results help center and reproducible against the live validator.*


---

Canonical HTML: https://jwatte.com/blog/blog-tool-jsonld-datetime-iso8601/
RSS: https://jwatte.com/feed.xml
JSON Feed: https://jwatte.com/feed.json
Hero image: https://jwatte.com/images/blog-tool-jsonld-datetime-iso8601.webp
