control/timeline/timelineUtils.tstypescript
import * as math from "../../math";

type BaselineType = "alphabetic" | "bottom" | "hanging" | "ideographic" | "middle" | "top";
type AlignType = "center" | "end" | "left" | "right" | "start";

export function addSeconds(date: Date, seconds: number): Date {
    return new Date(+date + seconds * 1000);
}

export function dateToStr(
    date: Date,
    showTime: boolean = true,
    showMilliseconds: boolean = false,
    use24HourClock: boolean = true
): string {
    let month = MONTHS[date.getUTCMonth()],
        day = date.getUTCDate(),
        year = date.getUTCFullYear();

    if (showTime) {
        let hours = date.getUTCHours(),
            suffix = "";

        if (!use24HourClock) {
            suffix = hours < 12 ? " am" : " pm";
            hours = hours % 12 || 12;
        }

        let h = hours.toString().padStart(2, "0"),
            m = date.getUTCMinutes().toString().padStart(2, "0"),
            s = date.getUTCSeconds().toString().padStart(2, "0");

        if (showMilliseconds) {
            let ms = date.getUTCMilliseconds().toString().padStart(3, "0");
            return `${month} ${day} ${year} ${h}:${m}:${s}.${ms}${suffix}`;
        }

        return `${month} ${day} ${year} ${h}:${m}:${s}${suffix}`;
    }

    return `${month} ${day} ${year}`;
}

/**
 * Formats a date by a template, e.g. "MM/dd/yyyy", "hh:mm:ss.ms". Tokens: yyyy/yy year,
 * MMM month name, MM/M month, dd/d day, hh/h hours, mm/m minutes, ss/s seconds,
 * ms milliseconds, a/A am/pm. Case matters for M/m only. On a 12-hour clock
 * am/pm is appended after the time unless the template places it with "a".
 */
export function formatDate(date: Date, template: string, use24HourClock: boolean = true): string {
    const hours24 = date.getUTCHours();
    const hours = use24HourClock ? hours24 : hours24 % 12 || 12;
    const meridiem = hours24 < 12 ? "am" : "pm";

    const pad = (n: number, len: number) => n.toString().padStart(len, "0");

    let out = "",
        hasHours = false,
        hasMeridiem = false;

    for (let i = 0; i < template.length;) {
        const at = (token: string) => template.startsWith(token, i);
        const atCI = (token: string) => template.slice(i, i + token.length).toLowerCase() === token;

        if (atCI("yyyy")) {
            out += pad(date.getUTCFullYear(), 4);
            i += 4;
        } else if (atCI("yy")) {
            out += pad(date.getUTCFullYear() % 100, 2);
            i += 2;
        } else if (at("MMM")) {
            out += MONTHS[date.getUTCMonth()];
            i += 3;
        } else if (at("MM")) {
            out += pad(date.getUTCMonth() + 1, 2);
            i += 2;
        } else if (at("ms") || at("mS")) {
            out += pad(date.getUTCMilliseconds(), 3);
            i += 2;
        } else if (at("mm")) {
            out += pad(date.getUTCMinutes(), 2);
            i += 2;
        } else if (at("m")) {
            out += date.getUTCMinutes().toString();
            i += 1;
        } else if (at("M")) {
            out += (date.getUTCMonth() + 1).toString();
            i += 1;
        } else if (atCI("dd")) {
            out += pad(date.getUTCDate(), 2);
            i += 2;
        } else if (atCI("d")) {
            out += date.getUTCDate().toString();
            i += 1;
        } else if (atCI("hh")) {
            out += pad(hours, 2);
            hasHours = true;
            i += 2;
        } else if (atCI("h")) {
            out += hours.toString();
            hasHours = true;
            i += 1;
        } else if (atCI("ss")) {
            out += pad(date.getUTCSeconds(), 2);
            i += 2;
        } else if (atCI("s")) {
            out += date.getUTCSeconds().toString();
            i += 1;
        } else if (atCI("a")) {
            out += template[i] === "A" ? meridiem.toUpperCase() : meridiem;
            hasMeridiem = true;
            i += 1;
        } else {
            out += template[i];
            i += 1;
        }
    }

    if (!use24HourClock && hasHours && !hasMeridiem) {
        out += " " + meridiem;
    }

    return out;
}

export function createCanvasHTML(): HTMLCanvasElement {
    return document.createElement("canvas");
}

export function getNearestTimeLeft(t: number, div: number): number {
    return t - (t % div);
}

export function drawNotch(
    ctx: CanvasRenderingContext2D,
    xOffset: number = 0,
    size: number = 10,
    thickness: number = 2,
    color: string = "white"
) {
    ctx.lineWidth = thickness;
    ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.moveTo(xOffset, 0);
    ctx.lineTo(xOffset, size);
    ctx.stroke();
}

export function drawText(
    ctx: CanvasRenderingContext2D,
    text: string,
    x: number,
    y: number,
    font: string = "12px Arial",
    fillStyle: string = "black",
    align: AlignType = "left",
    baseLine: BaselineType = "bottom",
    rotDeg: number = 0
) {
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(rotDeg * math.RADIANS);
    ctx.fillStyle = fillStyle;
    ctx.textBaseline = baseLine;
    ctx.font = font;
    ctx.textAlign = align;
    ctx.fillText(text, 0, 0);
    ctx.restore();
}

export const SCALES: [number, number][] = [
    [0.001, 10],
    [0.002, 10],
    [0.005, 10],
    [0.01, 10],
    [0.02, 10],
    [0.05, 10],
    [0.1, 10],
    [0.25, 10],
    [0.5, 5],
    [1.0, 10],
    [2.0, 10],
    [5.0, 5],
    [10.0, 10],
    [15.0, 15],
    [30.0, 6],
    [60.0, 12], // 1min
    [120.0, 12], // 2min
    [300.0, 5], // 5min
    [600.0, 10], // 10min
    [900.0, 15], // 15min
    [1800.0, 6], // 30min
    [3600.0, 12], // 1hr
    [7200.0, 10], // 2hr
    [14400.0, 4], // 4hr
    [21600.0, 6], // 6hr
    [43200.0, 12], // 12hr
    [86400.0, 24], // 24hr
    [172800.0, 2], // 2days
    [345600.0, 4], // 4days
    [604800.0, 7], // 7days
    [1296000.0, 15], // 15days
    [2592000.0, 5], // 30days
    [5184000.0, 6], // 60days
    [7776000.0, 9], // 90days
    [15552000.0, 18], // 180days
    [31536000.0, 12], // 365days
    [63072000.0, 2], // 2years
    [126144000.0, 4], // 4years
    [157680000.0, 5], // 5years
    [315360000.0, 10], // 10years
    [630720000.0, 2], // 20years
    [1261440000.0, 4], // 40years
    [1576800000.0, 5], // 50years
    [3153600000.0, 10], // 100years
    [6307200000.0, 2], // 200years
    [12614400000.0, 4], // 400years
    [15768000000.0, 5], // 500years
    [31536000000.0, 10] // 1000years
];

export function getScale(seconds: number) {
    for (let i = 0, len = SCALES.length; i < len; i++) {
        if (SCALES[i][0] > seconds) {
            return SCALES[i - 1];
        }
    }
}

export const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];