File size: 2,541 Bytes
c09f67c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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;
}