000%

Updated

NOTES · SEO / GEO / AEO08 · 2026.10

Represent Canonical URLs, Publication Dates, Revisions, and Past and Present Accurately

Use canonical addresses, dates, validity periods, and working code to explain which revision of a document applies when.

§ 01SEO / GEO / AEO

Addresses and time answer different questions

‘Where is the original?’, ‘When was it published?’, and ‘Does it apply now?’ are different questions. A canonical URL identifies the representative address for duplicate or very similar documents; publication and modification dates describe document history. The validity period describes when fees, timetables, and eligibility stated in the body apply.

Suppose the fictional library’s 2025 timetable is moved to a new CMS in 2026. Using migration time as first publication and renaming it ‘Current opening hours’ makes historical material look current. Pointing its canonical to the current timetable also signals that facts from different periods are one document. Preserve the original period, source, and self-referencing address, and link current guidance so readers can check historical rules.

Search systems group documents and estimate dates shown in results. Generative answers must select material appropriate to the question’s time. Consistent addresses, periods, and content give people and tools a basis for comparison. In Search Console, read the selected canonical and last crawl time; in AI answers, compare the cited page and the validity period of its statement.

§ 02SEO / GEO / AEO

Distinguish duplicates needing canonicalization from separate documents

Canonicalization selects a representative URL from multiple candidates. If only tracking UTM parameters or output format differ for the same article, a common canonical can be used. Internal links and sitemaps should also use it. Google describes redirects and rel=canonical as strong signals and sitemaps as weaker signals; the search system selects the final canonical. Canonical URL guidelines

A browser reading a canonical link stays at the original URL. Use a 301 or 308 redirect to move visitors. Protect private documents with authentication and authorization; provide noindex to exclude public documents from search.

When the base and UTM versions of a notice are crawled separately, search systems must compare them and select a canonical. Aligning canonical and internal links consolidates signals for identical content at the intended address and consistently suggests the URL for results. Historical and current notices answer for different periods and keep separate addresses. Check mistaken use of past facts as current facts through the document’s period explanation and actual AI citations too.

Distinguish duplicates needing canonicalization from separate documents
Same article + UTM
Meaning
Base article URL
Example
Same body; only campaign traffic differs
Old slug of the same article
Meaning
Permanent redirect to the new address
Example
Update existing links and sitemap too
List pages 1 and 2
Meaning
Each page’s own address
Example
They provide different items
Current guidance and 2025 record
Meaning
Each document’s own address
Example
Show period and link to current guidance
Korean and English translations
Meaning
Language-specific addresses
Example
Connect actual translations with hreflang
Missing article
Meaning
404 and error screen
Example
Do not inherit the home-page canonical
§ 03SEO / GEO / AEO

Separate creation, publication, revision, event, and effective times

createdAt is the internal time a draft was created. Store a verified first-publication date as publishedAt and use it for JSON-LD datePublished. contentModifiedAt is the substantive change time of public content and supplies dateModified. Changing it on every save, deployment, or crawl makes it impossible to tell which fact changed when.

An event date is when the described event happened. The date an operating policy was approved, the notice published, and the new timetable took effect may differ. Distinguish ‘Published,’ ‘Updated,’ and ‘Validity period’ labels in the body. Google’s date guidance calls for consistent publication and substantive modification dates in visible content and structured data, without confusing them with event dates. Page-date guidelines

A sitemap’s lastmod represents a significant change at that URL. Derive it from actual changes to body, important links, or structured data. Omit it without an accurate record. Simply adding today’s date to an old article does not explain a substantive update. Sitemap guidelines · Content-update guidelines

If a notice says ‘Published October 24; opening hours change from November 1’ and is read on October 25, the old hours still apply. Selecting the newest material by publication date alone can report future hours as today’s. Publication and revision dates indicate when a document appeared; validity periods indicate when its facts apply. Write ‘from November 1’ in the body too, and distinguish current from scheduled guidance.

