Existing time stored in clipboard, subtract 10 minutes, set clipboard to new time

Hi all!

I handle scheduling and billing for my mental health practice. Our system used to let us set default durations at 50 minutes. They did some “upgrades” and the only default duration is now 60 minutes. I handle hundreds of sessions per week so manually changing the End Time was annoying so I figured I’d write a script to help.

Attached, you’ll see the interface I’m working with. Using a combination of Keyboard Maestro and the included script, I do the following:
• (me) Hover cursor over End Time entry
• (KM) Triple click to select all
• (KM) Cut (storing to clipboard)
• (AppleScript) Do the math/calculations to subtract 10 minutes and store New End Time to clipboard
• (KM) Paste

I finally got my script to work, handling AM vs PM, giving 12AM a value of 0 hours, going from hour 10 to hour 9 but keeping 2 characters in the result, etc… the only time I haven’t figured out yet is when the New End Time crosses the midnight marker. I get negative values.

I know there is a much more slick method of doing this which would be a lot cleaner. If anybody has some insight to rewriting the AppleScript. Thanks in advance!!

-Ryan

--(add to your clipboard as an example of what the existing time would be)
--02:30 PM
set OldEndTime to the clipboard

set OldHour to text 1 thru 2 of OldEndTime --10
set OldColon to text 3 of OldEndTime --:
set OldMin to text 4 thru 5 of OldEndTime --00
set OldSpacer to text 6 of OldEndTime -- 
set OldPhase to text 7 thru 8 of OldEndTime --AM/PM
set AdjPhase to "" --PM/AM?
set HourPaste to "" --Need a blank starting point?

--Figure out new total of minutes minus 10 based on AM or PM
if OldPhase is "AM" and OldHour is "12" then --12AM gets 0 hour minutes
	set SumHours to "00"
	set SumMins to OldMin
	set TotalMins to SumHours + SumMins
else
	if OldPhase is "PM" and OldHour is "12" then --12PM gets 12 hour minutes
		set SumHours to (OldHour * 60)
		set SumMins to OldMin
		set TotalMins to SumHours + SumMins
	else
		--AM or PM
		if OldPhase is "PM" then --PM adds 12 hour minutes
			set SumHours to (60 * OldHour)
			set SumMins to OldMin
			set SumPhase to (12 * 60)
			set TotalMins to SumPhase + SumHours + SumMins
		else
			set SumHours to (60 * OldHour) --AM adds 0 hour minutes
			set SumMins to OldMin
			set SumPhase to (0)
			set TotalMins to SumPhase + SumHours + SumMins
		end if
	end if
end if
set NewEndTime to TotalMins - 10 --Subtracts 10 minutes, changing OldEndTime from 60 minutes to 50 minutes
log result

if NewEndTime ≥ 720 then --If new time ends up in AM territory, set new phase to AM, otherwise do PM
	set NewPhase to "PM"
else
	set NewPhase to "AM"
end if
log result

set TrimHour to ((((NewEndTime)) / 60)) as text
log result
if TrimHour = 0 then
	set NewHour to "12"
else
	if 10 > TrimHour and TrimHour > 1 then
		set NewHour to (character 1 of TrimHour as string)
	else
		set NewHour to (characters 1 thru 2 of TrimHour as string)
	end if
end if
log result

if NewHour is "0." and NewPhase is "AM" then
	set HourPaste to "12" --Set midnight hour to show "12"... "AM"
	--display dialog "1"
else
	if NewHour is "12" and NewPhase is "PM" then
		set HourPaste to "12" --Set midnight hour to show "12"... "AM"
		--display dialog "2"
	else
		if 22 ≤ NewHour and NewHour ≤ 23 then
			set HourPaste to (NewHour - 12) --Set 10PM to 11PM
			--display dialog "3"
		else
			if 13 ≤ NewHour and NewHour ≤ 21 then
				set HourPaste to ("0" & (NewHour - 12)) --Set 1PM to 9PM with a leading 0
				--display dialog "4"
			else
				if 10 ≤ NewHour and NewHour ≤ 11 then
					set HourPaste to (NewHour) --Set 10AM to 11AM
					--display dialog "5"
				else
					if 1 ≤ NewHour and NewHour ≤ 9 then
						set HourPaste to ("0" & NewHour) --Set 1AM to 9AM with a leading 0
						--display dialog "6"
					end if
				end if
			end if
		end if
	end if
