All files / src/utils formatters.ts

99.3% Statements 143/144
96.66% Branches 29/30
100% Functions 8/8
99.3% Lines 143/144

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 1451x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 2x 2x 5x 5x 5x 5x 5x 5x 7x 3x 7x 1x 7x 1x 7x   7x 7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 26x 3x 3x 23x 23x 23x 23x 23x 26x 5x 5x 5x 26x 18x 18x 18x 18x 26x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 1x 1x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8x 6x 8x 3x 3x 3x 3x 3x 3x 8x 1x 1x 1x 1x 1x 1x 1x 1x 20x 20x 20x 20x 1x 1x 1x 1x 1x 1x 1x 1x 1x 21x 21x 21x 21x 21x  
/**
 * @file formatters.ts
 * @description
 * Date and number formatting utilities based on user preferences.
 * Supports multiple locales and formats (DE, EN_US, etc.).
 *
 * @enterprise
 * - Locale-aware formatting
 * - Reusable across components
 * - Type-safe
 * - Consistent formatting throughout app
 *
 * @usage
 * import { formatDate, formatNumber } from '../utils/formatters'
 * formatDate(new Date(), 'DD.MM.YYYY')  // → '27.11.2025'
 * formatNumber(1234.56, 'DE')           // → '1.234,56'
 */
import type { DateFormat, NumberFormat } from '../context/settings/SettingsContext';
 
/**
 * Format a date according to the specified format.
 * @param date - Date object or ISO string
 * @param format - Target date format
 * @returns Formatted date string
 * @example
 * formatDate(new Date(2025, 10, 27), 'DD.MM.YYYY')  // → '27.11.2025'
 * formatDate(new Date(2025, 10, 27), 'YYYY-MM-DD')  // → '2025-11-27'
 * formatDate(new Date(2025, 10, 27), 'MM/DD/YYYY')  // → '11/27/2025'
 */
export const formatDate = (date: Date | string, format: DateFormat): string => {
  const d = typeof date === 'string' ? new Date(date) : date;
 
  if (isNaN(d.getTime())) {
    return '';
  }
 
  const day = String(d.getDate()).padStart(2, '0');
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const year = d.getFullYear();
 
  switch (format) {
    case 'DD.MM.YYYY':
      return `${day}.${month}.${year}`;
    case 'YYYY-MM-DD':
      return `${year}-${month}-${day}`;
    case 'MM/DD/YYYY':
      return `${month}/${day}/${year}`;
    default:
      return d.toISOString().split('T')[0];
  }
};
 
/**
 * Format a number according to locale-specific conventions.
 * @param num - Number to format
 * @param format - Target number format ('DE' or 'EN_US')
 * @param decimals - Number of decimal places (default: 2)
 * @returns Formatted number string
 * @example
 * formatNumber(1234.56, 'DE')     // → '1.234,56'
 * formatNumber(1234.56, 'EN_US')  // → '1,234.56'
 * formatNumber(1000, 'DE', 0)     // → '1.000'
 */
export const formatNumber = (num: number, format: NumberFormat, decimals: number = 2): string => {
  if (typeof num !== 'number' || isNaN(num)) {
    return '';
  }
 
  const parts = num.toFixed(decimals).split('.');
  const integerPart = parts[0];
  const decimalPart = parts[1];
 
  if (format === 'DE') {
    // German: 1.234,56 (dot for thousands, comma for decimal)
    const thousands = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
    return decimals > 0 ? `${thousands},${decimalPart}` : thousands;
  } else {
    // English/US: 1,234.56 (comma for thousands, dot for decimal)
    const thousands = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return decimals > 0 ? `${thousands}.${decimalPart}` : thousands;
  }
};
 
/**
 * Get number formatting configuration for DataGrid/tables.
 * Useful for currency formatting in tables.
 * @param format - Target number format
 * @returns Object with decimal and thousands separators
 * @example
 * const config = getCurrencyFormat('DE')  // → { decimal: ',', thousands: '.' }
 */
export const getCurrencyFormat = (format: NumberFormat): { decimal: string; thousands: string } => {
  return format === 'DE'
    ? { decimal: ',', thousands: '.' }
    : { decimal: '.', thousands: ',' };
};
 
/**
 * Parse a formatted number string back to a number.
 * Useful for form inputs.
 * @param str - Formatted number string
 * @param format - Format of the input string
 * @returns Parsed number
 * @example
 * parseFormattedNumber('1.234,56', 'DE')   // → 1234.56
 * parseFormattedNumber('1,234.56', 'EN_US') // → 1234.56
 */
export const parseFormattedNumber = (str: string, format: NumberFormat): number => {
  if (!str) return 0;
 
  if (format === 'DE') {
    // German: remove dots (thousands separator), replace comma with dot
    return parseFloat(str.replace(/\./g, '').replace(',', '.'));
  } else {
    // English/US: remove commas (thousands separator)
    return parseFloat(str.replace(/,/g, ''));
  }
};
 
/**
 * Get today's date in ISO YYYY-MM-DD format.
 * @returns Today in local ISO format (e.g., '2025-12-08')
 * @example
 * getTodayIso()  // → '2025-12-08'
 */
export const getTodayIso = (): string => {
  const d = new Date();
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
};
 
/**
 * Get a date n days ago in ISO YYYY-MM-DD format.
 * @param n - Number of days ago
 * @returns Date n days ago in local ISO format
 * @example
 * getDaysAgoIso(180)  // → '2025-06-12' (180 days ago)
 */
export const getDaysAgoIso = (n: number): string => {
  const d = new Date();
  d.setDate(d.getDate() - n);
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
};