Separate creation, publication, revision, event, and effective times
createdAt
Meaning
Internal creation start
Example
First save; not a required public value
publishedAt / datePublished
Meaning
First publication
Example
Preserve through later edits and migration
contentModifiedAt / dateModified
Meaning
Substantive change to public content
Example
Record with the change description
eventDate
Meaning
Event date
Example
Store only the date if time is unknown
validFrom / validUntil
Meaning
Internal validity period
Example
Determine current, scheduled, or historical status
lastmod
Meaning
Significant change at the URL
Example
Omit without a reliable record
§ 04SEO / GEO / AEO

Represent publication state independently from current or historical applicability

A historical record can also be public. Separate publicationStatus—draft, published, withdrawn—from the applicability of its facts. Deleting a record because it no longer applies prevents answers to questions about past policies or fees. Decide public availability after evaluating preservation value and access policy.

The document ID identifies the same article, revision an edited version, and slug the name used in its address. Preserve stable ID and revision history when title or slug changes for the same document. If current guidance and historical records are separate documents, give them different IDs and explain the relationship with ‘Current guidance’ and ‘Previous guidance’ links. supersedes can serve as an internal relationship indicating that a later document replaces an earlier one.

validFrom and validUntil store applicability periods in the example model. Put publication and modification dates in WebPage; display validity in the body, such as ‘January 1–December 31, 2025.’ Schema.org properties are defined per document type. WebPage properties

§ 05SEO / GEO / AEO

Turn date and address decisions into executable functions

Save the following as identity-time.mjs. It runs on Node.js 22 without external packages. dateOnly validates an actual YYYY-MM-DD calendar date and returns it unchanged. instant validates a second-precision timestamp with an explicit time zone. Do not invent midnight or a time zone for an event whose date alone is known.

ISO 8601 is an international standard for date and time representation. This example uses second-precision dates, times, and UTC offsets such as 2026-10-23T12:00:00+09:00. +09:00 means nine hours ahead of UTC; the same instant in UTC is 2026-10-23T03:00:00Z. Services requiring seasonal changes in regional time zones must manage those rules too.

canonicalUrl builds a URL from a stored path and page number that changes the content. Allowed paths here are restricted to lowercase Latin letters, digits, and hyphens. Extend the data contract if a real service supports Korean slugs or other parameters. Since the request Host and entire query are not copied, UTM values and arbitrary domains do not enter the canonical address.

