/ โ† JSON-LD Fixes / Date Format Errors
In this guide
  1. What a valid date looks like
  2. The right formatter in each language
  3. Empty, zero and placeholder dates
  4. Dates that are valid but wrong
  5. A corrected block
  6. The fix, step by step
  7. Keeping dates right
  8. Questions and answers

What a valid date looks like

schema.org has two kinds of date value. A Date is a calendar day. A DateTime is a moment in time. Both are written in ISO 8601, and properties such as datePublished and startDate accept either.

ValueValid?Notes
2026-09-28YesA date with no time. Fine for priceValidUntil, birth dates and all-day items.
2026-09-28T14:30:00+01:00YesThe clearest form: local time with its offset from UTC.
2026-09-28T13:30:00ZYesThe same moment expressed in UTC. Z means an offset of zero.
2026-09-28T14:30:00.000+01:00YesFractional seconds are allowed.
2026-09-28T14:30:00Accepted, but ambiguousNo offset, so the reader has to guess the time zone. Google may assume one you did not intend.
2026-09-28 14:30:00NoA space instead of T. This is the raw database format and a very common leak.
28/09/2026 or 09/28/2026NoSlashes, and no way to tell day from month.
28 September 2026NoReadable by people, not a machine format.
2026-9-8NoMonths and days need two digits: 2026-09-08.
1790596800NoA Unix timestamp is a number of seconds, not a date.
๐Ÿ’ก Durations are a different format. Properties such as cookTime, duration and totalTime take ISO 8601 durations like PT45M, not dates. A date checker that flags one of these is usually telling you a date was printed where a duration belongs.

The right formatter in each language

Nearly every bad date comes from a template that reuses the display format. The fix is to call a separate formatter for the markup, leaving the visible date as it is.

EnvironmentPrints the wrong thingUse instead
PHPdate('d/m/Y', $ts)date(DATE_ATOM, $ts) or $dt->format('c'), both giving 2026-09-28T14:30:00+01:00.
WordPressget_the_date() with no argument, which uses the site's display settingget_the_date( 'c' ) and get_the_modified_date( 'c' ), which use the time zone set in Settings, General.
Shopify Liquid{{ article.published_at | date: format: 'date' }}{{ article.published_at | date: '%Y-%m-%dT%H:%M:%S%:z' }}. Plain %z gives +0100 with no colon; check the output in view source.
JavaScripttoLocaleDateString() or toString()toISOString(), which always returns UTC with a Z. Correct, but the time shown will differ from local time by the offset.
Pythonstrftime('%d %B %Y'), or isoformat() on a naive datetimeMake the datetime time-zone aware first, for example with zoneinfo.ZoneInfo('Europe/London'), then call isoformat().
SQL straight to template2026-09-28 14:30:00Parse the value into a date object in your code and format it, or use the database's ISO formatting function.

Two points are easy to miss. First, the offset must match when the date falls, not when the page is built: a post published in January in the UK is +00:00, one published in July is +01:00. Formatters that work from a named time zone such as Europe/London get this right automatically; a hard-coded +01:00 string does not. Second, storing everything in UTC and outputting Z is equally valid and avoids the question entirely, as long as the stored value really is UTC.

Empty, zero and placeholder dates

A date that was never set can surface in markup in several disguises. Each one tells you something about the code behind it.

The fix is the same in each case: test whether the source value exists before formatting it, and leave the property out when it does not. A missing optional date is better than a false one. If the property is required for a rich result, such as startDate on an Event, the event is not ready to be marked up until a real date exists.

Dates that are valid but wrong

A value can be perfectly formatted and still make no sense. The JSON Checker and Google's tools look at date pairs and at dates that have run out.

PropertyRule to checkTypical fault
datePublished and dateModifiedModified should be the same as or later than published.Imported posts where the import date became the published date but the original modified date was kept.
startDate and endDateEnd on or after start.An evening event ending after midnight given the start day as its end day.
priceValidUntilShould be in the future while the price is offered.A sale end date that passed months ago; Google may disregard the offer price in a rich result once it has expired.
validThrough on JobPostingShould not be in the past while the job is live.A closing date that was never updated when the advert was extended.
uploadDate on VideoObjectShould not be in the future.A scheduled premiere date printed before the video is available.
datePublishedShould match the date shown on the page.Markup using the post's creation date while the page shows the publish date, or the other way round.

A related trap is a dateModified that changes on every page build even when nothing was edited. Some templates use the file's build time or the cache time. That does not break the markup, but it misstates freshness, and Google's guidance is that the modified date should reflect meaningful changes to the content.

A corrected block

This BlogPosting was previously printing "datePublished": "28 September 2026" from the theme's display format and an empty dateModified. After switching both to an ISO formatter and adding a check for the empty value, the output is:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Choosing Bulbs for a Shady Border",
  "datePublished": "2026-09-28T08:15:00+01:00",
  "dateModified": "2026-09-28T11:40:00+01:00",
  "author": {
    "@type": "Person",
    "name": "Robin Ashdown",
    "url": "https://www.example.co.uk/authors/robin-ashdown/"
  },
  "image": "https://www.example.co.uk/images/shady-border-bulbs-1200.jpg"
}
</script>

For a post that has never been edited, the template now either prints dateModified equal to datePublished or leaves it out; both are acceptable. It no longer prints an empty string.

