AI schema generators commonly produce three date problems: wrong format (not ISO 8601), missing timezone, and stale years from the LLM's training cutoff. These break Google Rich Results eligibility and confuse AI engines deciding content freshness. This guide covers the format, the override patterns, and the dateModified discipline.
| Type | Format | Example |
|---|---|---|
| Date only | YYYY-MM-DD | 2026-05-18 |
| Datetime UTC | YYYY-MM-DDTHH:MM:SSZ | 2026-05-18T14:30:00Z |
| Datetime + offset | YYYY-MM-DDTHH:MM:SS±HH:MM | 2026-05-18T15:30:00+01:00 |
| Datetime with millis | YYYY-MM-DDTHH:MM:SS.sssZ | 2026-05-18T14:30:00.123Z |
"datePublished": "05/18/2026" ❌ MM/DD/YYYY "datePublished": "18/05/2026" ❌ DD/MM/YYYY "datePublished": "May 18, 2026" ❌ written format
"datePublished": "2026-05-18T14:30:00" ⚠️ ambiguous <!-- Parser assumes user-local; unpredictable across users -->
"datePublished": "2024-05-18" ❌ generator's training data is from 2024 <!-- Even if the page is 2026, the LLM defaulted to its cutoff year -->
"datePublished": "2026-05-18", "dateModified": "2026-05-18T14:30:00Z" ❌ mixed precision <!-- Both fields should match precision: both date or both datetime -->
"datePublished": "2027-12-01" ❌ scheduled date treated as published <!-- Schema datePublished is when it became publicly available, not authored -->
{
"@type": "Article",
"headline": "How to choose a CRM",
"datePublished": "2026-01-15T09:00:00Z",
"dateModified": "2026-04-22T14:30:00Z",
"author": {
"@type": "Person",
"name": "Jane Baker"
}
}
<!-- For an event scheduled in future -->
{
"@type": "Event",
"name": "Workshop",
"startDate": "2026-09-15T10:00:00+01:00",
"endDate": "2026-09-15T16:00:00+01:00"
}
Don't let the LLM guess from prose — feed it the real values:
// Bad: rely on LLM to extract from "Published last week"
const schema = await ai.generateSchema(htmlContent);
// Good: pass explicit dates from your CMS
const schema = await ai.generateSchema({
content: htmlContent,
metadata: {
datePublished: post.publishedAt.toISOString(), // ← from DB
dateModified: post.updatedAt.toISOString(), // ← from DB
author: { name: post.author.name, url: post.author.profileUrl }
}
});
// Generator must use the metadata, not infer from content
If your generator can't be re-configured, post-process the output:
function correctSchemaDates(schema, actualPublished, actualModified) {
function walk(obj) {
if (typeof obj !== 'object' || obj === null) return;
if (obj.datePublished) {
obj.datePublished = actualPublished; // ← always override
}
if (obj.dateModified) {
obj.dateModified = actualModified;
}
for (const v of Object.values(obj)) {
if (typeof v === 'object') walk(v);
}
}
walk(schema);
return schema;
}
// In your CMS publish pipeline
const generated = await ai.generateSchema(content);
const corrected = correctSchemaDates(generated, post.publishedAt, post.updatedAt);
embedInPage(corrected);
Set dateModified ONLY on genuine content change. Track in your CMS:
// On save
async function savePost(postId, newContent) {
const old = await db.getPost(postId);
const contentHash = hash(newContent);
// Only update dateModified if content actually changed
if (contentHash !== old.contentHash) {
await db.updatePost(postId, {
content: newContent,
contentHash,
dateModified: new Date()
});
}
// Otherwise: still save (typo fix, image swap) but don't bump dateModified
}
| Schema type | Date fields |
|---|---|
| Article / BlogPosting | datePublished, dateModified, dateCreated |
| Event | startDate, endDate, previousStartDate |
| Product / Offer | priceValidUntil, validFrom, validThrough |
| Recipe | datePublished, dateModified |
| JobPosting | datePosted, validThrough |
| Course | startDate, endDate, courseSchedule |
| Review | datePublished, dateCreated |
// ISO 8601 regex
const ISO8601 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2}))?$/;
function validateSchemaDates(schema) {
const errors = [];
function walk(obj, path = '') {
if (typeof obj !== 'object' || obj === null) return;
for (const [k, v] of Object.entries(obj)) {
if (k.match(/^(date|start|end|valid)[A-Z]/) && typeof v === 'string') {
if (!ISO8601.test(v)) {
errors.push(`${path}.${k}: not ISO 8601 "${v}"`);
}
}
if (typeof v === 'object') walk(v, `${path}.${k}`);
}
}
walk(schema);
return errors;
}