// Node.js 22, No external packages. Timestamps use seconds; publication and modification may be date-only. export function dateOnly(value) { if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new Error('Invalid date'); const ms = Date.parse(value + 'T00:00:00Z'); if (!Number.isFinite(ms) || new Date(ms).toISOString().slice(0, 10) !== value) throw new Error('Invalid date'); return value; // Do not shift an event date across time zones. } export function instant(value) { const m = typeof value === 'string' && value.match( /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|[+-]\d{2}:\d{2})$/); if (!m) throw new Error('Timezone required'); dateOnly(m[1]); const offset = m[5] === 'Z' ? [0, 0] : m[5].slice(1).split(':').map(Number); if (+m[2] > 23 || +m[3] > 59 || +m[4] > 59 || offset[0] > 23 || offset[1] > 59) throw new Error('Invalid time'); const ms = Date.parse(value); if (!Number.isFinite(ms)) throw new Error('Invalid time'); return ms; } export function canonicalUrl(path, page = 1, origin = 'https://example.com') { // Use stored document paths and meaningful page numbers, not the request Host or query. if (!/^\/[a-z0-9-]+(?:\/[a-z0-9-]+)*$/.test(path)) throw new Error('Invalid path'); if (!Number.isSafeInteger(page) || page < 1) throw new Error('Invalid page'); const url = new URL(path, origin); if (page > 1) url.searchParams.set('page', String(page)); return url.href; } // Publication accepts Date or second-precision DateTime; preserve date-only precision. export function contentDate(value) { if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return dateOnly(value); instant(value); return value; } // Comparison bounds only; do not invent stored or displayed publication times. // Dates in this exercise use the library calendar in Asia/Seoul. function bounds(value) { contentDate(value); if (value.length > 10) return [instant(value), instant(value)]; const start = instant(value + 'T00:00:00+09:00'); return [start, start + 86400000 - 1]; } export function isPublic(doc, now = Date.now()) { if (!Number.isFinite(now)) throw new Error('Invalid comparison time'); if (!['draft', 'published', 'withdrawn'].includes(doc.status)) throw new Error('Invalid status'); if (doc.publishedAt !== null) contentDate(doc.publishedAt); if (doc.deletedAt !== null) instant(doc.deletedAt); const release = doc.publicFrom === null ? null : instant(doc.publicFrom); return doc.status === 'published' && doc.deletedAt === null && release !== null && release <= now; } export function documentId(id, origin = 'https://example.com') { if (typeof id !== 'string' || !/^[a-z0-9-]+$/.test(id)) throw new Error('Invalid id'); return new URL('/#document-' + id, origin).href; } export function describeDocument(doc, asOf, origin = 'https://example.com') { const now = instant(asOf); // Report stored-data errors even in drafts; determine public availability after validation. const published = doc.publishedAt === null ? null : bounds(doc.publishedAt); const modified = doc.contentModifiedAt === null ? null : bounds(doc.contentModifiedAt); const from = doc.validFrom === null ? null : instant(doc.validFrom); const until = doc.validUntil === null ? null : instant(doc.validUntil); if (published && modified && modified[1] < published[0]) throw new Error('Modified before publication'); if (from !== null && until !== null && from >= until) throw new Error('Invalid interval'); if (doc.eventDate !== null) dateOnly(doc.eventDate); const canonical = canonicalUrl(doc.path, 1, origin); const id = documentId(doc.id, origin); if (!isPublic(doc, now)) return null; if (published && published[0] > now) throw new Error('Future publication'); if (modified && modified[0] > now) throw new Error('Future modification'); const applicability = from === null ? 'unknown' : now < from ? 'scheduled' : until !== null && now >= until ? 'historical' : 'current'; return { id, canonical, applicability, ...(published === null ? {} : { datePublished: doc.publishedAt }), ...(modified === null ? {} : { dateModified: doc.contentModifiedAt }), }; } export function formatContentDate(value) { contentDate(value); // Do not convert date-only values to another time zone. const day = value.length === 10 ? value : new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', }).format(new Date(value)); const [year, month, date] = day.split('-').map(Number); return `${year}-${month}-${date}`; } export const example = { id: 'hours-2025', path: '/archive/opening-2025', revision: 2, status: 'published', deletedAt: null, publicFrom: '2024-12-23T09:00:00+09:00', createdAt: '2024-12-20T09:00:00+09:00', publishedAt: '2024-12-23T09:00:00+09:00', contentModifiedAt: '2026-10-01T09:00:00+09:00', eventDate: '2024-12-19', validFrom: '2025-01-01T00:00:00+09:00', validUntil: '2026-01-01T00:00:00+09:00', };

describeDocument receives a document and comparison time asOf. It validates ID, address, dates, and periods first, including reporting invalid draft dates. Only valid input reaches isPublic; nonpublic documents return null. Applicability uses a start-inclusive, end-exclusive interval [start, end). An unknown start yields unknown. The returned id, canonical, datePublished, dateModified, and applicability are used together in the body and JSON-LD in the next implementation chapter.

publicFrom is the exact start of public access, independent of publishedAt. contentDate preserves Date and DateTime precision. bounds calculates comparison ranges for date-only values using this library’s Korean calendar, but never stores or displays those boundaries as publication times. Same-day edits are allowed when dates alone cannot establish ordering. Services using another region’s calendar should state their comparison policy too.

If applicability is historical, show a historical-record explanation and the original period. Even if dateModified is today, applicability can remain historical. Calculating these separately prevents a typo correction from turning an old timetable into current guidance. Reusing canonical and date calculations in one place also makes inconsistent addresses or dates in title, body, and JSON-LD easier to find.

§ 06SEO / GEO / AEO

Test start and end boundaries and invalid input

Save the next file as identity-time.test.mjs in the same folder and run node --test identity-time.test.mjs. Tests check public availability, applicability boundaries, time zones, and invalid dates and addresses separately.

