Midday / packages /import /src /utils.ts
Jules
Final deployment with all fixes and verified content
c09f67c
import * as chrono from "chrono-node";
import { format } from "date-fns";
/**
* Validates if a date is actually valid (handles month lengths and leap years).
* Creates a Date object and verifies the components match what was requested.
*/
function isValidDate(year: number, month: number, day: number): boolean {
const date = new Date(year, month - 1, day);
return (
date.getFullYear() === year &&
date.getMonth() === month - 1 &&
date.getDate() === day
);
}
/**
* Formats a date string into YYYY-MM-DD format.
* For ISO-format dates (YYYY-MM-DD...), extracts the date portion directly to preserve
* the date in the original timezone (avoids UTC conversion shifting the date).
* Falls back to chrono-node for other formats.
*/
export function formatDate(dateString: string): string | undefined {
if (!dateString?.trim()) return undefined;
const trimmed = dateString.trim();
// Fast path: extract date directly from ISO-like formats (YYYY-MM-DD...)
// This preserves the date in the source timezone instead of converting to UTC
const isoMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (isoMatch) {
const [, year, month, day] = isoMatch;
const y = Number(year);
const m = Number(month);
const d = Number(day);
// Validate the date is actually valid (handles month lengths and leap years)
if (isValidDate(y, m, d)) {
return `${year}-${month}-${day}`;
}
// Invalid ISO-format date (e.g., Feb 30), return undefined
return undefined;
}
// Fallback: use chrono-node for other formats (Oct 1, 2025, 01/10/2025, etc.)
const parsed = chrono.parseDate(trimmed);
if (!parsed) return undefined;
return format(parsed, "yyyy-MM-dd");
}
export function formatAmountValue({
amount,
inverted,
}: {
amount: string;
inverted?: boolean;
}) {
let value: number;
// Handle special minus sign (−) by replacing with standard minus (-)
const normalizedAmount = amount.replace(/−/g, "-");
if (normalizedAmount.includes(",")) {
// Remove thousands separators and replace the comma with a period.
value = +normalizedAmount.replace(/\./g, "").replace(",", ".");
} else if (normalizedAmount.match(/\.\d{2}$/)) {
// If it ends with .XX, it's likely a decimal; remove internal periods.
value = +normalizedAmount.replace(/\.(?=\d{3})/g, "");
} else {
// If neither condition is met, convert the amount directly to a number
value = +normalizedAmount;
}
if (inverted) {
return +(value * -1);
}
return value;
}