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
Ejecútalo en tu máquina
Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en 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
Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.
Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña 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 ""
Archivos del proyecto
.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