Chapter 9: Marking Time
Biscuit was due for a booster. Dr. Portbridge was sure of it, but the appointment book fought her every step. One entry read
"15/03/2025", another"March 15", a third"2025-03-15". To her they were the same afternoon; to the computer they were three unrelated strings. She couldn’t ask “which vaccinations are overdue?” because a string doesn’t know it’s a date. It was time to give dates real meaning.
The problem with string dates
Back in Chapter 3 we stored a date as a string and promised a better way later. This is later.
A string like "15/03/2025" is opaque. Nothing stops a typo ("2025-13-40"), nothing agrees on the format, and, worst of all, the date carries no meaning the machine can compute with. Dolfin now has four temporal types that fix this:
| Type | Holds | Example value |
|---|---|---|
date | A calendar day | date(March 15th 2025) |
time | A wall-clock time | time(2:30 PM) |
date_time | A day and a time | date_time(June 1st 2026, 14:30) |
duration | A length of time | duration(1y 6mo) |
The value inside the parentheses is a smart literal: you write the date the way a human would, and Dolfin parses it into the exact form an RDF datastore expects (xsd:date, xsd:time, xsd:dateTime, xsd:duration).
Writing dates
The most natural way is to spell it out. Month names are case-insensitive, and the ordinal ending (st, nd, rd, th) is optional:
date(March 15th 2025) # → 2025-03-15
date(15 March 2025) # → 2025-03-15
date(Mar 15 2025) # → 2025-03-15
You can also write it numerically. But 01/06/2025 is ambiguous (is it June 1st or January 6th?), so Dolfin makes you say which order the fields are in with an as mask:
date(15/03/2025 as d/m/y) # → 2025-03-15
date(2025-03-15 as y-m-d) # → 2025-03-15
The separator (/, -, or .) must match on both sides.
Set the locale once
Typing as d/m/y on every date gets old fast. The @locale directive, placed at the top of the file, sets the default field order for the whole file:
@locale d/m/y
fact rabies_shot a Vaccination
given_on date(15/03/2025) # no mask needed the file already knows
Times and timestamps
A time is 24-hour by default; add AM/PM for the 12-hour clock. An appointment slot pairs a day with a time in a date_time:
time(14:30) # → 14:30:00
time(2:30 PM) # → 14:30:00
date_time(June 1st 2026, 2:30 PM) # → 2026-06-01T14:30:00
Clinics keep local time, so you can pin a timezone, either inline, or file-wide with @timezone:
@timezone Europe/Brussels
fact checkup a Appointment
scheduled_for date_time(June 1st 2026, 9:00 AM) # → …T09:00:00+01:00
An inline offset (time(9:00 AM +02:00)) always overrides the file default.
Durations
A duration measures a span, how long a vaccine stays valid, how far ahead to send a reminder. Write one or more <number><unit> terms in any order:
y | mo | w | d | h | min | s |
|---|---|---|---|---|---|---|
| years | months | weeks | days | hours | minutes | seconds |
duration(3y) # → P3Y a rabies shot good for three years
duration(2w) # → P2W send the reminder two weeks ahead
duration(1h 30min) # → PT1H30M a long surgery slot
(The month unit is mo, so it never collides with minutes.)
Upgrading the clinic
Now the appointment book earns its keep. The Appointment gets a real date_time, and a new Vaccination concept records when a shot was given and how long it lasts:
concept Appointment:
has animal: one Animal
has scheduled_for: one date_time # was: has date: string
has reason: string
concept Vaccination:
has animal: one Animal
has vaccine_name: one string
has given_on: one date
has valid_for: one duration
With that, Biscuit’s booster is now a date the system understands, paired with a validity span:
fact rabies_shot a Vaccination
animal :biscuit
vaccine_name "Rabies"
given_on date(March 15th 2025)
valid_for duration(3y)
Because given_on is a real date and valid_for a real duration, the machine can now do what Dr. Portbridge couldn’t do by squinting at napkins: compare them, and work out exactly when the protection runs out. That arithmetic is the seed of the alerts we build next chapter.
The story so far
package <http://happypaws.com/clinic>:
dolfin_version "1"
version "0.1.0"
author "Dr. Helen Portbridge"
description "The Happy Paws veterinary clinic data model"
concept Animal:
has name: string
has species: string
has age: int
has owner: Owner
concept Owner:
has first_name: string
has last_name: string
has phone: string
concept Vaccination:
has animal: one Animal
has vaccine_name: one string
has given_on: one date
has valid_for: one duration
fact biscuit a Animal
name "Biscuit"
species "Dog"
age 5
fact rabies_shot a Vaccination
animal :biscuit
vaccine_name "Rabies"
given_on date(March 15th 2025)
valid_for duration(3y)
Try it
Add an Appointment fact for Biscuit’s booster with a real date_time, and give the Vaccination a reminder_lead of two weeks:
@locale d/m/y
concept Animal:
has name: string
has species: string
has age: int
has owner: Owner
concept Owner:
has first_name: string
has last_name: string
has phone: string
concept Vaccination:
has animal: one Animal
has vaccine_name: one string
has given_on: one date
has valid_for: one duration
# add: has reminder_lead: optional duration
# Add your booster appointment and vaccination facts here
Dr. Portbridge closed the appointment book (the real one, the one in the computer) and smiled. Every date now knew it was a date. But knowing the dates wasn’t the same as being warned in time. What she really wanted was for the system to speak up on its own: “Biscuit’s rabies protection expires next month.” For that, the clinic would have to start thinking.