claude-code - 💡(How to fix) Fix [Workaround] Date-Weekday Verification Hook — Prevents Claude from writing wrong weekdays

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

  1. If claimed != actual -> exit code 2 (blocks the tool call) with a clear error message console.error('DATE VERIFIER: ' + errors.length + ' wrong weekday(s)!\n\n' + list + '\n\nFix before proceeding.');

Root Cause

Previous issues (#17338, #24466) were closed as duplicates without a user-facing solution. The model-level fix may take time, but users can protect themselves today with this hook. It is zero-config after installation and catches errors before they leave your machine.

This is especially critical for:

  • Job applications and interview scheduling
  • Business emails with meeting proposals
  • Calendar integrations
  • Any client-facing communication with dates

Code Example

#!/usr/bin/env node
const MONTH_NAMES = {
  january: 0, february: 1, march: 2, april: 3, may: 4, june: 5,
  july: 6, august: 7, september: 8, october: 9, november: 10, december: 11,
  jan: 0, feb: 1, mar: 2, apr: 3, jun: 5,
  jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
};
const EN_DAY_NAMES = {
  sun: 0, sunday: 0, mon: 1, monday: 1, tue: 2, tuesday: 2, tues: 2,
  wed: 3, wednesday: 3, thu: 4, thursday: 4, thur: 4, thurs: 4,
  fri: 5, friday: 5, sat: 6, saturday: 6,
};
const JP_DAY_NAMES = {
  '\u65e5': 0, '\u6708': 1, '\u706b': 2, '\u6c34': 3, '\u6728': 4, '\u91d1': 5, '\u571f': 6,
};
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

function verify(content) {
  const errors = [];
  const year = new Date().getFullYear();
  let match;

  // "Month Day (Weekday)"
  const p1 = /\b(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\s+(\d{1,2})\s*\((\w+)\)/gi;
  while ((match = p1.exec(content)) !== null) {
    const month = MONTH_NAMES[match[1].toLowerCase()];
    const day = parseInt(match[2], 10);
    const claimed = EN_DAY_NAMES[match[3].toLowerCase()];
    if (month === undefined || claimed === undefined) continue;
    const d = new Date(year, month, day);
    if (d.getMonth() !== month || d.getDate() !== day) continue;
    if (d.getDay() !== claimed)
      errors.push({ text: match[0], claimed: DAY_LABELS[claimed], actual: DAY_LABELS[d.getDay()] });
  }

  // "M/D (Weekday)"
  const p2 = /\b(\d{1,2})\/(\d{1,2})\s*\((\w+)\)/g;
  while ((match = p2.exec(content)) !== null) {
    const m = parseInt(match[1], 10) - 1, day = parseInt(match[2], 10);
    const claimed = EN_DAY_NAMES[match[3].toLowerCase()];
    if (claimed === undefined || m < 0 || m > 11) continue;
    const d = new Date(year, m, day);
    if (d.getMonth() !== m || d.getDate() !== day) continue;
    if (d.getDay() !== claimed)
      errors.push({ text: match[0], claimed: DAY_LABELS[claimed], actual: DAY_LABELS[d.getDay()] });
  }

  // Japanese "M\u6708D\u65e5(\u66dc\u65e5)"
  const p3 = /(\d{1,2})\u6708(\d{1,2})\u65e5\s*[\uff08(]([\u65e5\u6708\u706b\u6c34\u6728\u91d1\u571f](?:\u66dc\u65e5?)?)[\uff09)]/g;
  while ((match = p3.exec(content)) !== null) {
    const m = parseInt(match[1], 10) - 1, day = parseInt(match[2], 10);
    const claimed = JP_DAY_NAMES[match[3].charAt(0)];
    if (claimed === undefined || m < 0 || m > 11) continue;
    const d = new Date(year, m, day);
    if (d.getMonth() !== m || d.getDate() !== day) continue;
    if (d.getDay() !== claimed) {
      const jp = ['\u65e5', '\u6708', '\u706b', '\u6c34', '\u6728', '\u91d1', '\u571f'];
      errors.push({ text: match[0], claimed: jp[claimed], actual: jp[d.getDay()] });
    }
  }
  return errors;
}

let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (c) => { input += c; });
process.stdin.on('end', () => {
  try {
    const h = JSON.parse(input);
    if (!['Write', 'Edit', 'mcp__chrome-devtools__evaluate_script'].includes(h.tool_name)) {
      process.exit(0); return;
    }
    const ti = h.tool_input || {};
    const content = ti.content || ti.new_string || ti.function || '';
    if (!content || content.length < 5) { process.exit(0); return; }
    const errors = verify(content);
    if (!errors.length) { process.exit(0); return; }
    const list = errors.map(e => '  WRONG: "' + e.text + '" -- claimed ' + e.claimed + ', actually ' + e.actual).join('\n');
    console.error('DATE VERIFIER: ' + errors.length + ' wrong weekday(s)!\n\n' + list + '\n\nFix before proceeding.');
    process.exit(2);
  } catch { process.exit(0); }
});

---

{
  "matcher": "Write|Edit",
  "hooks": [
    {
      "type": "command",
      "command": "node \"~/.claude/hooks/date-weekday-verifier.js\"",
      "timeout": 5
    }
  ]
}

---

# Should BLOCK (wrong weekday):
echo '{"tool_name":"Write","tool_input":{"file_path":"t.md","content":"Meeting June 9 (Mon)"}}' \
  | node ~/.claude/hooks/date-weekday-verifier.js

# Should PASS (correct weekday):
echo '{"tool_name":"Write","tool_input":{"file_path":"t.md","content":"Meeting June 9 (Tue)"}}' \
  | node ~/.claude/hooks/date-weekday-verifier.js
RAW_BUFFERClick to expand / collapse

Problem

Claude consistently gets the day of the week wrong when writing dates. For example, writing "June 2 (Mon)" when June 2, 2026 is actually a Tuesday. This is a well-documented issue (see #17338, #24466, #11505, #24362) that has bitten many users.

This is not a one-off -- it happens reliably across conversations, devices, and contexts. LLMs fundamentally cannot calculate day-of-week because training data contains roughly equal probability for each weekday, so the model is essentially guessing.

Real-World Impact

In my case, I was applying for a position at a major AI company. Claude drafted my interview availability email with all 5 dates having the wrong weekday. The recruiter politely pointed out the mismatch. Embarrassing, but preventable.

My wife (who uses Claude for work in a different domain) independently confirmed: "Claude ALWAYS gets the days wrong."

Solution: A Claude Code Hook

I built a PreToolUse hook that intercepts Write, Edit, and evaluate_script calls, scans for date+weekday patterns, and blocks the tool call if any weekday is incorrect.

What it catches

PatternExampleLanguage
Full month nameJune 2 (Tue)English
Abbreviated monthJun 2 (Tue)English
Slash format6/2 (Tue)English
Japanese6月2日(火)Japanese

How it works

  1. Regex extracts all date+weekday pairs from the content being written
  2. Node.js new Date(year, month, day).getDay() computes the actual weekday
  3. If claimed != actual -> exit code 2 (blocks the tool call) with a clear error message
  4. If all correct -> exit 0 (passes silently)

The hook code

Save as ~/.claude/hooks/date-weekday-verifier.js:

#!/usr/bin/env node
const MONTH_NAMES = {
  january: 0, february: 1, march: 2, april: 3, may: 4, june: 5,
  july: 6, august: 7, september: 8, october: 9, november: 10, december: 11,
  jan: 0, feb: 1, mar: 2, apr: 3, jun: 5,
  jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
};
const EN_DAY_NAMES = {
  sun: 0, sunday: 0, mon: 1, monday: 1, tue: 2, tuesday: 2, tues: 2,
  wed: 3, wednesday: 3, thu: 4, thursday: 4, thur: 4, thurs: 4,
  fri: 5, friday: 5, sat: 6, saturday: 6,
};
const JP_DAY_NAMES = {
  '\u65e5': 0, '\u6708': 1, '\u706b': 2, '\u6c34': 3, '\u6728': 4, '\u91d1': 5, '\u571f': 6,
};
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

function verify(content) {
  const errors = [];
  const year = new Date().getFullYear();
  let match;

  // "Month Day (Weekday)"
  const p1 = /\b(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)\s+(\d{1,2})\s*\((\w+)\)/gi;
  while ((match = p1.exec(content)) !== null) {
    const month = MONTH_NAMES[match[1].toLowerCase()];
    const day = parseInt(match[2], 10);
    const claimed = EN_DAY_NAMES[match[3].toLowerCase()];
    if (month === undefined || claimed === undefined) continue;
    const d = new Date(year, month, day);
    if (d.getMonth() !== month || d.getDate() !== day) continue;
    if (d.getDay() !== claimed)
      errors.push({ text: match[0], claimed: DAY_LABELS[claimed], actual: DAY_LABELS[d.getDay()] });
  }

  // "M/D (Weekday)"
  const p2 = /\b(\d{1,2})\/(\d{1,2})\s*\((\w+)\)/g;
  while ((match = p2.exec(content)) !== null) {
    const m = parseInt(match[1], 10) - 1, day = parseInt(match[2], 10);
    const claimed = EN_DAY_NAMES[match[3].toLowerCase()];
    if (claimed === undefined || m < 0 || m > 11) continue;
    const d = new Date(year, m, day);
    if (d.getMonth() !== m || d.getDate() !== day) continue;
    if (d.getDay() !== claimed)
      errors.push({ text: match[0], claimed: DAY_LABELS[claimed], actual: DAY_LABELS[d.getDay()] });
  }

  // Japanese "M\u6708D\u65e5(\u66dc\u65e5)"
  const p3 = /(\d{1,2})\u6708(\d{1,2})\u65e5\s*[\uff08(]([\u65e5\u6708\u706b\u6c34\u6728\u91d1\u571f](?:\u66dc\u65e5?)?)[\uff09)]/g;
  while ((match = p3.exec(content)) !== null) {
    const m = parseInt(match[1], 10) - 1, day = parseInt(match[2], 10);
    const claimed = JP_DAY_NAMES[match[3].charAt(0)];
    if (claimed === undefined || m < 0 || m > 11) continue;
    const d = new Date(year, m, day);
    if (d.getMonth() !== m || d.getDate() !== day) continue;
    if (d.getDay() !== claimed) {
      const jp = ['\u65e5', '\u6708', '\u706b', '\u6c34', '\u6728', '\u91d1', '\u571f'];
      errors.push({ text: match[0], claimed: jp[claimed], actual: jp[d.getDay()] });
    }
  }
  return errors;
}

let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (c) => { input += c; });
process.stdin.on('end', () => {
  try {
    const h = JSON.parse(input);
    if (!['Write', 'Edit', 'mcp__chrome-devtools__evaluate_script'].includes(h.tool_name)) {
      process.exit(0); return;
    }
    const ti = h.tool_input || {};
    const content = ti.content || ti.new_string || ti.function || '';
    if (!content || content.length < 5) { process.exit(0); return; }
    const errors = verify(content);
    if (!errors.length) { process.exit(0); return; }
    const list = errors.map(e => '  WRONG: "' + e.text + '" -- claimed ' + e.claimed + ', actually ' + e.actual).join('\n');
    console.error('DATE VERIFIER: ' + errors.length + ' wrong weekday(s)!\n\n' + list + '\n\nFix before proceeding.');
    process.exit(2);
  } catch { process.exit(0); }
});

Installation

Add to ~/.claude/settings.json under hooks.PreToolUse:

{
  "matcher": "Write|Edit",
  "hooks": [
    {
      "type": "command",
      "command": "node \"~/.claude/hooks/date-weekday-verifier.js\"",
      "timeout": 5
    }
  ]
}

Test it

# Should BLOCK (wrong weekday):
echo '{"tool_name":"Write","tool_input":{"file_path":"t.md","content":"Meeting June 9 (Mon)"}}' \
  | node ~/.claude/hooks/date-weekday-verifier.js

# Should PASS (correct weekday):
echo '{"tool_name":"Write","tool_input":{"file_path":"t.md","content":"Meeting June 9 (Tue)"}}' \
  | node ~/.claude/hooks/date-weekday-verifier.js

Why this matters

Previous issues (#17338, #24466) were closed as duplicates without a user-facing solution. The model-level fix may take time, but users can protect themselves today with this hook. It is zero-config after installation and catches errors before they leave your machine.

This is especially critical for:

  • Job applications and interview scheduling
  • Business emails with meeting proposals
  • Calendar integrations
  • Any client-facing communication with dates

Environment

  • Platform: Windows 11 (also works on Linux/macOS)
  • Claude Code version: latest
  • Node.js: v24+

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING