---
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

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"

For simple "what time is it?" without a location, answer directly from the `<datetime>` block in the system prompt — no tool use needed.

## 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.
- Uses the server's system clock. If the server clock is wrong, answers 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.
