---
name: datetime
description: >
  Timezone queries (current time, conversion, UTC offset, DST) and
  relative-date arithmetic (last Tuesday, two weeks ago, Q3 last year).
  Trigger phrases: "what time is it in", "convert time", "timezone",
  "time difference", "DST", "last X", "N days/weeks/months ago",
  "this/next/previous X", "Qn YYYY", "Qn last year".
---

# Datetime & Timezone Queries

Invoked by the admin agent directly.

Answer timezone-related questions and relative-date arithmetic using Node.js — never freelance the math. Relative dates and quarter forms must go through the deterministic one-liner in this skill; the same algorithm runs server-side in the memory plugin's `relative-date.ts`, so chat answers stay consistent with timeline rows the operator sees in the graph.

## When to Activate

- User asks for the current time in a named city or timezone
- User asks to convert a time between timezones
- User asks about UTC offset, time difference, or DST status for a location
- User asks "what time is it?" with a location qualifier
- User uses a relative date: "last Tuesday", "two weeks ago", "yesterday", "this Christmas", "Q3 last year", "Q1 2025"

## Which surface for which question

One rule, no exceptions:

- **"What time is it?" / today / the current instant / today's weekday** → read the `<datetime>` block. It is injected into every turn (a `UserPromptSubmit` hook) and reports now in the operator's own timezone with the weekday and the raw UTC time; it is never stale. No tool call — the answer is already in front of you.
- **The current time in another named city or timezone** → the Node one-liner below (it formats now in any IANA zone).
- **The weekday or locale view of a *specific* datetime you already know** — a future meeting time, a stored timestamp — → the `time-resolve` tool (scheduling plugin): pass it the ISO and it returns the operator's locale view plus the weekday. Never eyeball a weekday; that is the failure this rule exists to prevent.
- **A relative date phrase** — "last Tuesday", "two weeks ago", "Q3 2024" → the deterministic Node one-liner in the "Relative date arithmetic" section below.

## Behaviour

Use Bash to run a Node.js one-liner with `Intl.DateTimeFormat`. The IANA timezone database is built into Node.js — no external packages are required.

### Current time in a timezone

```bash
node -e "console.log(new Intl.DateTimeFormat('en-GB', { weekday:'long', year:'numeric', month:'long', day:'numeric', hour:'2-digit', minute:'2-digit', second:'2-digit', timeZone:'IANA_TZ', timeZoneName:'longOffset' }).format(new Date()))"
```

Replace `IANA_TZ` with the target timezone identifier (e.g., `America/New_York`, `Asia/Tokyo`, `Europe/Berlin`).

### Time conversion

To convert a specific time from one zone to another, construct a `Date` object for the source time and format it in the target zone:

```bash
node -e "
const d = new Date('ISO_DATETIME');
const fmt = (tz) => new Intl.DateTimeFormat('en-GB', { weekday:'long', year:'numeric', month:'long', day:'numeric', hour:'2-digit', minute:'2-digit', timeZone:tz, timeZoneName:'short' }).format(d);
console.log(fmt('SOURCE_TZ'));
console.log(fmt('TARGET_TZ'));
"
```

### DST and offset info

```bash
node -e "
const now = new Date();
const opts = { timeZone:'IANA_TZ', timeZoneName:'longOffset' };
const parts = new Intl.DateTimeFormat('en-GB', opts).formatToParts(now);
const offset = parts.find(p => p.type === 'timeZoneName')?.value ?? 'unknown';
// Check DST by comparing Jan and Jul offsets
const jan = new Date(now.getFullYear(), 0, 1);
const jul = new Date(now.getFullYear(), 6, 1);
const janOff = new Intl.DateTimeFormat('en-GB', opts).formatToParts(jan).find(p => p.type === 'timeZoneName')?.value;
const julOff = new Intl.DateTimeFormat('en-GB', opts).formatToParts(jul).find(p => p.type === 'timeZoneName')?.value;
const dstActive = janOff !== julOff;
const inDst = dstActive && offset !== janOff;
console.log('Current offset:', offset);
console.log('DST observed:', dstActive);
console.log('Currently in DST:', inDst);
"
```

## Common City-to-Timezone Mapping

When the user names a city, map it to the IANA identifier:
- London → `Europe/London`
- New York → `America/New_York`
- Los Angeles → `America/Los_Angeles`
- Tokyo → `Asia/Tokyo`
- Sydney → `Australia/Sydney`
- Dubai → `Asia/Dubai`
- Mumbai / Delhi → `Asia/Kolkata`
- Berlin / Paris → `Europe/Berlin` / `Europe/Paris`
- Singapore → `Asia/Singapore`
- Hong Kong → `Asia/Hong_Kong`

For less common cities, use Bash to search the IANA database:
```bash
node -e "console.log(Intl.supportedValuesOf('timeZone').filter(z => z.toLowerCase().includes('SEARCH_TERM'.toLowerCase())).join('\n'))"
```

## Relative date arithmetic

