feat(시간 표시): 화면 시각 = 보는 사람 PC 시간대 · 서버 응답 UTC Z · 서버가 만드는 파일 글 = 한국 시각 (PLAN 593-1 · 요구 456)
- 공용 `common_util_time.ts`(날짜 · 시각 · 오늘/어제/n 일 전 · Z 없는 옛 글 = UTC) · `common_util_time.py`(UTC Z 글 · 한국 시각) - 화면 바꿈: 대시보드(프로젝트 · 휴지통 · 시스템 로그 · 보관함 · 홈 카드) · B09 굳힘 · E03 · E05 · E07 · M02 호표 이력 · 목록 고친 날 · 지난 판 - 서버: 응답 datetime = UTC + Z(main.py) · 로컬 시각 저장 → UTC · 대시보드 경과일 = UTC 기준 · 엑셀 날짜 · 파일 이름 = 한국 시각 - 시험: test_593_time(서울 · 뉴욕 · Z 없는 옛 글 · 서버 글) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,11 +27,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_time import utc_now_text
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger
|
||||
from M02_MasterTemplete import M02_Hopyo_Match as hopyo_match
|
||||
@@ -217,7 +217,7 @@ class Builder:
|
||||
}
|
||||
|
||||
def build(self, checked: dict[int, dict[str, Any]], whole: list[dict[str, Any]]) -> dict:
|
||||
stamp = datetime.now().isoformat(timespec="seconds")
|
||||
stamp = utc_now_text()
|
||||
hopyo, file = self.hopyo, self.doc.get("파일") or ""
|
||||
lines, mats, missing, used, mat_lines = [], {}, [], set(), {}
|
||||
nation: dict[tuple, dict[str, Any]] = {}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_time import utc_now_text
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Labor_Inputs as labor_inputs
|
||||
@@ -245,7 +245,7 @@ def write(
|
||||
if not entries:
|
||||
return 0
|
||||
have = read(folder)
|
||||
stamp = datetime.now().isoformat(timespec="seconds")
|
||||
stamp = utc_now_text()
|
||||
version = _version()
|
||||
out: dict[str, dict[str, Any] | None] = dict(have) # 옛 꼴도 새 꼴로 다시 씀
|
||||
for full, row in entries.items():
|
||||
|
||||
@@ -11,11 +11,11 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_time import utc_now_text
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
DIRNAME = "실무자료"
|
||||
@@ -91,7 +91,7 @@ def save(
|
||||
doc = {
|
||||
"키": key,
|
||||
"파일": name,
|
||||
"올린때": datetime.now().isoformat(timespec="seconds"),
|
||||
"올린때": utc_now_text(),
|
||||
"올린이": who,
|
||||
**parsed,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ from __future__ import annotations
|
||||
import logging
|
||||
import re
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -31,6 +30,7 @@ from B05_Profile.B05_Profile_Structures_Schema import STRUCTURE_ID_RULE
|
||||
from common_util.common_util_drainage_pipes import pipe_points_path_in, read_pipe_points_file
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_summary_item import GROUP_JOIN, snapshot_folder_name
|
||||
from common_util.common_util_time import kst_now, utc_now_text
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
from M02_MasterTemplete.M02_Item_Source import (
|
||||
LABOR_FILE,
|
||||
@@ -82,7 +82,7 @@ def safe_folder(base: Path, name: str) -> Path | None:
|
||||
|
||||
|
||||
def now_text() -> str:
|
||||
return datetime.now().isoformat(timespec="seconds")
|
||||
return utc_now_text()
|
||||
|
||||
|
||||
def same_item(manifest: Any, item: dict[str, Any]) -> bool:
|
||||
@@ -181,7 +181,7 @@ def retire(folder: Path) -> str | None:
|
||||
if isinstance(manifest, dict) and manifest.get("고침"):
|
||||
past = folder.parent / PAST_DIRNAME
|
||||
past.mkdir(parents=True, exist_ok=True)
|
||||
target = past / f"{folder.name}_{datetime.now():%Y%m%d%H%M%S}"
|
||||
target = past / f"{folder.name}_{kst_now():%Y%m%d%H%M%S}"
|
||||
shutil.move(str(folder), str(target))
|
||||
return target.name
|
||||
shutil.rmtree(folder)
|
||||
|
||||
@@ -14,11 +14,11 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_time import kst_today
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
@@ -109,7 +109,7 @@ def _load() -> tuple[dict[str, Any], str]:
|
||||
return _m01(combo_store._read)
|
||||
path = store.root() / FILE
|
||||
if not path.is_file():
|
||||
head = {"그룹": mcb.GROUP, "판": str(date.today().year), "폴더": [], "다음": 1}
|
||||
head = {"그룹": mcb.GROUP, "판": str(kst_today().year), "폴더": [], "다음": 1}
|
||||
return {**head, "줄": []}, ""
|
||||
raw = path.read_bytes()
|
||||
return json.loads(raw), store.version_of(raw)
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type LedgerRow,
|
||||
} from "./M02_MasterTemplete_Hopyo_Api";
|
||||
import { fetchLogicInputs } from "./M02_MasterTemplete_Labor_Api";
|
||||
import { dateTimeText } from "../common_util/common_util_time";
|
||||
import { openLaborInput } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick";
|
||||
import "./M02_MasterTemplete_Hopyo.css";
|
||||
@@ -262,9 +263,7 @@ export function buildHopyoLedger(opt: LedgerOptions): HTMLElement {
|
||||
},
|
||||
}),
|
||||
);
|
||||
const when = [r.누가?.이름, r.때?.replace("T", " ").slice(0, 16), r.처음파일]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
const when = [r.누가?.이름, dateTimeText(r.때, ""), r.처음파일].filter(Boolean).join(" · ");
|
||||
return el("tr", {
|
||||
className: `m02-hopyo-ledger__row${r.고침 ? "" : " is-upper"}${r.고친 ? " is-edited" : ""}`,
|
||||
attrs: { "data-ledger": r.열쇠 },
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import { createInputField, el, showToast } from "@ui/ui_template_elements";
|
||||
import { buildPager } from "../M01_MasterData/M01_MasterData_UI_Cells";
|
||||
import { dateText } from "../common_util/common_util_time";
|
||||
import { buildHeadFilters } from "../M01_MasterData/M01_MasterData_UI_Head";
|
||||
import "../M01_MasterData/M01_MasterData_UI_Style.css";
|
||||
import { fetchTree, type Layer, type TreeItem } from "./M02_MasterTemplete_Api_Fetch";
|
||||
@@ -50,7 +51,7 @@ const TEXT = {
|
||||
|
||||
const SIZE = 30;
|
||||
const DASH = "—";
|
||||
const day = (iso?: string): string => (iso ? iso.slice(0, 10) : "");
|
||||
const day = (iso?: string): string => dateText(iso, "");
|
||||
const money = (value: number | null | undefined): string =>
|
||||
typeof value === "number" ? value.toLocaleString("ko-KR", { maximumFractionDigits: 0 }) : DASH;
|
||||
/** 가장 늦게 고친 날 — 항목 통째 */
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_time import utc_from_timestamp
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
@@ -103,9 +103,7 @@ def _list_extras(folder: Path, col: str) -> dict[str, Any]:
|
||||
for kind in ("structure", "area", "labor"):
|
||||
path = folder / kind / f"{col}.json"
|
||||
if path.is_file():
|
||||
stamps[kind] = datetime.fromtimestamp(path.stat().st_mtime).isoformat(
|
||||
timespec="seconds"
|
||||
)
|
||||
stamps[kind] = utc_from_timestamp(path.stat().st_mtime)
|
||||
labor = None
|
||||
path = folder / "labor" / f"{col}.json"
|
||||
if path.is_file():
|
||||
|
||||
@@ -13,12 +13,12 @@ import threading
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from contextvars import ContextVar
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util import common_util_spreadsheet as spreadsheet
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_time import utc_from_timestamp
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
|
||||
@@ -106,7 +106,7 @@ def _path(kind: str, name: str) -> Path:
|
||||
|
||||
def _info(kind: str, path: Path) -> dict[str, Any]:
|
||||
raw = path.read_bytes()
|
||||
stamp = datetime.fromtimestamp(path.stat().st_mtime).isoformat(timespec="seconds")
|
||||
stamp = utc_from_timestamp(path.stat().st_mtime)
|
||||
row = {"종류": kind, "이름": path.stem, "판": version_of(raw), "수정일": stamp}
|
||||
if (kind, path.stem) in IN_USE:
|
||||
row["쓰는곳"] = IN_USE[(kind, path.stem)] # 화면이 지우기 확인 창 전에 막음
|
||||
|
||||
@@ -18,11 +18,11 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_time import utc_from_timestamp, utc_now_text
|
||||
from config import config_system
|
||||
|
||||
LAYERS = ("system", "company", "personal", "project")
|
||||
@@ -128,7 +128,7 @@ def version_of(path: str | Path) -> str | None:
|
||||
|
||||
|
||||
def _modified(path: Path) -> str:
|
||||
return datetime.fromtimestamp(path.stat().st_mtime).isoformat(timespec="seconds")
|
||||
return utc_from_timestamp(path.stat().st_mtime)
|
||||
|
||||
|
||||
def list_templates(layer_dir: str | Path) -> list[dict[str, Any]]:
|
||||
@@ -314,7 +314,7 @@ def _stamp(layer: str, name: str, version: str | None, **extra: Any) -> dict[str
|
||||
"층": layer,
|
||||
"이름": name,
|
||||
"판": version,
|
||||
"적용일": datetime.now().isoformat(timespec="seconds"),
|
||||
"적용일": utc_now_text(),
|
||||
}
|
||||
entry.update({key: value for key, value in extra.items() if value is not None})
|
||||
return entry
|
||||
|
||||
@@ -16,10 +16,11 @@ import asyncio
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_time import kst_now
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from config import config_system # noqa: E402
|
||||
@@ -133,7 +134,7 @@ def render(report: list[dict[str, Any]], system: list[dict[str, Any]]) -> str:
|
||||
lines = [
|
||||
"# 기존 프로젝트 양식 넣기",
|
||||
"",
|
||||
f"- 돌린 때: {datetime.now().isoformat(timespec='seconds')}",
|
||||
f"- 돌린 때: {kst_now().isoformat(timespec='seconds')}",
|
||||
f"- 시스템 양식: {names}",
|
||||
"- 방법: 작업본 · `_initial/` 에 없는 것만 더함 · 있는 양식 · 다른 파일은 그대로",
|
||||
f"- 프로젝트 {len(report)}개 · 넣음 {sum(r['상태'] == '넣음' for r in report)}개",
|
||||
|
||||
@@ -41,6 +41,7 @@ import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
import { createTabs } from "@ui/ui_template_tabs";
|
||||
import { dateTimeText } from "../../common_util/common_util_time";
|
||||
|
||||
/** 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점) — 모든 종류에서 숨김(52-12 · 정의 화면 = 이름만) */
|
||||
const OLD_EDITOR = false;
|
||||
@@ -362,7 +363,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
...(versions.length
|
||||
? versions.map((v) => {
|
||||
const b = createButton({
|
||||
label: to("PastItem", `${v.판} · ${v.저장일}`),
|
||||
label: to("PastItem", `${v.판} · ${dateTimeText(v.저장일)}`),
|
||||
variant: past?.판 === v.판 ? "filled" : "ghost",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -17,6 +16,7 @@ from pydantic import ValidationError
|
||||
|
||||
from B05_Profile import B05_Profile_Structures_Schema as schema
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_time import utc_from_timestamp
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Check import catalog_errors
|
||||
|
||||
@@ -43,7 +43,7 @@ def history() -> list[dict[str, Any]]:
|
||||
for path in folder.glob("판_*.json") if folder.is_dir() else []:
|
||||
number = path.stem.removeprefix("판_")
|
||||
if number.isdigit():
|
||||
when = datetime.fromtimestamp(path.stat().st_mtime).strftime("%Y-%m-%d %H:%M")
|
||||
when = utc_from_timestamp(path.stat().st_mtime)
|
||||
rows.append({"판": int(number), "저장일": when})
|
||||
return sorted(rows, key=lambda r: r["판"], reverse=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user