-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidate_dates.py
More file actions
140 lines (115 loc) · 4.78 KB
/
validate_dates.py
File metadata and controls
140 lines (115 loc) · 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
"""
日本語テキスト中の日付+曜日パターンを検証するClaude Codeフック。
Write/Edit後に自動実行し、曜日の間違いがあれば警告を出力する。
検出パターン:
- 4月29日(火) 4月29日(火) 4月29日(火曜日) 4月29日(火曜日)
- 2026年4月29日(火) 2026/4/29(火)
- 4/29(火) 4/29(火)
"""
import json
import re
import sys
from datetime import date
WEEKDAYS_JA = {
0: '月', 1: '火', 2: '水', 3: '木', 4: '金', 5: '土', 6: '日',
}
WEEKDAY_TO_NUM = {v: k for k, v in WEEKDAYS_JA.items()}
# 年月日(曜日) パターン
PATTERNS = [
# 2026年4月29日(火) or (火曜日)
re.compile(
r'(\d{4})\s*年\s*(\d{1,2})\s*月\s*(\d{1,2})\s*日\s*[((]\s*([月火水木金土日])\s*(?:曜日?)?\s*[))]'
),
# 4月29日(火) — 年なし
re.compile(
r'(?<!\d)(\d{1,2})\s*月\s*(\d{1,2})\s*日\s*[((]\s*([月火水木金土日])\s*(?:曜日?)?\s*[))]'
),
# 2026/4/29(火) or 2026-4-29(火)
re.compile(
r'(\d{4})\s*[/\-]\s*(\d{1,2})\s*[/\-]\s*(\d{1,2})\s*[((]\s*([月火水木金土日])\s*(?:曜日?)?\s*[))]'
),
# 4/29(火) — 年なし
re.compile(
r'(?<!\d)(\d{1,2})\s*/\s*(\d{1,2})\s*[((]\s*([月火水木金土日])\s*(?:曜日?)?\s*[))]'
),
]
def guess_year(month: int, day: int) -> int:
"""年が省略された場合、現在の年または翌年を推定"""
today = date.today()
candidate = today.year
try:
d = date(candidate, month, day)
# 3ヶ月以上過去なら翌年と推定
if (today - d).days > 90:
candidate += 1
except ValueError:
pass
return candidate
def validate_file(file_path: str) -> list[str]:
"""ファイル内の日付+曜日を検証。エラーリストを返す。"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except (OSError, UnicodeDecodeError):
return []
errors = []
for line_num, line in enumerate(content.split('\n'), 1):
# パターン1: 年月日(曜日)
for m in PATTERNS[0].finditer(line):
year, month, day, weekday = int(m.group(1)), int(m.group(2)), int(m.group(3)), m.group(4)
err = check_weekday(year, month, day, weekday, line_num, m.group(0))
if err:
errors.append(err)
# パターン2: 月日(曜日) — 年なし
for m in PATTERNS[1].finditer(line):
if PATTERNS[0].search(line[max(0, m.start()-5):m.end()]):
continue
month, day, weekday = int(m.group(1)), int(m.group(2)), m.group(3)
year = guess_year(month, day)
err = check_weekday(year, month, day, weekday, line_num, m.group(0))
if err:
errors.append(err)
# パターン3: YYYY/M/D(曜日)
for m in PATTERNS[2].finditer(line):
year, month, day, weekday = int(m.group(1)), int(m.group(2)), int(m.group(3)), m.group(4)
err = check_weekday(year, month, day, weekday, line_num, m.group(0))
if err:
errors.append(err)
# パターン4: M/D(曜日) — 年なし
for m in PATTERNS[3].finditer(line):
if PATTERNS[2].search(line[max(0, m.start()-5):m.end()]):
continue
month, day, weekday = int(m.group(1)), int(m.group(2)), m.group(3)
year = guess_year(month, day)
err = check_weekday(year, month, day, weekday, line_num, m.group(0))
if err:
errors.append(err)
return errors
def check_weekday(year: int, month: int, day: int, weekday_ja: str, line_num: int, matched: str) -> str | None:
"""曜日が正しいか検証。間違いならエラーメッセージを返す。"""
try:
d = date(year, month, day)
correct_weekday = WEEKDAYS_JA[d.weekday()]
if weekday_ja != correct_weekday:
return (
f'[日付エラー] 行{line_num}: "{matched}" → '
f'{year}年{month}月{day}日は{weekday_ja}曜日ではなく【{correct_weekday}曜日】です'
)
except ValueError:
return f'[日付エラー] 行{line_num}: "{matched}" → 無効な日付です({year}/{month}/{day})'
return None
def main():
tool_input = json.loads(sys.stdin.read())
file_path = tool_input.get('tool_input', {}).get('file_path', '')
if not file_path:
return
# テキストファイルのみ対象
if not any(file_path.endswith(ext) for ext in ('.md', '.html', '.txt', '.csv', '.json')):
return
errors = validate_file(file_path)
if errors:
print('\n'.join(errors), file=sys.stderr)
sys.exit(2)
if __name__ == '__main__':
main()