fix(시간 표시): 서버 시각 모두 끝 Z · Z 없는 옛 기록 = 한국 시각으로 읽음 (PLAN 593-2 · 요구 456)
- 서버: DB 시각 글(DATE_FORMAT) 끝 Z · B03 올린 때 · D04 확정 시각 · 건강 확인 시각 = UTC Z · 개요 최근 변경 T 치환 뺌 - 서버가 만드는 엑셀(E05 면적 계산 시각) = 한국 시각 글(kst_text · Z 없는 옛 글은 이미 한국 시각) - 화면: Z 없는 글 = 옛 로컬 기록 → +09:00 로 읽음(옛 파일은 고쳐 쓰지 않음) - 시험: test_593_time(UTC · 서울 · 뉴욕 시간대 · Z 없는 길 0 정적 검사 · D04 · kst_text) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""593-1 시간 표시 — UTC 글 → 보는 PC 시간대(TS) · 서버가 만드는 글 = UTC Z / 한국 시각(Python)."""
|
||||
"""593 시간 표시 — Z 글 → 보는 PC 시간대(TS) · Z 없는 옛 글 = 한국 시각 · 서버가 내는 글 = UTC Z / 파일 글 = 한국 시각(Python)."""
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -21,11 +21,11 @@ import * as m from "./common_util_time.js";
|
||||
const now = Date.parse("2026-10-30T20:00:00Z");
|
||||
const out = {
|
||||
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
date: ["2026-10-30 01:02:03", "2026-10-30T01:02:03", "2026-10-30T01:02:03Z", "2026-10-30T10:02:03+09:00", null, "", "x"].map((v) => m.dateTimeText(v)),
|
||||
date: ["2026-10-30 10:02:03", "2026-10-30T10:02:03", "2026-10-30T01:02:03Z", "2026-10-30T10:02:03+09:00", null, "", "x"].map((v) => m.dateTimeText(v)),
|
||||
day: [m.dateText("2026-10-30T20:30:00Z"), m.dateText("2026-10-30"), m.dateText(null), m.dateText("")],
|
||||
time: [m.timeText("2026-10-30 20:30:00"), m.timeText(null)],
|
||||
time: [m.timeText("2026-10-30T20:30:00Z"), m.timeText(null)],
|
||||
ago: ["2026-10-30T20:00:00Z", "2026-10-30T10:00:00Z", "2026-10-29T10:00:00Z", "2026-10-26T10:00:00Z", "2026-10-01T10:00:00Z", null].map((v) => m.agoText(v, "-", now)),
|
||||
kst: m.kstText("2026-10-30 01:02:03"),
|
||||
kst: m.kstText("2026-10-30 10:02:03"),
|
||||
};
|
||||
writeFileSync(process.argv[2], JSON.stringify(out));
|
||||
"""
|
||||
@@ -60,11 +60,11 @@ def runner(tmp_path_factory):
|
||||
def test_seoul(runner):
|
||||
r = runner("Asia/Seoul")
|
||||
assert r["tz"] == "Asia/Seoul"
|
||||
# Z 없는 옛 글 · ISO · Z · +09:00 모두 같은 한국 시각
|
||||
# Z 없는 옛 글(한국 시각) · ISO · Z · +09:00 모두 같은 한국 시각
|
||||
assert r["date"][:4] == ["2026-10-30 10:02"] * 4
|
||||
assert r["date"][4:] == ["—", "—", "—"]
|
||||
assert r["day"] == ["2026-10-31", "2026-10-30", "-", "-"] # 20:30Z = 한국 다음 날 05:30
|
||||
assert r["time"] == ["05:30", ""]
|
||||
assert r["time"] == ["05:30", ""] # 20:30Z
|
||||
assert r["kst"] == "2026-10-30 10:02"
|
||||
# 지금 = 한국 10-31 05:00 → 한국 달력 날짜로 센다
|
||||
assert r["ago"] == ["오늘", "어제", "2 일 전", "5 일 전", "2026-10-01", "-"]
|
||||
@@ -77,10 +77,17 @@ def test_other_zone_follows_viewer(runner):
|
||||
assert r["day"][1] == "2026-10-30" # 날짜만 있는 값은 안 바뀜
|
||||
|
||||
|
||||
def test_utc_zone_old_text_is_kst(runner):
|
||||
r = runner("UTC") # 옛 로컬 글 10:02(한국) = UTC 01:02
|
||||
assert r["date"][:4] == ["2026-10-30 01:02"] * 4
|
||||
assert r["kst"] == "2026-10-30 01:02"
|
||||
|
||||
|
||||
def test_server_utc_text():
|
||||
assert srv.utc_text(datetime(2026, 10, 30, 1, 2, 3, 999)) == "2026-10-30T01:02:03Z"
|
||||
assert srv.utc_text(datetime(2026, 10, 30, 10, 2, 3, tzinfo=srv.KST)) == "2026-10-30T01:02:03Z"
|
||||
assert srv.utc_text(None) is None
|
||||
assert srv.utc_text("x") == "x" and srv.utc_text("") is None
|
||||
assert srv.utc_now_text().endswith("Z")
|
||||
assert srv.utc_from_timestamp(0) == "1970-01-01T00:00:00Z"
|
||||
|
||||
@@ -97,3 +104,47 @@ def test_응답_시각_끝_Z():
|
||||
srv.install_json_utc()
|
||||
body = jsonable_encoder({"at": datetime(2026, 10, 30, 1, 2, 3), "n": 1})
|
||||
assert body == {"at": "2026-10-30T01:02:03Z", "n": 1}
|
||||
|
||||
|
||||
def test_kst_text_파일_글():
|
||||
assert srv.kst_text("2026-10-30T01:02:03Z") == "2026-10-30 10:02:03"
|
||||
assert srv.kst_text("2026-10-30T10:02:03") == "2026-10-30 10:02:03" # 옛 글 = 한국 시각
|
||||
assert srv.kst_text("2026-10-30 10:02:03") == "2026-10-30 10:02:03"
|
||||
assert srv.kst_text(datetime(2026, 10, 30, 10, 2, 3, tzinfo=srv.KST)) == "2026-10-30 10:02:03"
|
||||
assert srv.kst_text(None) == "" and srv.kst_text("x") == "x"
|
||||
|
||||
|
||||
def test_D04_확정_시각_끝_Z():
|
||||
from D04_PlanConfirm.D04_PlanConfirm_Router import unit_summary, year_summary
|
||||
|
||||
at = datetime(2026, 10, 30, 1, 2, 3)
|
||||
assert year_summary({"year": 2026, "route_json": {}, "confirmed_at": at})["confirmed_at"] == (
|
||||
"2026-10-30T01:02:03Z"
|
||||
)
|
||||
unit = {
|
||||
"piece_id": None, "line": 1, "line_name": "본선", "year": 2026, "length_m": 0,
|
||||
"planned": [], "confirmed_at": at,
|
||||
} # fmt: skip
|
||||
assert unit_summary(unit)["confirmed_at"] == "2026-10-30T01:02:03Z"
|
||||
|
||||
|
||||
def _server_sources():
|
||||
skip = {"venv", "node_modules", "resources", "tmp", "docs", ".claude", "config", "storage"}
|
||||
for dirpath, dirs, files in os.walk(ROOT):
|
||||
dirs[:] = [d for d in dirs if d not in skip and not d.startswith(".")]
|
||||
for name in files:
|
||||
if name.endswith(".py"):
|
||||
path = Path(dirpath) / name
|
||||
yield path, path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_서버_시각_글_Z_없는_길_0():
|
||||
# DB 시각을 글로 뽑는 SQL = 끝 Z · 시각 칸을 str() 로 감싸 내보내지 않음
|
||||
bad_sql = re.compile(r"%i:%%?s(?!Z)")
|
||||
bad_str = re.compile(r"""\bstr\([^)\n]*(_at|timestamp|_until)["']\]\)""")
|
||||
found = []
|
||||
for path, text in _server_sources():
|
||||
for no, line in enumerate(text.splitlines(), 1):
|
||||
if bad_sql.search(line) or bad_str.search(line):
|
||||
found.append(f"{path.name}:{no}")
|
||||
assert found == []
|
||||
|
||||
Reference in New Issue
Block a user