Date Duration/Date Calculator Macro

Does anyone have a good suggestion for an already built duration or date calculator macro?

I want to calculate the difference between 2 dates for example

Start date: 06/23/2022 -- End Date: 08/22/2022

Result 61 days or 2 months 1 day etc.

It depends a bit on how you want to capture or enter the two dates.

In an Execute JavaScript for Automation action, some of the basic elements might have this kind of shape:

(() => {
    "use strict";

    return (new Date("08/22/2022") - new Date("06/23/2022")) / (
        1000 * 60 * 60 * 24
    );
})();

(JS date differences are integers representing milliseconds, and the DateTime values created above are midnight at the start of the calendar day)

For example:

Compound difference between two dates.kmmacros (6.7 KB)


Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    // Rob Trew @2016, @2022

    // main :: IO ()
    const main = () => {
        const localNames = [
            "weeks", "days", "hr", "min", "sec"
        ];

        const
            kme = Application("Keyboard Maestro Engine"),
            [fromSeconds, toSeconds] = [
                "FromDateTime", "ToDateTime"
            ]
            .map(k => parseInt(kme.getvariable(k), 10)),

            diffSeconds = toSeconds - fromSeconds,
            diffDays = diffSeconds / (24 * 60 * 60),

            compound = compoundDuration(
                localNames
            )(
                toSeconds - fromSeconds
            );

        return `${diffDays} days = ${compound}`;
    };


    // ---------------- COMPOUND DURATION ----------------

    // compoundDuration :: [String] -> Int -> String
    const compoundDuration = labels =>
        nSeconds => weekParts(nSeconds)
        .map((v, i) => [v, labels[i]])
        .reduce((a, x) =>
            a.concat(
                x[0] ? [
                    `${x[0]} ${x[1] || "?"}`
                ] : []
            ), []
        )
        .join(", ");


    // weekParts :: Int -> [Int]
    const weekParts = nSeconds =>
        [0, 7, 24, 60, 60]
        .reduceRight((a, x) => {
            const
                r = a[0],
                mod = x !== 0 ? r % x : r;

            return [
                (r - mod) / (x || 1),
                [mod, ...a[1]]
            ];
        }, [nSeconds, []])[1];


    // MAIN ---
    return main();
})();
2 Likes

Or, (variant JS, with an updated compoundDuration function),
and buttons to reset From or To to now.

Compound difference between two dates.kmmacros (12 KB)

Expand disclosure triangle to view JS Source

(Hover cursor over the code and click the Copy icon in top right corner)

(() => {
    "use strict";

    // Rob Trew @2022

    // Ver 0.2 (an updated compound duration function)

    // main :: IO ()
    const main = () => {
        const localNames = [
            "wk", "d", "hr", "min", "sec"
        ];

        const
            kme = Application("Keyboard Maestro Engine"),
            [fromSeconds, toSeconds] = [
                "FromDateTime", "ToDateTime"
            ]
            .map(k => parseInt(kme.getvariable(k), 10)),

            diffSeconds = toSeconds - fromSeconds,
            diffDays = diffSeconds / (24 * 60 * 60),
            compound = compoundDuration(localNames)(
                diffSeconds
            );

        return `${diffDays} days = ${compound}`;
    };


    // ---------------- COMPOUND DURATION ----------------

    // compoundDuration :: [String] -> Int -> String
    const compoundDuration = localNames =>
    // A report on compound duration of a quantity of
    // seconds using five name strings for the local
    // equivalents of  "wk", "d", "hr", "min", "sec".
        nSeconds => mapAccumR(r => ([k, n]) => {
            const v = n !== 0 ? (r % n) : r;

            return [
                (r - v) / (n || 1),
                0 < v ? `${v} ${k}` : ""
            ];
        })(nSeconds)(
            zip(localNames)([0, 7, 24, 60, 60])
        )[1]
        .filter(Boolean)
        .join(", ");


    // --------------------- GENERIC ---------------------

    // mapAccumR :: (acc -> x -> (acc, y)) -> acc ->
    //    [x] -> (acc, [y])
    const mapAccumR = f =>
    // A tuple of an accumulation and a list
    // obtained by a combined map and fold,
    // with accumulation from right to left.
        acc => xs => [...xs].reduceRight(
            ([a, b], x) => second(
                v => [v].concat(b)
            )(
                f(a)(x)
            ),
            [acc, []]
        );


    // second :: (a -> b) -> ((c, a) -> (c, b))
    const second = f =>
    // A function over a simple value lifted
    // to a function over a tuple.
    // f (a, b) -> (a, f(b))
        ([x, y]) => [x, f(y)];


    // zip :: [a] -> [b] -> [(a, b)]
    const zip = xs =>
    // The paired members of xs and ys, up to
    // the length of the shorter of the two lists.
        ys => Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => [xs[i], ys[i]]);


    // MAIN ---
    return main();
})();
2 Likes

