Calendar math
Ask anything about dates — "what weekday is Christmas 2027?", "how many days
Showcase — Calendar math
Ask anything about dates — "what weekday is Christmas 2027?", "how many days
until 2026-12-31?" — and the model calls a date_facts tool instead of
guessing. Weekday and day-count arithmetic is a known model weak spot: they
pattern-match calendars instead of counting. Python's datetime counts. The
model's whole job here is extracting YYYY-MM-DD from your sentence and
narrating the exact facts that come back.
Run
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Open http://localhost:3000.
To run against Gemini instead:
PROVIDER=gemini docker compose up --build
What's where
backend/ai_openai.py—date_facts(weekday, days from today, ISO week, leap year) + the tool schema and the round tripbackend/ai_gemini.py— same tool, GeminiFunctionDeclarationspellingbackend/main.py— identical FastAPI loader; reads PROVIDER and dispatchesfrontend/app/page.tsx— textarea + resultdocker-compose.yml— two services, secrets mounted from./secrets/
If a question names two dates, the model emits two function calls in the same turn and both results go back together — the round trip handles a list, not a single call.
Stop
docker compose down
Run locally
Download the project as a ZIP and run it with Docker. Brings up a FastAPI backend + Next.js frontend on localhost:3000.
unzip calendar-math.zip
cd calendar-math
bash bootstrap-secrets.sh # one-time: pulls API keys into ./secrets
docker compose up --build # default provider: openai
# or: PROVIDER=gemini docker compose up --build
Type some input, pick a provider, and run the same code shown in Source against the live API. Sign-in required.
The same modules the Run button hits. The whole project (frontend, Dockerfile, compose) is in the ZIP under README.
backend/ai_openai.py
"""Week 5 - Showcase 2 (OpenAI): calendar math the model can't fake.
Models are famously unreliable at weekday and date-difference arithmetic —
they pattern-match instead of counting. One date_facts tool built on
Python's datetime fixes the whole category: the model extracts the date,
the standard library does the calendar.
"""
import datetime as dt
import json
from openai import OpenAI
_client = OpenAI()
_MODEL = "gpt-5.4-nano"
def _date_facts(date: str) -> dict:
d = dt.date.fromisoformat(date)
today = dt.date.today()
return {
"date": d.isoformat(),
"weekday": d.strftime("%A"),
"days_from_today": (d - today).days,
"iso_week": d.isocalendar().week,
"day_of_year": d.timetuple().tm_yday,
"is_leap_year": (d.year % 4 == 0 and d.year % 100 != 0) or d.year % 400 == 0,
"today": today.isoformat(),
}
_TOOLS = [{
"type": "function",
"name": "date_facts",
"description": "Exact calendar facts for a date: weekday, days from today "
"(negative if past), ISO week, day of year, leap year. "
"Call it once per date mentioned.",
"parameters": {
"type": "object",
"properties": {
"date": {"type": "string", "description": "The date in YYYY-MM-DD format."},
},
"required": ["date"],
},
}]
def run(question: str) -> str:
response = _client.responses.create(
model=_MODEL,
instructions="Answer calendar questions. Always use the date_facts tool "
"for weekday and day-count math; never count days yourself.",
input=question.strip(),
tools=_TOOLS,
)
calls = [item for item in response.output if item.type == "function_call"]
if not calls:
return response.output_text
outputs = []
for call in calls:
args = json.loads(call.arguments)
try:
result = _date_facts(**args)
except ValueError as exc:
result = {"error": str(exc)}
outputs.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
})
final = _client.responses.create(
model=_MODEL,
previous_response_id=response.id,
input=outputs,
tools=_TOOLS,
)
return final.output_text
backend/ai_gemini.py
"""Week 5 - Showcase 2 (Gemini): calendar math the model can't fake."""
import datetime as dt
import os
from google import genai
from google.genai import types
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-3.1-flash-lite"
def _date_facts(date: str) -> dict:
d = dt.date.fromisoformat(date)
today = dt.date.today()
return {
"date": d.isoformat(),
"weekday": d.strftime("%A"),
"days_from_today": (d - today).days,
"iso_week": d.isocalendar().week,
"day_of_year": d.timetuple().tm_yday,
"is_leap_year": (d.year % 4 == 0 and d.year % 100 != 0) or d.year % 400 == 0,
"today": today.isoformat(),
}
_CONFIG = types.GenerateContentConfig(
system_instruction="Answer calendar questions. Always use the date_facts tool "
"for weekday and day-count math; never count days yourself.",
tools=[types.Tool(function_declarations=[types.FunctionDeclaration(
name="date_facts",
description="Exact calendar facts for a date: weekday, days from today "
"(negative if past), ISO week, day of year, leap year. "
"Call it once per date mentioned.",
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
"date": types.Schema(type=types.Type.STRING,
description="The date in YYYY-MM-DD format."),
},
required=["date"],
),
)])],
)
def run(question: str) -> str:
contents = [types.Content(role="user", parts=[types.Part(text=question.strip())])]
response = _client.models.generate_content(
model=_MODEL, contents=contents, config=_CONFIG,
)
if not response.function_calls:
return response.text or ""
contents.append(response.candidates[0].content)
parts = []
for fc in response.function_calls:
try:
result = _date_facts(**fc.args)
except ValueError as exc:
result = {"error": str(exc)}
parts.append(types.Part.from_function_response(name=fc.name, response=result))
contents.append(types.Content(role="user", parts=parts))
final = _client.models.generate_content(
model=_MODEL, contents=contents, config=_CONFIG,
)
return final.text or ""
Project files
.gitignoreREADME.mdbackend/Dockerfilebackend/ai_gemini.pybackend/ai_openai.pybackend/main.pybackend/requirements.txtbootstrap-secrets.shdocker-compose.ymlfrontend/Dockerfilefrontend/app/layout.tsxfrontend/app/page.tsxfrontend/next.config.tsfrontend/package.jsonfrontend/tsconfig.json