import { describe, expect, it } from "vitest";
import { formatYmdSlash, fromDbJst, jstIsoFromDb, toDbJst, toJstIso } from "@/lib/format/date";
describe("toJstIso", () => {
it("UTC 0時 → JST 9時(日付は変わらない)", () => {
expect(toJstIso(new Date("2026-08-27T00:00:00.000Z"))).toBe(
"2026-08-27T09:00:00+09:00"
);
});
it("UTC 15時 → JST 翌0時(日付が繰り上がる)", () => {
expect(toJstIso(new Date("2026-08-27T15:00:00.000Z"))).toBe(
"2026-08-28T00:00:00+09:00"
);
});
it("秒未満は切り捨てられる(DBがTimestamp(0)/DateTime(0)なので通常は0秒)", () => {
expect(toJstIso(new Date("2026-08-27T00:00:00.500Z"))).toBe(
"2026-08-27T09:00:00+09:00"
);
});
it("元のJST文字列をnew Date()でparseしてから戻すと一致する(往復確認)", () => {
const original = "2026-08-27T02:00:00+09:00";
expect(toJstIso(new Date(original))).toBe(original);
});
});
// ★指示書Y — DBのDATETIMEは日本時間の壁時計値(TeamWorks第8回A2)。
// Prismaのアダプタはそれを UTC とみなして Date にするため、読み出しの境界で
// fromDbJst() を通して「真の瞬間」に直す。表記の約束: 「DBの生値」は
// new Date(`<値>Z`) (=アダプタの実際の挙動と同じ形)で入力を書く★
describe("fromDbJst", () => {
it("基本形(深夜清掃): DB生値 2026-09-11 02:00:00 → 真の瞬間 JST 02:00", () => {
expect(fromDbJst(new Date("2026-09-11T02:00:00Z")).getTime()).toBe(
new Date("2026-09-11T02:00:00+09:00").getTime()
);
});
it("日をまたぐ(JST 00:30)", () => {
expect(fromDbJst(new Date("2026-09-11T00:30:00Z")).getTime()).toBe(
new Date("2026-09-11T00:30:00+09:00").getTime()
);
});
it("日をまたぐ(JST 23:30)", () => {
expect(fromDbJst(new Date("2026-09-11T23:30:00Z")).getTime()).toBe(
new Date("2026-09-11T23:30:00+09:00").getTime()
);
});
it("月をまたぐ(月末の深夜)", () => {
expect(fromDbJst(new Date("2026-08-31T23:30:00Z")).getTime()).toBe(
new Date("2026-08-31T23:30:00+09:00").getTime()
);
});
it("月境界(UTCの日付で月を判定してはいけないことの証拠)", () => {
expect(fromDbJst(new Date("2026-09-01T00:30:00Z")).getTime()).toBe(
new Date("2026-09-01T00:30:00+09:00").getTime()
);
});
it("うるう年", () => {
expect(fromDbJst(new Date("2024-02-29T02:00:00Z")).getTime()).toBe(
new Date("2024-02-29T02:00:00+09:00").getTime()
);
});
it("年またぎ", () => {
expect(fromDbJst(new Date("2027-01-01T00:30:00Z")).getTime()).toBe(
new Date("2027-01-01T00:30:00+09:00").getTime()
);
});
it("ミリ秒が保たれる(情報を落とさない)", () => {
expect(fromDbJst(new Date("2026-09-11T02:00:00.500Z")).getTime()).toBe(
new Date("2026-09-11T02:00:00.500+09:00").getTime()
);
});
});
describe("toDbJst", () => {
it("基本形: 真の瞬間 → DBに書きたい壁時計値をUTCフィールドに載せたDate", () => {
expect(toDbJst(new Date("2026-09-11T02:00:00+09:00")).getTime()).toBe(
new Date("2026-09-11T02:00:00Z").getTime()
);
});
it("往復(fromDbJst(toDbJst(d)) === d)", () => {
const inputs = [
"2026-09-11T02:00:00+09:00",
"2026-09-11T00:30:00+09:00",
"2026-09-11T23:30:00+09:00",
"2026-08-31T23:30:00+09:00",
"2026-09-01T00:30:00+09:00",
"2024-02-29T02:00:00+09:00",
"2027-01-01T00:30:00+09:00",
"2026-09-11T02:00:00.500+09:00",
];
for (const s of inputs) {
const d = new Date(s);
expect(fromDbJst(toDbJst(d)).getTime()).toBe(d.getTime());
}
});
it("逆向きの往復(toDbJst(fromDbJst(d)) === d)", () => {
const inputs = [
"2026-09-11T02:00:00Z",
"2026-09-11T00:30:00Z",
"2026-09-11T23:30:00Z",
"2026-08-31T23:30:00Z",
"2026-09-01T00:30:00Z",
"2024-02-29T02:00:00Z",
"2027-01-01T00:30:00Z",
"2026-09-11T02:00:00.500Z",
];
for (const s of inputs) {
const d = new Date(s);
expect(toDbJst(fromDbJst(d)).getTime()).toBe(d.getTime());
}
});
});
// ★jstIsoFromDb() は toJstIso(fromDbJst(date)) の合成。戻り値の桁はDBの生値そのものになる
// ことが「直った」ことの判定基準(指示書Y)★
describe("jstIsoFromDb", () => {
it("DBの生値の桁がそのまま出る(これが本書の判定基準)", () => {
expect(jstIsoFromDb(new Date("2026-09-11T02:00:00Z"))).toBe(
"2026-09-11T02:00:00+09:00"
);
});
it("日付が前日に落ちない", () => {
expect(jstIsoFromDb(new Date("2026-09-11T00:30:00Z"))).toBe(
"2026-09-11T00:30:00+09:00"
);
});
it("月が変わらない", () => {
expect(jstIsoFromDb(new Date("2026-08-31T23:30:00Z"))).toBe(
"2026-08-31T23:30:00+09:00"
);
});
it("月が変わらない(逆方向)", () => {
expect(jstIsoFromDb(new Date("2026-09-01T00:30:00Z"))).toBe(
"2026-09-01T00:30:00+09:00"
);
});
it("うるう年", () => {
expect(jstIsoFromDb(new Date("2024-02-29T02:00:00Z"))).toBe(
"2024-02-29T02:00:00+09:00"
);
});
it("年またぎ", () => {
expect(jstIsoFromDb(new Date("2027-01-01T00:30:00Z"))).toBe(
"2027-01-01T00:30:00+09:00"
);
});
it("プロパティ: 任意のDB生値文字列について、DBの生値と表示が一致する(一般化)", () => {
const dbValues = [
"2026-09-11 02:00:00",
"2026-09-11 00:30:00",
"2026-09-11 23:30:00",
"2026-08-31 23:30:00",
"2026-09-01 00:30:00",
"2024-02-29 02:00:00",
"2027-01-01 00:30:00",
"2026-01-01 00:00:00",
"2026-12-31 23:59:59",
];
for (const s of dbValues) {
const isoNoOffset = s.replace(" ", "T");
const result = jstIsoFromDb(new Date(`${isoNoOffset}Z`));
expect(result).toBe(`${isoNoOffset}+09:00`);
}
});
});
// ★表7(指示書BB A4・D-182)★ — CSV の日付列は年つき `YYYY/MM/DD`(ゼロ埋め)。画面の `formatMd()` は変えない
describe("formatYmdSlash", () => {
it.each([
["C1", "2026-09-13T02:00:00+09:00", "2026/09/13"],
["C2(ゼロ埋め)", "2026-01-05T23:30:00+09:00", "2026/01/05"],
["C3(年末)", "2025-12-31T23:59:00+09:00", "2025/12/31"],
["C3(年始)", "2026-01-01T00:00:00+09:00", "2026/01/01"],
])("%s: %s → %s", (_label, iso, expected) => {
expect(formatYmdSlash(iso)).toBe(expected);
});
});