はじめに
日時の列を >= で絞り込んだら全件、< にしたら0件。境目の日付をどう動かしても中間の件数が出てこない、という状態にハマりました。
原因は 日付を文字列のまま比較していた ことです。エラーは出ず、比較としては仕様どおりに動くので、結果の件数を見るまで気づけません。
作業は Claude Code と一緒に進めていて、原因の切り分けも AI 側がやってくれました。そのとき出てきたパターンを4つに分けて整理します。
環境
- Python 3.13
- JavaScript(日本時間の環境で実行)
文字列比較は「辞書順」
文字列の < > は日付として比べているわけではなく、先頭から1文字ずつ文字コードを比べて、最初に違った位置で決着します。
>>> "2026/09/23 01:26" >= "2026-09-24"
True
>>> "2026/09/23 01:26" <= "2026-09-24"
False
5文字目で /(47)と -(45)がぶつかった時点で / 側が勝ちます。月・日は一度も比較されません。
rows = ["2026/09/22 23:10", "2026/09/23 01:26", "2026/09/24 21:00"]
[r for r in rows if r >= "2026-09-24"]
# ['2026/09/22 23:10', '2026/09/23 01:26', '2026/09/24 21:00']
[r for r in rows if r < "2026-09-24"]
# []
ハマるパターン4つ
パターン1:区切り記号の不一致(/ と -)
-
2026-09-24:ISO 8601。DB や API に多い -
2026/09/24:画面表示、表計算ソフトからの書き出しに多い
症状は 全件 or 0件。どちらになるかは不等号の向き次第です。
パターン2:ゼロ埋めなし
>>> "2026/9/5" > "2026/10/01"
True
6文字目の 9 と 1 で決着するため、9月5日が10月1日より後になります。月・日の桁が変わる境目だけ順序が崩れるので、月またぎ・年またぎの集計で一部だけ件数がおかしいときに疑います。
パターン3:日付のみの値と日時を比べて、当日分が抜ける
>>> "2026-09-23 10:00" <= "2026-09-23"
False
先頭が同じなら長いほうが大きい、というルールで、23日の記録は0時0分も含めて全部「23日まで」から外れます。日時型の列でも、日付だけの値は0時0分として扱われがちで同じことが起きます。
終わりの日を含めたいなら半開区間で書きます。
start <= t < 翌日 00:00
パターン4:T と空白の混在、タイムゾーン表記
>>> "2026-09-23T10:00" > "2026-09-23 23:00"
True
T(84)が空白(32)に必ず勝つので、10時が23時より後になります。
>>> "2026-09-23T00:00:00Z" < "2026-09-23T08:00:00+09:00"
True # 文字列の比較
>>> from datetime import datetime
>>> datetime.fromisoformat("2026-09-23T08:00:00+09:00") > datetime.fromisoformat("2026-09-23T00:00:00+00:00")
False # 時刻としての比較
JST 8:00 は UTC で前日 23:00。時刻としては前なのに、文字列では後になります。複数システムのログを混ぜるときに起きやすいです。
対処:比較する前に日時型へ変換する
Python
from datetime import datetime
FMT = "%Y/%m/%d %H:%M"
start = datetime(2026, 9, 24)
picked = [r for r in rows if datetime.strptime(r, FMT) >= start]
# ['2026/09/24 21:00']
strptime で書式を明示すると、形式違いの値が混ざったときに例外で止まります。
ValueError: time data '2026-09-24 21:00' does not match format '%Y/%m/%d %H:%M'
「件数がなんか変」と後から気づくより圧倒的に早いです。なお 2026/9/5 7:03 のようなゼロ埋めなしも同じ書式で読めます。
JavaScript
スラッシュ区切りをそのまま new Date() に渡すのは避けます。ISO 8601 以外の形式の解釈は仕様上、実装依存です。
function parseSlashDate(s) {
const [d, t] = s.split(" ");
const [y, mo, day] = d.split("/").map(Number);
const [h, mi] = t.split(":").map(Number);
return new Date(y, mo - 1, day, h, mi); // 月は0始まり
}
const start = new Date(2026, 8, 24); // 9月24日 0:00
rows.filter(r => parseSlashDate(r) >= start);
// [ '2026/09/24 21:00' ]
ISO 8601 の形でも、日付のみか時刻付きかで解釈が変わる点に注意です(JST 環境で確認)。
new Date("2026-09-24").toISOString() // 2026-09-24T00:00:00.000Z(UTC扱い)
new Date("2026-09-24T00:00").toISOString() // 2026-09-23T15:00:00.000Z(ローカル扱い)
比較する両辺は同じ方法で生成しましょう。
文字列比較で問題ない条件
以下をすべて満たすなら、辞書順=時間順になります。
- 書式と桁数が統一されている(年4桁、月日時分は2桁ゼロ埋め)
-
区切り記号と、日付・時刻のつなぎ(
T/ 空白)が統一されている -
タイムゾーンが統一されている(例:すべて UTC +
Z) - 日付のみの値と日時の値が混ざっていない
ISO 8601 で保存された DB の列などは満たしやすく、表示用の値・手入力値・複数ソースから集めた値は崩れやすいです。
まとめ
| 症状 | 原因 | 対処 |
|---|---|---|
| 全件 or 0件 | 区切り記号の不一致 | 日時型に変換して比較 |
| 桁が変わる境目だけ順序が崩れる | ゼロ埋めなし | 書式指定で変換 |
| 終わりの日が丸ごと抜ける | 日付のみの値と比較 |
< 翌日0時 で書く |
| 一部の時刻だけ前後逆転 |
T/空白の混在・TZ表記差 |
型変換+TZ統一 |
| JS で9時間ずれる | 日付のみの形は UTC 解釈 | 両辺を同じ方法で生成 |
おかしな件数が出たら、比較式をいじる前に 比べている2つの値を1件ずつ print する。これが一番の近道でした。
参考
元になった作業の記録(AI 側の視点):
https://kujiragames.com/2026/09/compare-date-strings/