{}
StrictJSON
Extraction

Calendar Event Extractor

Meeting invites, chat messages, and forwarded emails constantly bury event details in prose: 'Let's meet Tuesday at 2 in room B with Ana and Marc.' This prompt pulls that prose into a structured calendar event — title, start and end times in ISO 8601, location, attendee emails, and an all-day flag — ready to feed a calendar API or a scheduling agent. It handles the common traps: end times and locations are often unstated, and the schema says so explicitly with null instead of inventing a one-hour slot or a default room. Relative dates such as 'tomorrow' or 'next Friday' must be resolved against the current date before calling the model, because the model has no clock. And an all-day event is not the same as a midnight-to-midnight event: the is_all_day flag keeps that distinction instead of faking a time range.

System Prompt

You are a calendar event extractor. Read the text below and return a single JSON object that matches the provided schema exactly.

Extract the event title and its start date and time as an ISO 8601 datetime, for example 2026-11-20T14:30:00. If an end date and time is stated, extract it the same way; if no end is stated, set end_datetime to null. If a location is stated, extract it; if no location is stated, set location to null. List every attendee email address found in the text in attendees; use an empty array when no attendee is mentioned. Set is_all_day to true only when the event spans a whole day with no specific start time, otherwise false.

Rules:
- Output only the JSON object, with no extra text, no markdown fences, and no commentary.
- Extract the best value present in the text. Do not invent a title, a time, a location, or an attendee that is not in the text.
- If the title or the start date and time cannot be determined from the text, do not produce any output.
- The title must be non-empty. Datetimes must be valid ISO 8601. Every attendee must be a valid email address.

Raw JSON Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "CalendarEvent",
  "type": "object",
  "additionalProperties": false,
  "required": ["title", "start_datetime", "end_datetime", "location", "attendees", "is_all_day"],
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1
    },
    "start_datetime": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2})?(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$"
    },
    "end_datetime": {
      "type": ["string", "null"],
      "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2})?(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$"
    },
    "location": {
      "type": ["string", "null"],
      "minLength": 1
    },
    "attendees": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
      }
    },
    "is_all_day": {
      "type": "boolean"
    }
  }
}

Zod (TypeScript)

import { z } from "zod";

const isoDatetime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
const email = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;

export const CalendarEventSchema = z.strictObject({
  title: z.string().min(1),
  start_datetime: z.string().regex(isoDatetime),
  end_datetime: z.string().regex(isoDatetime).nullable(),
  location: z.string().min(1).nullable(),
  attendees: z.array(z.string().regex(email)),
  is_all_day: z.boolean(),
});

export type CalendarEvent = z.infer<typeof CalendarEventSchema>;

Pydantic v2 (Python)

from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field, field_validator


class CalendarEvent(BaseModel):
    model_config = ConfigDict(extra="forbid")

    title: str = Field(..., min_length=1)
    start_datetime: str = Field(..., pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?$")
    end_datetime: str | None = Field(..., pattern=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?$")
    location: str | None = Field(..., min_length=1)
    attendees: list[Annotated[str, Field(pattern=r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$")]]
    is_all_day: bool

    @field_validator("title", "start_datetime", "end_datetime", "location", mode="before")
    @classmethod
    def _strip_text(cls, v):
        if not isinstance(v, str):
            return v
        return v.strip()

Try it in the StrictJSON tool