import test from 'node:test'; import assert from 'node:assert/strict'; import { dateOnly, instant, canonicalUrl, describeDocument, example, isPublic, formatContentDate } from './identity-time.mjs'; const at = '2026-10-23T12:00:00+09:00'; const historical = describeDocument(example, at); test('Historical records remain public with their own addresses', () => { assert.equal(historical.applicability, 'historical'); assert.equal(historical.canonical, 'https://example.com/archive/opening-2025'); assert.equal(historical.datePublished, example.publishedAt); }); test('Include the start and exclude the end of applicability', () => { const d = { ...example, contentModifiedAt: null }; assert.equal(describeDocument(d, '2024-12-31T23:59:59+09:00').applicability, 'scheduled'); assert.equal(describeDocument(d, d.validFrom).applicability, 'current'); assert.equal(describeDocument(d, d.validUntil).applicability, 'historical'); }); test('Keep drafts, withdrawn, deleted, and not-yet-scheduled items private', () => { for (const change of [{ status: 'draft' }, { status: 'withdrawn' }, { deletedAt: at }, { publicFrom: '2027-01-01T00:00:00Z', contentModifiedAt: null }]) { assert.equal(describeDocument({ ...example, ...change }, at), null); } }); test('Do not infer current applicability from an unknown start', () => { assert.equal(describeDocument({ ...example, validFrom: null }, at).applicability, 'unknown'); }); test('Reject nonexistent dates and timestamps without time zones', () => { assert.throws(() => instant('2026-02-30T12:00:00+09:00')); assert.throws(() => instant('2026-10-23T12:00:00')); assert.throws(() => instant('2026-10-23T25:00:00Z')); assert.equal(dateOnly('2026-10-23'), '2026-10-23'); }); test('Equivalent time-zone expressions match and event dates remain unchanged', () => { assert.equal(instant(at), instant('2026-10-23T03:00:00Z')); assert.equal(dateOnly(example.eventDate), '2024-12-19'); }); test('Reject reversed periods, future modifications, and modifications before publication', () => { assert.throws(() => describeDocument({ ...example, validUntil: example.validFrom }, at)); assert.throws(() => describeDocument({ ...example, contentModifiedAt: '2027-01-01T00:00:00Z' }, at)); assert.throws(() => describeDocument({ ...example, contentModifiedAt: '2020-01-01T00:00:00Z' }, at)); }); test('Distinguish meaningful page numbers from invalid paths', () => { assert.equal(canonicalUrl('/guides', 2), 'https://example.com/guides?page=2'); assert.throws(() => canonicalUrl('//elsewhere.example/a')); assert.throws(() => canonicalUrl('/guides/../admin')); assert.throws(() => canonicalUrl('/guides', 0)); }); console.log(JSON.stringify(historical, null, 2)); test('Public checks also reject nonexistent calendar dates', () => { assert.throws(() => isPublic({ ...example, publishedAt: '2026-02-30T09:00:00+09:00' })); }); test('Report invalid draft dates before excluding from publication', () => { assert.throws(() => describeDocument({ ...example, status: 'draft', validFrom: 'bad-date' }, at)); }); test('Preserve date-only and unknown publication dates', () => { const d = { ...example, publishedAt: '2024-12-23', contentModifiedAt: null }; assert.equal(describeDocument(d, at).datePublished, '2024-12-23'); assert.equal(formatContentDate('2024-12-23'), '2024-12-23'); assert.equal(describeDocument({ ...d, publishedAt: null }, at).datePublished, undefined); assert.equal(describeDocument({ ...d, publicFrom: null }, at), null); }); test('Keep document ID when its address changes', () => { const moved = describeDocument({ ...example, path: '/archive/old-hours' }, at); assert.equal(moved.id, historical.id); assert.notEqual(moved.canonical, historical.canonical); });

The output’s applicability is historical and canonical is https://example.com/archive/opening-2025. Dates preserve the input first-publication and substantive modification dates. All 12 tests should pass. If current is returned at the end boundary, check whether the comparison is >=. If 2026-02-30 passes, check calendar-date validation. Accepting timestamps without time zones can make the same string represent different instants depending on the server’s region.

Use the returned publication and applicability states in page output. Publication switches the database revision and updates relevant caches. If a static page must change automatically at a scheduled time, connect a regeneration job for that time. Check already-cached revisions through the update paths in Cache operations.

§ 07SEO / GEO / AEO

Make current guidance and historical records readable in the body

