More Efficient Macro: 12-nests!

I know for a fact that I've done this the caveperson way. And I'd like to learn how to do it better or differently. Would I would like to do is choose a starting month and a markdown header level and then have the clipboard populate with LEVEL STARTMONTH.

I chose a 12-nested if statement (!). Are there better alternatives? Date math? A different action?

Ultiamtely, I'd like to ALSO choose the number of months. Example: start in october, go three months outputs:




You can use an array variable to hold the months, and then a Repeat loop to build the string. Here's one such solution that seems to work, though I only very lightly tested it.

Download Macro(s): markdown months .kmmacros (6.3 KB)

Macro screenshot

Macro notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.
System information
  • macOS 14.4.1
  • Keyboard Maestro v11.0.2

It allows you to enter the number of months, and I assumed if you chose a value that wrapped around a year-end, you'd want to start over with January again. Run it and it looks like this:

Which generates this output (with the above inputs):

## AUG
## SEP
## OCT
## NOV
## DEC
## JAN
## FEB

Is that what you're trying to achieve?


That's it! (I searched for array but couldn't figure it out!). Thank you so much.

1 Like

I like @griffman's solution. But, for fun, here's one that uses "date math" to both build a list for the prompt and to output the markdown months.

Possible advantages -- it should localise to the user's language and it's easy to change the "display" format in both prompt and final output (eg change MMMM in the %ICUDateTimeFor% token to MMM for short-form month names).

Should also be tidied to stop the repeated "seconds to add to get to the next month" calculations...

Months in Markdown (ICUDateTime).kmmacros (8.3 KB)


1 Like

Thanks so much, Nige_S! I knew that there would be multiple paths, with multiple strengths. I appreciate your time!

FWIW some elements for doing this kind of thing in an Execute JavaScript for Automation action, perhaps in a subroutine.

This kind of thing can generate month names (long and short form) in whichever locale the system is set in, or in a particular language if the two-letter locale code is given explicitly.

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

    const kmvar = {
        "local_Two_letter_Locale_Code": "", // e.g. "en", "fr", "de", "it"
        "local_Start_Month_Number": 4,
        "local_Number_of_months": 9,
        "local_Markdown_Heading_Level": 2,
        "local_Month_Names_Abbreviated": "" // 1 if true

    const main = () =>

    // monthNameSequence :: String -> Int ->
    // Int -> Bool -> [String]
    const monthNameSequence = localeName =>
        // localeName can be a two-letter string
        // If the string is empty or undefined,
        // the system locale is used by default.
        firstMonthN => monthCount => abbreviated =>
                {length: monthCount},
                (_, i) => firstMonthN + i
            .map(iMonth => new Date(0, iMonth - 1)
                localeName || undefined,
                    month: abbreviated
                        ? "short"
                        : "long"

    // asMarkdownHeading :: Int -> String -> String
    const asMarkdownHeading = n =>
        // The given string as Markdown Heading level n
        s => `${"#".repeat(n)} ${s}`;

    return main();
1 Like

Do note that mine, as written, will break around the "36 months in advance month" because it only works in 30-day "months". You could make it more resilient by using an offset of 2,629,746 each loop (seconds in a year / 12) but really, unless you are frequently changing language or format, stick with @griffman's version!

1 Like