import type { PluginDefinition } from "@yaakapp/api"; import type { TemplateFunctionArg } from "@yaakapp-internal/plugins"; import type { ContextFn } from "date-fns"; import { addDays, addHours, addMinutes, addMonths, addSeconds, addYears, format as formatDate, isValid, parseISO, subDays, subHours, subMinutes, subMonths, subSeconds, subYears, } from "date-fns"; const dateArg: TemplateFunctionArg = { type: "text", name: "date", label: "Timestamp", optional: true, description: "Can be a timestamp in milliseconds, ISO string, or anything parseable by JS `new Date()`", placeholder: new Date().toISOString(), }; const expressionArg: TemplateFunctionArg = { type: "text", name: "expression", label: "Expression", description: "Modification expression (eg. '-5d +2h 3m'). Available units: y, M, d, h, m, s", optional: true, placeholder: "-5d +2h 3m", }; const formatArg: TemplateFunctionArg = { name: "format", label: "Format String", description: "Format string to describe the output (eg. 'yyyy-MM-dd at HH:mm:ss')", optional: true, placeholder: "yyyy-MM-dd HH:mm:ss", type: "text", }; export const plugin: PluginDefinition = { templateFunctions: [ { name: "timestamp.unix", description: "Get the timestamp in seconds", args: [dateArg], onRender: async (_ctx, args) => { const d = parseDateString(String(args.values.date ?? "")); return String(Math.floor(d.getTime() / 1000)); }, }, { name: "timestamp.unixMillis", description: "Get the timestamp in milliseconds", args: [dateArg], onRender: async (_ctx, args) => { const d = parseDateString(String(args.values.date ?? "")); return String(d.getTime()); }, }, { name: "timestamp.iso8601", description: "Get the date in ISO8601 format", args: [dateArg], onRender: async (_ctx, args) => { const d = parseDateString(String(args.values.date ?? "")); return d.toISOString(); }, }, { name: "timestamp.format", description: "Format a date using a dayjs-compatible format string", args: [dateArg, formatArg], previewArgs: [formatArg.name], onRender: async (_ctx, args) => formatDatetime(args.values), }, { name: "timestamp.offset", description: "Get the offset of a date based on an expression", args: [dateArg, expressionArg], previewArgs: [expressionArg.name], onRender: async (_ctx, args) => calculateDatetime(args.values), }, ], }; function applyDateOp(d: Date, sign: string, amount: number, unit: string): Date { switch (unit) { case "y": return sign === "-" ? subYears(d, amount) : addYears(d, amount); case "M": return sign === "-" ? subMonths(d, amount) : addMonths(d, amount); case "d": return sign === "-" ? subDays(d, amount) : addDays(d, amount); case "h": return sign === "-" ? subHours(d, amount) : addHours(d, amount); case "m": return sign === "-" ? subMinutes(d, amount) : addMinutes(d, amount); case "s": return sign === "-" ? subSeconds(d, amount) : addSeconds(d, amount); default: throw new Error(`Invalid data calculation unit: ${unit}`); } } function parseOp(op: string): { sign: string; amount: number; unit: string } | null { const match = op.match(/^([+-]?)(\d+)([yMdhms])$/); if (!match) { throw new Error(`Invalid date expression: ${op}`); } const [, sign, amount, unit] = match; if (!unit) return null; return { sign: sign ?? "+", amount: Number(amount ?? 0), unit }; } function parseDateString(date: string): Date { if (!date.trim()) { return new Date(); } const isoDate = parseISO(date); if (isValid(isoDate)) { return isoDate; } const jsDate = /^\d+(\.\d+)?$/.test(date) ? new Date(Number(date)) : new Date(date); if (isValid(jsDate)) { return jsDate; } throw new Error(`Invalid date: ${date}`); } export function calculateDatetime(args: { date?: string; expression?: string }): string { const { date, expression } = args; let jsDate = parseDateString(date ?? ""); if (expression) { const ops = String(expression) .split(" ") .map((s) => s.trim()) .filter(Boolean); for (const op of ops) { const parsed = parseOp(op); if (parsed) { jsDate = applyDateOp(jsDate, parsed.sign, parsed.amount, parsed.unit); } } } return jsDate.toISOString(); } export function formatDatetime(args: { date?: string; format?: string; in?: ContextFn; }): string { const { date, format } = args; const d = parseDateString(date ?? ""); return formatDate(d, String(format || "yyyy-MM-dd HH:mm:ss"), { in: args.in }); }