firehol/netdata

View on GitHub
src/libnetdata/parsers/duration.html

Summary

Maintainability
Test Coverage
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Duration Converter</title>
    <style>
        table {
            width: 50%;
            border-collapse: collapse;
            margin-top: 20px;
        }
        table, th, td {
            border: 1px solid black;
        }
        th, td {
            padding: 10px;
            text-align: center;
        }
        .error {
            color: red;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<h1>Duration Converter</h1>
<input type="text" id="durationInput" placeholder="Enter duration (e.g., 10d-12h)">
<div id="errorMessage" class="error"></div>

<table id="resultTable">
    <thead>
    <tr>
        <th>Unit</th>
        <th>Value</th>
        <th>Formatted</th>
        <th>Check</th>
    </tr>
    </thead>
    <tbody>
    </tbody>
</table>

<script>
    const NSEC_PER_USEC = 1000;
    const USEC_PER_MS = 1000;
    const NSEC_PER_SEC = 1000000000;
    const NSEC_PER_MS = USEC_PER_MS * NSEC_PER_USEC;
    const NSEC_PER_MIN = NSEC_PER_SEC * 60;
    const NSEC_PER_HOUR = NSEC_PER_MIN * 60;
    const NSEC_PER_DAY = NSEC_PER_HOUR * 24;
    const NSEC_PER_WEEK = NSEC_PER_DAY * 7;
    const NSEC_PER_YEAR = NSEC_PER_DAY * 365;
    const NSEC_PER_MONTH = NSEC_PER_DAY * 30;
    const NSEC_PER_QUARTER = NSEC_PER_MONTH * 3;

    const units = [
        { unit: "ns", formatter: true, multiplier: 1 },
        { unit: "us", formatter: true, multiplier: NSEC_PER_USEC },
        { unit: "ms", formatter: true, multiplier: NSEC_PER_MS },
        { unit: "s", formatter: true, multiplier: NSEC_PER_SEC },
        { unit: "m", formatter: true, multiplier: NSEC_PER_MIN },
        { unit: "min", formatter: false, multiplier: NSEC_PER_MIN },
        { unit: "h", formatter: true, multiplier: NSEC_PER_HOUR },
        { unit: "d", formatter: true, multiplier: NSEC_PER_DAY },
        { unit: "w", formatter: false, multiplier: NSEC_PER_WEEK },
        { unit: "wk", formatter: false, multiplier: NSEC_PER_WEEK },
        { unit: "mo", formatter: true, multiplier: NSEC_PER_MONTH },
        { unit: "M", formatter: false, multiplier: NSEC_PER_MONTH },
        { unit: "q", formatter: false, multiplier: NSEC_PER_QUARTER },
        { unit: "y", formatter: true, multiplier: NSEC_PER_YEAR },
        { unit: "Y", formatter: false, multiplier: NSEC_PER_YEAR },
        { unit: "a", formatter: false, multiplier: NSEC_PER_YEAR }
    ];

    function durationFindUnit(unit) {
        if (!unit) return units[0];
        return units.find(u => u.unit === unit) || null;
    }

    function roundToResolution(value, resolution) {
        if (value > 0) return Math.floor((value + (resolution - 1) / 2) / resolution);
        if (value < 0) return Math.ceil((value - (resolution - 1) / 2) / resolution);
        return 0;
    }

    function parseDouble(str) {
        str = str.trim();
        const match = str.match(/^[-+]?\d*\.?\d+/);
        if (match) {
            const number = parseFloat(match[0]);
            const remainingStr = str.slice(match[0].length).trim();
            return { number, remainingStr };
        }
        return { number: null, remainingStr: str };
    }

    function durationParse(duration, unit) {
        if (!duration || !unit) return false;

        let s = duration.trim();
        let nsec = 0;
        let isNegative = false;

        // Handle leading negative sign
        if (s.startsWith("-")) {
            isNegative = true;
            s = s.slice(1).trim();
        }

        while (s.length > 0) {
            s = s.trim();

            if (s.startsWith("never") || s.startsWith("off"))
                return 0;

            const { number, remainingStr } = parseDouble(s);
            if (number === null) return false;

            s = remainingStr;

            const match = s.match(/^([a-zA-Z]*)/);
            let currentUnit = unit;
            if (match && match[0].length > 0) {
                currentUnit = match[0];
                s = s.slice(match[0].length).trim();
            }

            const du = durationFindUnit(currentUnit);
            if (!du) return false;

            nsec += number * du.multiplier;
        }

        const unitMultiplier = durationFindUnit(unit).multiplier;
        nsec = roundToResolution(nsec, unitMultiplier);

        return isNegative ? -nsec : nsec;
    }

    function durationSnprintf(value, unit) {
        if (value === 0) return "off";

        const duMin = durationFindUnit(unit);
        let nsec = Math.abs(value) * duMin.multiplier;

        const isNegative = value < 0;
        let result = isNegative ? "-" : "";

        for (let i = units.length - 1; i >= 0 && nsec !== 0; i--) {
            const du = units[i];
            if (!du.formatter && du !== duMin) continue;

            const multiplier = du.multiplier;
            const rounded = (du === duMin) ? roundToResolution(nsec, multiplier) * multiplier : nsec;
            let unitCount = Math.floor(rounded / multiplier);

            if (unitCount !== 0) {
                result += `${unitCount}${du.unit}`;
                nsec -= unitCount * multiplier;
            }

            if (du === duMin) break;
        }

        return result || "off";
    }

    function updateTable() {
        const duration = document.getElementById("durationInput").value;
        const tableBody = document.getElementById("resultTable").querySelector("tbody");
        const errorMessage = document.getElementById("errorMessage");
        tableBody.innerHTML = "";
        errorMessage.textContent = "";

        units.forEach(unit => {
            let value = durationParse(duration, unit.unit);
            let formatted;
            let check;
            if(value === false) {
                value = "-";
                formatted = "";
                check = "parsing error";
            }
            else {
                formatted = durationSnprintf(value, unit.unit);
                const parsedValue = durationParse(formatted, unit.unit);
                check = (parsedValue === value) ? "ok" : `re-parsing error (${parsedValue})`;
            }

            const row = `<tr>
                <td>${unit.unit}</td>
                <td>${value}</td>
                <td>${formatted}</td>
                <td>${check}</td>
            </tr>`;
            tableBody.innerHTML += row;
        });
    }

    document.getElementById("durationInput").addEventListener("input", updateTable);
</script>
</body>
</html>