end if

--Minutes Calculation
set TrimMin to ((NewEndTime - (NewHour * 60)))
if TrimMin < 10 then
	set MinPaste to ("0" & TrimMin)
else
	set MinPaste to TrimMin
end if

--(HourPaste & ":" & MinPaste & " " & NewPhase as text)
set the clipboard to (HourPaste & ":" & MinPaste & " " & NewPhase as text)

I have to admit to being a little confused - you seem to have overly complicated the matter.

At first glance, I thought the calculation can be broken-down to:

Take the ‘minutes’ value
If minutes >= 10, subtract 10
if minutes < 10, subtract 1 from ‘hours’ and calculate (60 - minutes).

Adjust AM/PM if necessary

Format the time and return to the app

but in writing that I found an even easier way to do it - namely calculate the ‘minute’ of the day, subtract 10 and convert to HH:MM. This is what I came up with:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

set OldEndTime to the clipboard

-- use AppleScript text parsing to break out the time string
set {h, m, p} to words 1 through 3 of OldEndTime

-- do we have a PM time? if so, convert to 24h clock (add 12 hours)
if p = "PM" then set h to h + 12

-- calculate the number of minutes in the day to the specified time
set minTime to h * 60 + m

-- now subtract 10 minutes
set newMinTime to minTime - 10

-- calculate the hour and minute based on the new time
set newHour to newMinTime div 60
set newMinute to newMinTime mod 60

-- assume morning
set newAMPM to "AM"
-- check if it's a PM time
if newHour > 12 then
	-- if so, subtract 12 hours
	set newHour to newHour - 12
	-- and change the AM/PM designation
	set newAMPM to "PM"
end if

-- check for single digit hours/minutes and asjust
if newHour < 10 then set newHour to "0" & newHour
if newMinute < 10 then set newMinute to "0" & newMinute

-- construct the new time string
set newTimeString to newHour & ":" & newMinute & space & newAMPM as text

-- and we're done
return newTimeString
1 Like

Oh, I found a use case where my script fails - namely if the old end time is within the first 10 minutes of the day, and therefore t-10 would be the previous day.

Small corner case, but if it’s important you can add an additional check to ensure that newMinTime is > 10 before performing the calculations

1 Like

What a great solution, and SO much more simplified than what I was able to come up with.

Thank you!

I’m seeing what you’re speaking of if the time goes into the previous day, the AM doesn’t roll back to PM. This is better than mine throwing a negative value. We’ll never have a session go that late in the day, but I’ll play around to see if I can make it work correctly 100% of the time just for fun.

Thank you for the commentary as well, this is tremendously helpful in the thought process.

update: actually, if original End Time is 12:09 PM, it’s showing 11:59 PM as the New End Time. If original End Time is 01:00 AM, it’s showing 00:50 AM as the New End Time. I never thought working with time values could be so hard, but it’s fun to figure out! I’ll work off of your model a bit, thanks again!

Here is an alternative approach. It converts the ‘end time’ to seconds after midnight, subtracts 600 seconds and creates a new date object. It should only have a problem if the ‘oet’ is before 12:10 am, which presumably is unlikely. That said, I added an if…then to throw up a dialogue in that scenario. It should be possible to get it to subtract a day if needed.

-- in the comments, 'date' refers only to date information (ie year,month,day) and time refers only to time of day information (ie hour,minute,second)
use scripting additions

-- example original end time
set oet to "2:30 PM"

-- enter date of event
set ymd to "2024-09-30" -- use whichever format works with your system settings

-- combine given event date and original end time into new date object
set doet to date (ymd & space & oet as text)
--> date "Monday, September 30, 2024 at 2:30 PM"

-- convert original event time to seconds
set oldSeconds to time of doet
--> 52200

-- subtract 10 minutes worth of seconds (modified end time)
if oldSeconds is greater than 599 then -- ie oet is at least 12:10 am
	set newSeconds to oldSeconds - 600
	-- replace original end time with modified end time
	set time of doet to newSeconds
	
	-- return result
	doet
	--> date "Monday, September 30, 2024 at 2:20 PM"