Open a historical record with its period and navigation path, such as ‘This timetable applied from January 1 to December 31, 2025. See the following notice for current hours.’ If the machine-readable end is 2026-01-01T00:00:00+09:00, the last applicable calendar day is December 31, 2025. Compare boundary values with visible sentences.

Current guidance states its effective date and exceptions, with publication and modification dates labeled separately. A historical document still titled ‘Current guidance’ misleads readers even if its JSON-LD dates are correct. Edit title, summary, tables, sources, and structured data to describe the same time and conditions.

Preserve verified original dates and sources during republication or migration. If first publication is unknown, do not substitute the new system’s save time. Explain what readers need to interpret the material, such as ‘Original publication date unknown,’ and use only verifiable modification or effective dates.

§ 08SEO / GEO / AEO

Preserve date precision and the kind of change

If an old notice says only ‘Published December 23, 2024,’ store publishedAt as 2024-12-23. Adding 00:00:00+09:00 invents an unverified publication time. If even the date is unknown, keep null and omit datePublished. Use a separately verified publicFrom for scheduled server access. Google’s publication-date guidance describes time as optional. Publication-date guidelines

const migrated = { publishedAt: "2024-12-23", // Date confirmed in the source publicFrom: "2026-10-24T09:00:00+09:00", // Time the migrated copy becomes public contentModifiedAt: null, // No substantive modification record } // JSON-LD: "datePublished": "2024-12-23" // Display: Published 2024-12-23

A typo correction and a change in opening hours require different date updates. The following table illustrates publication date, modification date, lastmod, and applicability decisions by change type. Replacing a source link for a core rule also counts as a content change.

Preserve date precision and the kind of change
Correct one spacing error
Publication and modification dates
Keep first publication and substantive modification dates
lastmod and applicability period
Keep if no significant content change; keep period
Sunday closing time changes from 17 to 16
Publication and modification dates
Keep publication date; record modification time and explain the change in the body
lastmod and applicability period
Record significant-change time; change applicability from the new timetable’s effective date
Remove only UTM parameters from an evidence link
Publication and modification dates
Keep publication and substantive modification dates
lastmod and applicability period
Keep if important content and link purpose are unchanged
Replace an obsolete rule with a new operating rule
Publication and modification dates
Keep publication date; record new evidence and substantive modification date
lastmod and applicability period
Record major link and fact changes; compare new applicability period
Server redeployment or CMS migration
Publication and modification dates
Keep original publication and modification dates
lastmod and applicability period
Keep if content is unchanged; migration time is an internal record
§ 09SEO / GEO / AEO

Let current institutional introductions and historical activity records describe different times

Suppose an ‘Opening Preparation Committee’ prepared the fictional library in 2025 and a ‘Library Operations Team’ has operated it since 2026. If the home-page title still says ‘Opening Preparation Committee,’ material remains that can answer current questions with the old role. Conversely, changing the signature on 2025 minutes to the operations team alters the historical record. Show the current role in the current introduction, and the period’s role and dates in the minutes.

The body’s modification date explains when information changed. The role’s validity period explains which time-specific questions it can answer. Adding a 2026 follow-up link to 2025 minutes may update dateModified, but the meeting’s role and date remain in 2025. Retrieval that selects only ‘recently modified material’ can confuse the two.

Keep the institutional ID in the CMS and store role, effective start, end, and evidence-document ID separately. Generate current header, introduction, and sharing title from the current role; use the historically stored role for past pages. Include document ID, role, and validity period in internal RAG material too. Connect queries and retrieval conditions so ‘current’ questions find documents valid on the reference date and ‘2025’ questions find documents for that period.

Outputs to compare when a role changes
Header, introduction, sharing title
Current guidance in 2026
Library Operations Team, since 2026
2025 record
Distinguish current site-wide header from the role in the historical body
Body and sources
Current guidance in 2026
Current operating authority and contact point
2025 record
Opening Preparation Committee’s minutes and contemporary dates
Canonical and related links
Current guidance in 2026
Canonical address of the current introduction
2025 record
Minutes’ own canonical and link to the current introduction
Internal search and RAG
Current guidance in 2026
Role and evidence valid on the reference date
2025 record
Role and evidence from the year specified in the question
— FIN —