Files
2026-07-13 12:25:07 +08:00

112 lines
5.4 KiB
TypeScript

// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import perspective from "../perspective_client";
const JAN_1_2024_UTC = 1704067200000;
test.describe("Date timezone invariance", function () {
let SAVED_TZ: string | undefined;
test.beforeEach(() => {
SAVED_TZ = process.env.TZ;
process.env.TZ = "Europe/Amsterdam";
});
test.afterEach(() => {
process.env.TZ = SAVED_TZ;
});
test("date strings serialize to UTC midnight epoch ms", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: ["2024-01-01"] });
const view = await table.view();
const cols = (await view.to_columns()) as { d: number[] };
expect(cols.d).toEqual([JAN_1_2024_UTC]);
await view.delete();
await table.delete();
});
test("date epoch ms input round-trips exactly", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: [JAN_1_2024_UTC] });
const view = await table.view();
const cols = (await view.to_columns()) as { d: number[] };
expect(cols.d).toEqual([JAN_1_2024_UTC]);
await view.delete();
await table.delete();
});
test("formatted output prints the stored calendar day", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: ["2024-01-01"] });
const view = await table.view();
expect(await view.to_columns_string({ formatted: true })).toEqual(
'{"d":["2024-01-01"]}',
);
expect(await view.to_csv()).toEqual('"d"\n2024-01-01\n');
await view.delete();
await table.delete();
});
test("day_bucket stays on the UTC calendar day at the day boundary", async function () {
const table = await perspective.table({ t: "datetime" });
// 23:59 UTC is already the next day in Europe/Amsterdam
await table.update({ t: ["2020-01-31 23:59:00"] });
const view = await table.view({
expressions: { bucket: `bucket("t", 'D')` },
});
const cols = (await view.to_columns()) as { bucket: number[] };
// 2020-01-31T00:00:00Z
expect(cols.bucket).toEqual([1580428800000]);
await view.delete();
await table.delete();
});
test("day_of_week and hour_of_day compute in UTC", async function () {
const table = await perspective.table({ t: "datetime" });
await table.update({ t: ["2020-01-31 23:59:00"] });
const view = await table.view({
expressions: {
dow: `day_of_week("t")`,
hod: `hour_of_day("t")`,
},
});
const cols = (await view.to_columns()) as {
dow: string[];
hod: number[];
};
// Friday 23:00 UTC, not Saturday 00:59 Amsterdam
expect(cols.dow).toEqual(["6 Friday"]);
expect(cols.hod).toEqual([23]);
await view.delete();
await table.delete();
});
test("JSON date output agrees with Arrow date32 day arithmetic", async function () {
const table = await perspective.table({ d: "date" });
await table.update({ d: ["2024-01-01"] });
const view = await table.view();
const cols = (await view.to_columns()) as { d: number[] };
expect(cols.d[0] % 86400000).toEqual(0);
expect(cols.d[0] / 86400000).toEqual(19723); // days since epoch
await view.delete();
await table.delete();
});
});