Set a Reminder for the 1st or Last Weekday of Each Month

Is there a way to make a macro that pops up a user-defined message on either the first or last weekday of each month? Ideally one would be able to specify how long the message would remain on the screen.

Yep. Checkout the Cron trigger for your macro.
Then you can use any one of the KM Actions that display text. See: Display Text action.

If none of those give you the exact control you want over the timing, then you may need to use the AppleScript Display Dialog, something like this:

display dialog "Your message goes here" giving up after 5

Hey @Tomas,

Where there's a will, there is often a way.  :sunglasses:

The appended macro should work and is set for 08:00.

Cron syntax takes a little getting used to.

https://wiki.keyboardmaestro.com/trigger/Cron
https://en.wikipedia.org/wiki/Cron

00 8 1,2,3 * *

Min, Hr, DayOfMonth, Month, WeekDayNum

1,2,3 is 1 or 2 or 3

Oops, I set the macro for “Tue” instead of “Mon”, so you'll have to fix that in the IF statement.

I could have just as easily used a normal Time of Day trigger set to Monday and then tested for the day of the month.

-Chris


Pop-Up Window with Timed Trigger v1.00.kmmacros (9.4 KB)
     ⇢ Created with KM v9.0

JMichael:

Thanks for the pointer to Cron trigger.
If I enter this as a trigger:

0 8-9 1-7 * +Mon

Can you tell me if it will run between 8 and 9 on the first Monday of every month? The Cron stuff is a bit convoluted for me...

And is there a way to get around this? (following note is from Cron trigger web-page)
"Remember that your Mac must be awake for any trigger to run, and that if the screen is sleeping, locked, or screen saving, then UI actions generally will not work."
...Sometimes I do not start up the computer until after 9 and want to make sure I see the message...

Many thanks for your help.

This is actually a bit tricky. The Cron trigger can detect the first Monday of the month. Your example:

Will run at 8:00 and 9:00 on the first Monday of the month.

But the first Monday of the month is not necessarily the first workday of the month. To do that, you will need to trigger on each of the first three days of the month (the first workday always comes within the first three days) which are also weekdays, and then check the DOW and DAY function to determine wether to run.

So a Cron Trigger of:

0 9 1-3 * +Mon-Fri

That will potentially trigger on all three first days of the month 1, 2, 3, but you want to only run on one of them.

But the if the first workday of the month is not the 1st, then it must be a Monday. And if not the 1st or 2nd, then it must also be a Monday.

So you just use two triggers:

0 9 1 * +Mon-Fri
0 9 2-3 * +Mon

So that will first at 9am, on the 1st of the month if it is a weekday, or at 9am on the 2nd or 3rd if it is a Monday (in which case the previous days would have been weekend days).

For the last workday, the same process applies, but using negative days:

0 9 -1 * +Mon-Fri
0 9 -2-3 * +Fri

I believe that should work.

To add to the various options already given, another technique you could use also employs the cron trigger, which gets set to an initial value that would represent the first weekday of next month, and triggers every minute between the hours of 8am and midday.

So, at the time of writing, I would set the cron trigger value to "* 08-12 2 9 * 2019", which represents 8-12:* on day 2 in Sep in 2019 (which is a Monday).