How do I get it to return:

Number of days
Number of weeks
Number of months
Number of years

More than just the 60, the number of days?

Number of months

First you would need to define that.

  • It means a calendar month with a variable number of days ? Now you need calendrical lookups or calculations.
  • It means a period of four weeks ? Just divide the number of weeks by 4.

Weeks ?

It already gives those:

Number of years ?

Calendar years, or multiples of 365 days ?

If calendar years then you need calendrical lookups or computations.


Calendrical Calculations: The Ultimate Edition: Reingold, Edward M., Dershowitz, Nachum

1 Like

As a first sketch, using JavaScript dates:

Compound difference between two dates (including calendar years and months).kmmacros (13 KB)

Expand disclosure triangle to view JS snippet
const
    jsFrom = new Date(fromSeconds * 1000),
    jsTo = new Date(toSeconds * 1000),
    calendarYearDelta = (
        jsTo.getFullYear() - jsFrom.getFullYear()
    ),
    calendarMonthDelta = (
        jsTo.getMonth() - jsFrom.getMonth()
    ),
    [years, months] = 0 > calendarMonthDelta ? [
        calendarYearDelta - 1,
        12 + calendarMonthDelta
    ] : [
        calendarYearDelta,
        calendarMonthDelta
    ];

return [
    `${diffDays} days = ${compound}`,
    `\n\n${years} years and ${months} calendar months.`
];

Expand disclosure triangle to view full JS script
(() => {
    "use strict";

    // Rob Trew @2022

    // Ver 0.2 (an updated compound duration function)
    // Ver 0.3 (added JS calendar date deltas for yr, month)

    // main :: IO ()
    const main = () => {
        const localNames = [
            "wk", "d", "hr", "min", "sec"
        ];

        const
            kme = Application("Keyboard Maestro Engine"),
            [fromSeconds, toSeconds] = [
                "FromDateTime", "ToDateTime"
            ]
            .map(k => parseInt(kme.getvariable(k), 10)),


            diffSeconds = Math.abs(toSeconds - fromSeconds),
            diffDays = diffSeconds / (24 * 60 * 60),
            compound = compoundDuration(localNames)(
                diffSeconds
            );

        const
            jsFrom = new Date(fromSeconds * 1000),
            jsTo = new Date(toSeconds * 1000),
            calendarYearDelta = (
                jsTo.getFullYear() - jsFrom.getFullYear()
            ),
            calendarMonthDelta = (
                jsTo.getMonth() - jsFrom.getMonth()
            ),
            [years, months] = 0 > calendarMonthDelta ? [
                calendarYearDelta - 1,
                12 + calendarMonthDelta
            ] : [
                calendarYearDelta,
                calendarMonthDelta
            ];

        return [
            `${diffDays} days = ${compound}`,
            `\n\n${years} year(s) and `,
            `${months} calendar month(s).`
        ].join("");
    };


    // ---------------- COMPOUND DURATION ----------------

    // compoundDuration :: [String] -> Int -> String
    const compoundDuration = localNames =>
    // A report on compound duration of a quantity of
    // seconds using five name strings for the local
    // equivalents of  "wk", "d", "hr", "min", "sec".
        nSeconds => mapAccumR(r => ([k, n]) => {
            const v = n !== 0 ? (r % n) : r;

            return [
                (r - v) / (n || 1),
                0 < v ? `${v} ${k}` : ""
            ];
        })(nSeconds)(
            zip(localNames)([0, 7, 24, 60, 60])
        )[1]
        .filter(Boolean)
        .join(", ");


    // --------------------- GENERIC ---------------------

    // mapAccumR :: (acc -> x -> (acc, y)) -> acc ->
    //    [x] -> (acc, [y])
    const mapAccumR = f =>
    // A tuple of an accumulation and a list
    // obtained by a combined map and fold,
    // with accumulation from right to left.
        acc => xs => [...xs].reduceRight(
            ([a, b], x) => second(
                v => [v, ...b]
            )(
                f(a)(x)
            ),
            [acc, []]
        );


    // second :: (a -> b) -> ((c, a) -> (c, b))
    const second = f =>
    // A function over a simple value lifted
    // to a function over a tuple.
    // f (a, b) -> (a, f(b))
        ([x, y]) => [x, f(y)];


    // zip :: [a] -> [b] -> [(a, b)]
    const zip = xs =>
    // The paired members of xs and ys, up to
    // the length of the shorter of the two lists.
        ys => Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => [xs[i], ys[i]]);


    // MAIN ---
    return main();
})();

1 Like