Resolve dates only when the reference date is explicit
entity enrichment
temporal parsing
workforce research
Learn how simple R rules handle dates, weekdays, times of day, and durations in workforce text.
Calendar work stalls at Apply by October 15: the coordinator can see the month and day, but not the year. A deadline needs that missing context before it can land on a calendar.
A date parser turns a time expression into a date a computer can sort. The hard part is knowing when the text gives a full date, when it needs a reference date, and when it is not a calendar date at all.
Note
Riverton dates and notices in this lesson are made-up teaching examples.
TipWhat you will learn
By the end of this lesson, you will be able to:
separate full dates from relative dates;
use an explicit reference date rather than Sys.Date();
recognize durations and times of day as different kinds of time language;
compare a small temporal parser with this page’s expected answers; and
compare the rule output with spaCy entity labels.
State the reference date
A fixed reference date anchors the examples: 2026-08-28. That date is a Friday. A page whose output changes each day cannot be checked, so the lesson passes the date as a variable and never calls Sys.Date().
October 15 needs the reference year. Evening is a time of day without a date. 12-week is a duration, not a point on the calendar.
Write a small resolver
The resolver looks for a full ISO date such as 2026-10-15, then for a month and day, then for weekdays. The shorthand \(text) means “for each text value, run this small function.”
The resolver turns Monday into the next Monday after the reference date. It uses the same-year convention for month-day phrases, gives next Friday its own branch, and treats any bare weekday name as the next occurrence. Those are policy choices. A different rollover rule would be needed after October 15, and next Monday shows that the weekday branch is too blunt.
Score the examples
The resolver and expected answers share an author, so the score below does not estimate parser accuracy. It checks whether this page still behaves as described.
knitr::kable( score_summary,col.names =c("Measure", "Matching examples"),caption ="Agreement with this page's expected answers",row.names =FALSE)
Agreement with this page’s expected answers
Measure
Matching examples
category matches
11
date matches
10
One example is fully resolved from the text alone. Five need the reference date. Five are not resolvable as a single date. The next Monday row is the date mismatch: the rule treats it like Monday, while this lesson’s expected answer uses the following week.
Ask spaCy for a second opinion
spaCy labels spans of text as entity types such as DATE and TIME. The labels are useful clues, not answers to the resolution problem.
source("R/use-spacy.R")pipeline <-use_project_spacy()pipeline_version <-spacy_pipeline_version()spacy_parsed <-spacy_parse(setNames(temporal_examples$text, temporal_examples$expr_id),pos =TRUE,lemma =TRUE,entity =TRUE,dependency =TRUE,nounphrase =TRUE)spacy_entities <-entity_extract(spacy_parsed, type ="all")spacy_checks <- spacy_entities |>filter(doc_id %in%c("time-01", "time-02", "time-03")) |>select(doc_id, entity, entity_type)spacy_finalize()knitr::kable(tibble(name = pipeline_version$name,version = pipeline_version$version,language = pipeline_version$lang,license = pipeline_version$license,spacy = pipeline_version$spacy ),col.names =c("Pipeline", "Version", "Language", "License", "spaCy"),caption ="spaCy pipeline used for the second opinion",row.names =FALSE)
spaCy pipeline used for the second opinion
Pipeline
Version
Language
License
spaCy
core_web_sm
3.8.0
en
MIT
3.8.7
knitr::kable( spacy_checks,col.names =c("Expression ID", "Entity text", "spaCy label"),caption ="spaCy DATE and TIME labels on selected examples",row.names =FALSE)
spaCy DATE and TIME labels on selected examples
Expression ID
Entity text
spaCy label
time-01
October_15
DATE
time-02
Evening
TIME
time-03
12_-_week
DATE
spaCy labels October 15 as DATE, Evening as TIME, and 12-week as DATE. The first label helps find the span, but it still needs a year. 12-week belongs in DATE under the scheme spaCy was trained on because that class includes periods. The label is right, and it is still not a calendar date.
Temporal parsing in production
Finding a temporal span (like spaCy tagging next Friday as a DATE) is only the first step. Extracting a fully normalized value requires a dedicated tool like Meta’s Duckling or Python’s dateparser rather than hand-coded R branches.
These tools organize key concepts that custom rules often mix together: - Document Creation Time (DCT) / Reference Time: A relative phrase like next Friday or tomorrow means nothing without a strict reference date. Parsers do not enforce this automatically; callers must provide the anchor (e.g., passing RELATIVE_BASE to dateparser). - Granularity and representative ambiguity: A phrase like last month is ambiguous. Depending on the context, does it mean the previous calendar month (a granularity of one month) or the last 30 days? A robust parser separates the span detection from the normalized value, keeping these assumptions visible.
What to remember
A temporal parser needs a stated reference date for relative expressions.
Month-day dates need a same-year or rollover policy.
Weekday phrases need more than one next Friday special case.
Entity labels can find time language without settling the date.
Apply by October 15 can be put on a 2026 calendar here because the lesson supplies 2026 as the reference year. Evening and starts in the fall still do not give this parser a day to place.