All files / webapp/model germanDate.ts

93.33% Statements 28/30
92.85% Branches 26/28
100% Functions 3/3
93.33% Lines 28/30

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                                                        1056993x 3172x     1053821x   1053821x 1140x     1052681x 1052681x 1052681x   1052681x               1052681x   1052681x               1052681x   1052681x                       29990x 29990x   29990x 750x     29240x 582x     28658x 862x     27796x                             3113x   3113x 218x     2895x 2895x   2895x          
import { getRelativePeriodEnd } from "./supportEndThresholds";
 
/**
 * Shared date helpers for PAM's own dd.mm.yyyy format. Until recently these
 * were duplicated (with partially diverging behavior, see
 * {@link isDateWithinRelativePeriod}) across the Main, SupportStatus and
 * ProductDetail controllers.
 *
 * Deliberately plain, free functions rather than controller methods: this
 * lets them be passed around unbound - e.g. directly as a Sorter#comparator -
 * without the `this`-binding pitfalls of passing a class method as a
 * callback (see Main.controller.ts history: passing the bare method
 * reference `this.compareDates` to a Sorter would throw a TypeError as soon
 * as the table got sorted by a date field, since the Sorter calls the
 * comparator without a bound `this`).
 */
 
/**
 * Parses a PAM date value (a "dd.mm.yyyy" string) into a `Date` normalized
 * to midnight local time. Returns `null` for anything else, including
 * unparseable or calendar-invalid strings (e.g. "31.02.2026"), and for the
 * non-dd.mm.yyyy strings PAM sometimes uses for UI5 entries (e.g.
 * "Q3/2027") - every value reaching this function originates from
 * JSON-parsed PAM/OwnProducts data, which can never contain a `Date`.
 * @param value The raw value to parse
 * @returns The parsed date, or `null` if it could not be parsed
 */
export function parseGermanDate(value: unknown): Date | null {
    if (typeof value !== "string" || !value.trim()) {
        return null;
    }
 
    const parts = value.trim().split(".");
 
    if (parts.length !== 3) {
        return null;
    }
 
    const day = Number(parts[0]);
    const month = Number(parts[1]);
    const year = Number(parts[2]);
 
    Iif (
        !Number.isInteger(day) ||
        !Number.isInteger(month) ||
        !Number.isInteger(year)
    ) {
        return null;
    }
 
    const date = new Date(year, month - 1, day);
 
    Iif (
        date.getFullYear() !== year ||
        date.getMonth() !== month - 1 ||
        date.getDate() !== day
    ) {
        return null;
    }
 
    date.setHours(0, 0, 0, 0);
 
    return date;
}
 
/**
 * Comparator for sorting raw PAM date values (parsed via
 * {@link parseGermanDate}) in ascending order. Unparseable values sort last,
 * regardless of which side of the comparison they're on.
 * @param value1 The first raw date value
 * @param value2 The second raw date value
 * @returns A negative number if value1 is earlier, positive if later, 0 if equal or both unparseable
 */
export function compareDates(value1: unknown, value2: unknown): number {
    const date1 = parseGermanDate(value1);
    const date2 = parseGermanDate(value2);
 
    if (date1 === null && date2 === null) {
        return 0;
    }
 
    if (date1 === null) {
        return 1;
    }
 
    if (date2 === null) {
        return -1;
    }
 
    return date1.getTime() - date2.getTime();
}
 
/**
 * Checks whether a raw PAM date value falls within "months" months from
 * today (inclusive of today). The period ends on the last day of the target
 * month, see {@link getRelativePeriodEnd} in supportEndThresholds.ts.
 * @param value The raw date value to check
 * @param months How many months from today the period extends
 * @returns `true` if the parsed date lies within that period
 */
export function isDateWithinRelativePeriod(
    value: unknown,
    months: number
): boolean {
    const maintenanceDate = parseGermanDate(value);
 
    if (maintenanceDate === null) {
        return false;
    }
 
    const today = new Date();
    today.setHours(0, 0, 0, 0);
 
    return (
        maintenanceDate.getTime() >= today.getTime() &&
        maintenanceDate.getTime() <= getRelativePeriodEnd(today, months).getTime()
    );
}