else
	-- if original end time is between midnight and 12:09 am
	display dialog "This meeting is oddly scheduled"
end if

@Camelot I think your script also has an issue with the time from 12:00 pm to 12:09 pm and 1:00 pm to 1:09 pm. — because the ‘12’ hour precedes the ‘1’ hour.

1 Like
use AppleScript version "2.4"
use scripting additions

Decrease_Time_Value("12:09 AM", 10) --"11:59 PM"
Decrease_Time_Value("12:09 PM", 10) --"11:59 AM"
Decrease_Time_Value("01:00 AM", 10) --"12:50 AM"

set the clipboard to "12:01 AM"
Decrease_Time_Value(the clipboard, 10) --"11:51 PM"

on Decrease_Time_Value(timeValue, minutesModifier)
	--timeVal is the time value string retrieved by your KM code.
	--Since we are recieving a string of the timeValue we create an temporary date object with this timeValue.
	set tempDateObject to (date (short date string of (current date) & " " & timeValue))
	set TimeAndDateAsNumber to ((tempDateObject) - (date "Friday, January 1, 1904 at 12:00:00 AM"))
	set earlierDateAsNumber to (TimeAndDateAsNumber - (minutesModifier * 60))
	set modDate to (date "Friday, January 1, 1904 at 12:00:00 AM") + (4.294967296E+9 + earlierDateAsNumber) mod 4.294967296E+9
	set output to time string of modDate
	set AppleScript's text item delimiters to ":"
	set output to text items of output
	set AppleScript's text item delimiters to ""
	set newTimeString to (item 1 of output) & ":" & (item 2 of output) & (text 3 thru 5 of (item 3 of output))
	return newTimeString
end Decrease_Time_Value
1 Like

You’re right - stand by :slight_smile:

1 Like

Wow - what a rathole this turned out to be :slight_smile:

I took the opportunity to refactor my earlier example.

My thinking was to use the OS to do the heavy-lifting… it has a whole suite of datetime-based functions, after all.

What I came up with was, at best inconsistencies, but maybe a bug in AppleScript parsing. I appreciate the hivemind’s thinking on this.

First, here’s the script I came up with.

The idea is to take the input string and coerce it to a date - by default, if you just pass in a time string, it assumes the current date).
From here it should be a simple matter to subtract 10 minutes (the OS does the heavy lifting of AM/PM borders, day changes, etc.).
Then reconvert back to a time string.

Three lines of AppleScript. Sounds simple, right? This should literally look like:

set theTime to "12:05 PM"

set d to date theTime
set d to d - 10 * minutes
return time string of d

Beautiful! Simple! go grab a beer.

Except, it’s not that simple. (and here’s the bug?)

AppleScript does not consistently parse the ‘PM’ component of the string. It ALWAYS assumed the time is passed in 24-hour format. This is independent of the 24-hour setting in system preferences. However, in testing, SOMETIMES it did recognize the PM time. Anecdotally, it seems like this happens the first time I run the script after editing/recompiling, but subsequent runs always ignore the AM/PM string.

So this led me down a rat hole of how to interpret AM/PM, accounting for the fact that sometimes the OS will recognize it, and sometimes it won’t.

Here’s what I came up with. A lot more than 3 lines of script, but it appears to catch every corner case of input time

set oldEndTime to the clipboard -- 
	-- parse the time string
	set {h, m, p} to words of oldEndTime
	
	-- convert to a date object
	set d to date oldEndTime
	
	-- skanky hack to deal with 24-hour system setting
	if word 3 of oldEndTime = "PM" then
		-- input string says PM, but OS doesn't recognize this
		if h as integer < 12 then set d to d + 12 * hours
	else
		-- otherwise we have a morning time, so adjust
		if h as integer = 12 then
			set d to d - 12 * hours
		end if
	end if
	
	-- at this point we have a properly formatted date object with the end time
	
	-- take off 10 minutes
	set d to d - 10 * minutes
	-- convert to a time string
	set ts to time string of d
	
	-- parse it out
	set {h, m, s} to words of ts
	
	-- adjust for 24-hour
	-- first assume AM
	set p to " AM"
	if h as integer ≥ 12 then
		set p to " PM"
		if h as integer > 12 then
			set h to h - 12
		end if
	end if
	
	set newEndTime to h & ":" & m & p as text
	return newEndTime