A quick pattern check

If you want to scan exported markup yourself, this regular expression matches a date or a full date-time with an optional offset. Anything in a date property that does not match deserves a look:

^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$

It checks shape only. It will accept 2026-02-30, so for anything automated, parse the value with a real date library as well.

The fix, step by step

Step 1

Collect every date property per template

Run one page from each template through the JSON Checker and list every property holding a date: datePublished, dateModified, startDate, endDate, uploadDate, datePosted, validThrough, priceValidUntil and any others. Note the exact value each prints and whether the checker flagged it. Include blocks from plugins and apps as well as your own, since each source formats dates in its own way and may need its own fix.

Step 2

Find the code that formats each date

Search the theme or template files for the property name, then follow the value back to the function or filter that prints it. In WordPress look for get_the_date or a plugin setting; in Shopify look for the Liquid date filter; in custom code look for string formatting of a date object. If a plugin or app prints the date, check its settings for a format option before editing its files.

Step 3

Switch to an ISO 8601 formatter with an offset

Replace the display format in the markup with an ISO formatter: DATE_ATOM or 'c' in PHP and WordPress, %Y-%m-%dT%H:%M:%S%:z in Liquid, toISOString() in JavaScript or isoformat() on a time-zone aware Python datetime. Base the offset on a named time zone such as Europe/London rather than a fixed string, so winter and summer dates get the correct offset automatically without anyone having to remember the clock change.

Step 4

Omit dates that have no real value

Wrap each date in a check so the property is only printed when the source value exists and is not zero. This stops empty strings, 0000-00-00 and 1970 dates reaching the page. Never fall back to the current date, because it makes every page look newly published. If a required date such as an event start is missing, hold back that type until the real date is known.

Step 5

Check the order and expiry of related dates

Compare each pair: modified on or after published, end on or after start. Look for dates that have run out, such as a past priceValidUntil or validThrough, and either update them from live data or remove them when there is no real end date. Confirm the published date in the markup matches the date shown on the page, since a mismatch undermines trust in both.

Step 6

Retest and watch the reports

Re-run the JSON Checker on each template and confirm every date now appears as YYYY-MM-DD or a full date-time with an offset. Test one winter-dated and one summer-dated page to prove the offset changes correctly. In Search Console, request indexing for key pages and use Validate fix on affected enhancement reports, then check the same pages again after the next clock change in spring or autumn.

Keeping dates right

For AI-generated markup, where the model itself chooses the date, the AI schema date guide covers the different set of mistakes that appear there.

Questions and answers

1. Do I need to include a time in datePublished?

No. A plain date such as 2026-09-28 is valid ISO 8601 and schema.org accepts it. Adding a time with an offset makes the value more precise, which helps when a post is published close to midnight and the date could otherwise be read as the day before or after in another time zone. If your platform stores a full timestamp, output it with the offset; if it only stores a date, output the date alone.

2. Is Z better than +01:00 or +00:00?

Neither is better; they are different ways of writing the same moment. Z means the time is in UTC. An offset such as +01:00 means the time is local and that many hours ahead of UTC. Both are valid. Use Z when your code converts everything to UTC before printing, and a local offset when you print local times. What matters is that the time and the offset agree, so the moment described is the real one.

3. Why do my dates show as 1970?

A date of 1 January 1970 almost always means the source value was empty or null and the code formatted it anyway. Unix time counts seconds from the start of 1970, so a zero or missing timestamp becomes that date. Find the template or plugin field that supplies the date, check whether it is empty for the affected pages, and add a condition so the property is left out whenever there is no real value.

4. My Shopify dates end in +0100. Is that a problem?

Shopify date filters with the %z code print the offset without a colon, such as +0100. Strict ISO 8601 uses +01:00 in the extended format used for the rest of the value, and some validators flag the mixed form. Change the format string to use %:z, which prints the colon, and confirm the result in view source. Alternatively print the time in UTC with a Z, provided the value really is converted first.

5. Should dateModified change every time the page is rebuilt?

No. dateModified should reflect a meaningful change to the content, such as corrected facts or a rewritten section, not a cache refresh, a theme deployment or a change to a sidebar. Templates that use the build time or file time make every page look freshly updated, which misstates freshness. Base the value on the content record edit date, and ideally only update it for substantive edits rather than typo fixes.

6. What happens if priceValidUntil is in the past?

The markup still parses, but the offer describes a price that is, by its own statement, no longer valid. Google can ignore an expired price in a rich result, and the JSON Checker flags it as a logic problem. If the price has no planned end, leave priceValidUntil out entirely. If it is a sale price with an end date, generate the value from the same data that ends the sale, so the two always agree.

7. Do event dates need the local time zone of the venue?

Yes, for in-person events the times should be the local times at the venue with the offset that applies on that date. An event in London in July is written with +01:00; one in December with +00:00. For online events that people join from anywhere, choose the time zone you publish the event in and state its offset. Leaving the offset off means attendees in other time zones may see the wrong start time.

Check every date in your markup

The JSON Checker parses every JSON-LD block on your page, validates the syntax, checks each type against schema.org, verifies required fields, detects conflicting types and runs the result against Google rich result criteria.

Open JSON-LD Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All JSON-LD Fixes  ·  JSON Syntax Errors  ·  Missing Required Fields  ·  AI Schema Date Errors  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.