When the operator references a relative date — "last Tuesday", "two weeks ago", "yesterday", "this Christmas", "Q3 2024", "Q1 last year" — never compute the result by hand. The agent loses track of weekday offsets, leap-day arithmetic, and quarter-start months. Use this deterministic one-liner. The reference timestamp defaults to now; pass an ISO string as the second argument to anchor against an earlier moment (e.g. when the operator's message timestamp matters).

```bash
node -e "
const REF = process.argv[2] ? new Date(process.argv[2]) : new Date();
const TXT = process.argv[1];
const WEEKDAYS = {sun:0,sunday:0,mon:1,monday:1,tue:2,tues:2,tuesday:2,wed:3,weds:3,wednesday:3,thu:4,thur:4,thurs:4,thursday:4,fri:5,friday:5,sat:6,saturday:6};
const NUMS = {one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12};
const sod = d => { const o=new Date(d); o.setUTCHours(0,0,0,0); return o; };
const addD = (d,n) => { const o=new Date(d); o.setUTCDate(o.getUTCDate()+n); return o; };
const addM = (d,n) => { const o=new Date(d); o.setUTCMonth(o.getUTCMonth()+n); return o; };
const addY = (d,n) => { const o=new Date(d); o.setUTCFullYear(o.getUTCFullYear()+n); return o; };
const base = sod(REF);
let m;
if (/\\bevery(\\s+other)?\\s+/i.test(TXT)) { console.log('null (recurring)'); process.exit(0); }
if (m = TXT.match(/\\b(yesterday|today|tomorrow)\\b/i)) {
  const w = m[1].toLowerCase();
  console.log((w==='yesterday'?addD(base,-1):w==='tomorrow'?addD(base,1):base).toISOString());
} else if (m = TXT.match(/\\b(last|next|this)\\s+([A-Za-z]+)\\b/i)) {
  const q = m[1].toLowerCase(), t = WEEKDAYS[m[2].toLowerCase()];
  if (t === undefined) { console.log('null'); process.exit(0); }
  const dow = base.getUTCDay();
  let delta;
  if (q==='last') { delta = t-dow; if (delta>=0) delta-=7; }
  else if (q==='next') { delta = t-dow; if (delta<=0) delta+=7; }
  else { const id = dow===0?7:dow, it = t===0?7:t; delta = it-id; }
  console.log(addD(base, delta).toISOString());
} else if (m = TXT.match(/\\b(a|an|\\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\\s+(day|week|month|year)s?\\s+ago\\b/i)) {
  const w = m[1].toLowerCase();
  const n = (w==='a'||w==='an')?1:(NUMS[w]??Number(w));
  const u = m[2].toLowerCase();
  const d = u==='day'?addD(base,-n):u==='week'?addD(base,-7*n):u==='month'?addM(base,-n):addY(base,-n);
  console.log(d.toISOString());
} else if (TXT.match(/\\b(?:this\\s+)?Christmas\\b/i)) {
  console.log(new Date(Date.UTC(base.getUTCFullYear(),11,25)).toISOString());
} else if (TXT.match(/\\b(?:this\\s+)?New\\s+Year(?:'s)?(?:\\s+Day)?\\b/i)) {
  console.log(new Date(Date.UTC(base.getUTCFullYear(),0,1)).toISOString());
} else if (m = TXT.match(/\\bQ([1-4])\\s+(\\d{2}|\\d{4})\\b/i)) {
  let y = Number(m[2]); if (y<100) y+=2000;
  console.log(new Date(Date.UTC(y,(Number(m[1])-1)*3,1)).toISOString());
} else if (m = TXT.match(/\\bQ([1-4])\\s+(last|this|next)\\s+year\\b/i)) {
  const refY = base.getUTCFullYear();
  const y = m[2].toLowerCase()==='last'?refY-1:m[2].toLowerCase()==='next'?refY+1:refY;
  console.log(new Date(Date.UTC(y,(Number(m[1])-1)*3,1)).toISOString());
} else { console.log('null'); }
" "PHRASE" "OPTIONAL_REF_ISO"
```

Replace `PHRASE` with the relative phrase ("last Tuesday", "Q3 2024") and optionally `OPTIONAL_REF_ISO` with the ISO reference timestamp.

The algorithm mirrors `platform/plugins/memory/mcp/src/lib/relative-date.ts` — the server-side parser the timeline extractor uses. Recurring forms ("every other Friday") return `null` by design. Phrases that don't match return `null`.

## Boundaries

- Answers timezone questions and relative-date arithmetic only — not scheduling, reminders, or alarms.
- The `<datetime>` block reports the current instant in the operator's own timezone (resolved from their profile on the graph), falling back to the server zone only when no timezone is configured. The Node one-liners here read the server's system clock. Either way, if the server clock is wrong the times will be wrong.
- Historical timezone data (e.g., "what time was it in Tokyo on 15 March 1990?") depends on the Node.js ICU dataset and may be inaccurate for dates before 1970.
- Relative-date parser handles single-date phrases only; multi-date ranges ("from March through May") are out of scope.