This solves the problem of having your computer sleeping and missing a cron trigger that is set to a range of times that are too narrow (e.g. 8-9am, which won't trigger if you wake your computer at 9.30am); or too infrequent (e.g. on the hour, or every half hour, which also risks missing an alert).

Setting it to trigger every minute over a four-hour time span means you and your computer only need to be awake for, at most, 60 seconds any time in the morning after 8am. When the macro is triggered, the first action will calculate the first weekday of the following month, and programmatically assign a new value to the cron trigger. Therefore, the macro will only ever trigger once per month, and on the precise day that you need it to.

Here's an AppleScript:

use application id "com.apple.systemevents"
use application id "com.stairways.keyboardmaestro.editor"
use scripting additions

property text item delimiters : space

-- Set the date to the 1st of the month
tell the (current date) to set [¬
	firstWeekdayOfTheMonth, ¬
	day, its month, time] to [¬
	it, 1, (its month) + 1, 0]

tell the firstWeekdayOfTheMonth
	-- If the 1st of the month is a weekend,
	-- then shift the date forward to Monday
	if its weekday is in [Saturday, Sunday] then ¬
		set the day to day + (its weekday) mod 5
	
	-- Day, month, year numerical values
	set [d, m, y] to [day, its month as integer, year]
end tell

-- The Keyboard Maestro bits
set cronValue to {"*", "08-12", d, m, "*", y} as text
set plist to {Cron:cronValue, MacroTriggerType:"Cron"}
make new property list item with properties {value:plist}
set the xml of trigger 1 of macro "__TEMP__" to the result's text

and it would be the first action in the macro:

The only edit you need to make to the script is to change the reference to the macro on the last line:

set the xml of trigger 1 of macro "__TEMP__" to the result's text

My temporary macro is called "__TEMP__", but you would name yours whatever you like, and adjust that line in the script accordingly (I actually prefer to reference macros by their UUID, which is unique and won't ever change in the lifetime of the macro. Thus, my final line would look like this:

set the xml of trigger 1 of macro id "2AE4C053-CA6D-4D2D-80C7-A1F72259FC09" to the result's text

But using the name is absolutely fine, as long as you remember to update it in your script if you ever change the name of your macro.)


The rest of the macro, which I haven't included, I imagine is very straightforward: show an alert; insert a pause for however long you wish; then close the alert.

I was playing around with displaying the alert using a Custom Floating HTML Prompt, which can be dismissed either by clicking the close button, or pressing the escape key. Here's what it looks like:

And you can nab my code below if you fancy using it:

<html>
	<head>
		<style>
			* {

			}

			body {
				margin: 0; padding: 0;
				font-family: Menlo, Hack, monospace;
				font-size: 13pt;
			}

			.container {
				position: absolute;
				display: block;
				margin: 0; padding: 0;
				left: 0; top: 0; right: 0;
				width: 100%;
			}

			.msgTitle {
				position: relative;
				display: block;
				margin: 0; padding: 0;
				left: 0; top: 0;
				font-size: 18pt;
				font-weight: 600;
				text-transform: capitalize;
			}

			.msgBody {
				position: relative;
				display: block;
				margin: 0; padding: 5px;
				margin-top: 1em;
				font-weight: 300;
				color: deeppink;
				text-align: justify;
			}

			.date {
				position: relative;
				display: block;
				margin: 0; padding: 0;
				font-family: PT Mono, monospace;
				font-size: 8pt;
				font-weight: 100;
				font-style: oblique;
				color: grey;
			}
		</style>
	</head>
	<body>
		<div class="container">
			<h2 class="msgTitle">Alert notification title</h2>
			<p class="date">current date & time</p>
			<div class="msgBody">
				I was playing around with displaying the alert using a <b><code>Custom Floating HTML Prompt</code></b>, which can be dismissed either by clicking the close button, or pressing the escape key.
			</div>
		</div>

		<script type="text/javascript">
			const _self = window.self;
			const date = new Date().toLocaleString();
			
			const msgTitle = document.querySelector('h2.msgTitle');
			const msgDate = document.querySelector('p.date');
			const msgBody = document.querySelector('div.msgBody');
			const container = document.querySelector('div.container');

			const km = window.KeyboardMaestro;
	
			container.addEventListener('keydown', (event) => {
				if (event.keyCode == 27 || event.keyCode == 10) {
					return closeMsg();
				}
			});

			function KMInit() {
				_self.status = msgTitle.innerText;
				return;
			}
	
			function KMDidShowWindow() {
				const yOffset = _self.outerHeight - _self.innerHeight;
				_self.resizeTo(400);
				_self.resizeTo(container.clientWidth, container.clientHeight+yOffset);
				_self.moveTo(0.5 * (Screen.width - _self.innerWidth),
					0.5 * (Screen.height - _self.innerHeight));
				document.body.style.width = _self.innerWidth;
				document.body.style.height = _self.innerHeight;
				msgDate.innerText = date;
				_self.status = msgTitle.innerText;
				document.title = date;
			}
	
			function KMWillCloseWindow() {
				return;
			}

			closeMsg = () => {
				_self.KeyboardMaestro.Cancel();
				return _self.close();
			}
		</script>
	<//body>
</html>

First off, thank you, everyone for the replies. I am going to be slammed with stuff to take care of until early next month, so will not have much time to poke around with everyone's suggestions. But I welcome any further ideas on this topic. I have a few trial macros in place, and if I get time before the beginning of next month, I will resume working on this...

Peter: Per your suggestion - is this set up correctly for the macro to run on the first weekday?
24%20PM

CJK: In order for the message to popup within a certain time slot, is merely adding "* 08-12" to the front of the Cron trigger sufficient, or do I need to also use the AppleScripts you provided?

Thanks

T

Random thought. Do you want the first weekday or the first business day of the month? I hope you do indeed mean weekday and not business day or else public holidays might mess you up...

If you tag a person by username preceded with an ‘@‘, the person will be notified. I just happened to stop by now and see your question for me, otherwise I would not have known.

The general answer to your query is Yes: the current range will probably be fine; and No, you do not need any additional scripting beyond what I've given you.

For a more absolute guarantee, you should advise me as certainly as you can what times you would want the macro to trigger between I went by the implied reference you appeared content with, which was 8-9am, but with a margin of error to extend the cut off to sometime after 9am.

Here's a specific breakdown of how you would optimise the range if you want to:

  • To summarise how the macro works: it is triggered on one, and only one, specific day of the month, namely the first weekday. On this day, the time it gets triggered is the very first 60-second window after 8am (and before 12pm) that your computer is on, awake, and has the Keyboard Maestro Engine running. Once triggered, it won't trigger again until the following month.

  • So, given this, hopefully you will notice that the end of the time range is not very strict with the conditions placed upon it that will still ensue the macro triggers whilst then also keep from triggering beyond a certain cut off. There would be very little value in narrowing the range from the right, as the macro won't trigger again after the first run.

  • The start of the time range is where you want to consider the earliest the macro should trigger that satisfies the conditions that:

    • it must not trigger any earlier than the start of the specified time range; and

    • It may trigger at some point later than the start of the specified time range; but

    • It will trigger on or after the start of the specified time range, at the earliest opportunity when the computer is on, and awake, and the KM engine is running.

So if the macro triggered at 8am, and you were content with reading the alert and not require to be alerted again, then this is fine. If you know that you don't want the alert before, say, 9am,. that you would charge the start of the range to 9am.

If you require repeat alerts within the time range, then come back to me and I'll advise on this.

1 Like

Apologies, it might be clear if you waded through my reply above that I misread your query, and thought you were asking if additional AppleScripts were needed to tighten the time range.

So the actual answer to your query I have read again is Yes, you do need to use the AppleScript I provided. If you omit the AppleScript action, the * 08-12 time range will trigger the macro every 60 seconds from 8am until midday.

However, if you really want to avoid the use of a script, and are slightly relaxed about repeat alerts, you can narrow the time range from the right and reduce the frequency of repetition to have the macro triggered, say, between 8-10am every half hour: */30 08-10.