I tell you, trying to figure this out has been making me want to go and grab MANY beers!

In your latest script, I found an error with anything PM becoming an AM value.

You stated a bunch of the complications I’ve encountered… which lead to the SLOP of “code” I came up with.

Time values are so funny… 12 has a 0 hour value when it’s AM and has a 12 hour value when it becomes PM. Figuring out how to go from 12:00 PM to 11:50 AM when numerically they make sense. 12 is somehow less than 1 when they’re both PM. When going from a 2-character time (10:00 PM), how to come up with a 2-character time as your New End Time (09:50 PM)?

I’ve learned a lot of things that may be simple for y’all, but I’m just an end user who comes up with things that hopefully work and save myself time. I never knew about AppleScript versions, scripting additions, using mod, etc.

I believe my Decrease_Time_Value handler behaves correctly in the edge cases. Please let me know if you find that false.

FWIW, this can also be done with a shortcut, although it requires a recent version of macOS. Just select the time in the app and run the shortcut by way of the app’s Services menu or a keyboard shortcut. The existing time will be replaced with the adjusted time.

I obviously don’t have the OP’s app for testing, but the shortcut worked with several apps and text boxes in dialogs.

Adjust Time.shortcut (22.2 KB)

1 Like

Hi. This effort handles both 12-hour and 24-hour times.

set OldEndTime to "02:30 PM" --(the clipboard)
set {h, m} to OldEndTime's words 1 thru 2

-- If 12-hour time, convert the hour to 24-hour time.
set twelveHourTime to ((OldEndTime ends with "AM") or (OldEndTime ends with "PM"))
if (twelveHourTime) then
	set h to h mod 12
	if (OldEndTime ends with "PM") then set h to h + 12
end if

-- Subtract 10 minutes from the time and extract the new hour and minutes.
set newSeconds to (days + h * hours + (m - 10) * minutes) mod days
set {h, m} to {newSeconds div hours, newSeconds mod hours div minutes}

-- If 12-hour time, convert the hour value to that.
if (twelveHourTime) then
	set ampm to item (((h > 11) as integer) + 1) of {" AM", " PM"}
	set h to (h + 11) mod 12 + 1
else
	set ampm to ""
end if
-- Convert to text with leading zeros.
tell (10000 + h * 100 + m) as text
	set NewEndTime to text 2 thru 3 & ":" & text 4 thru 5 & ampm
end tell
1 Like

Thanks peavine! I haven’t played with shortcuts much but I can see a lot of potential here!

Nigel_Garvey, I’m not finding anything that causes this to fail. Like some other suggestions above, it’s great for me to see how concise you’re all able to make this. Again, just learning about the mod function and it’s eliminating a lot of other formulas I would think of first.

I’m so used to using AppleScript’s time constants that it’s only occurred to me this morning to use minutes instead of seconds as the calculation units, which requires fewer operations. So this in my script …

-- Subtract 10 minutes from the time and extract the new hour and minutes.
set newSeconds to (days + h * hours + (m - 10) * minutes) mod days
set {h, m} to {newSeconds div hours, newSeconds mod hours div minutes}

… can be reduced to:

-- Subtract 10 minutes from the time and extract the new hour and minutes.
set newMinutes to (1430 + h * 60 + m) mod 1440 -- 1440 minutes in a day.
set {h, m} to {newMinutes div 60, newMinutes mod 60}

Or of course:

-- Subtract 10 minutes from the time and extract the new hour and minutes.
tell (1430 + h * 60 + m) mod 1440 to set {h, m} to {it div 60, it mod 60}

.:wink:

1 Like

Nigel has answered Ryan’s question. Just to work on my skills, I wrote an ASObjC solution.

use framework "Foundation"
use scripting additions

set startDate to "02:30 PM"

set dateFormatter to current application's NSDateFormatter's new()
dateFormatter's setDateFormat:"hh:mm a"
set startDate to dateFormatter's dateFromString:startDate
set adjustedDate to startDate's dateByAddingTimeInterval:-600
set adjustedDate to (dateFormatter's stringFromDate:adjustedDate) as text
2 Likes

:sunglasses::pinching_hand:t3: :flushed::dark_sunglasses::pinching_hand:t3:

This is far beyond what I expected. Thanks again for helping!