Apple Script: Can’t get date of "2018-12-12 10:00 AM"
AppleScript dates are notoriously picky about format. They take their cue from your own system date & time settings, so even if I got a script to work properly on my system utilising a specific date & time format, it would probably not work on your system unless you happened to have the same macOS settings.
Therefore, the best way to deal with AppleScript date objects is to create them from scratch using day, month, year and time components. That way, they work consistently across all systems, but, most importantly, allow you to manipulate them in your own script without unexpected errors.
Add the following handler to the bottom of your script. It will give you the means to create an AppleScript date object by supplying to it the day, month and year, and (optionally) the hour, minute and second components:
to makeASDate given year:y as integer ¬
, month:m ¬
, day:d as integer ¬
, hours:h as integer : 0 ¬
, minutes:min as integer : 0 ¬
, seconds:s as integer : 0
local y, m, d, h, min, s
tell the (current date) to set ¬
[ASDate, year, its month, day, time] to ¬
[it, y, m, d, h * hours + min * minutes + s]
ASDate
end makeASDate
To use it, simply call the handler like so:
makeASDate given year:2018, month:January, day:23
--> date "Tuesday, 23 January 2018 at 00:00:00"
or
makeASDate given year:2018, month:1, day:23, hours:14, minutes:30, seconds:00
--> date "Tuesday, 23 January 2018 at 14:30:00"
Now, your next task is to extract the year, month a day components from your variables in order to pass them to the makeASDate
handler. The easiest way to do this is by splitting your date/time variables into words. For example, with your start date and time:
set [sy, sm, sd] to words of (sDate of theResult)
--> {"2018", "12", "12"}
set [sh, smin, AMPM] to words of (sTime of theResult)
--> {"10", "00", "AM"}
if AMPM = "PM" then set sh to sh + 12
and similarly with your end times. I've illustrated a quick test with your time variable to determine whether or not it is AM or PM, and changed the value of the hour component accordingly (the makeASDate
handler uses the 24-hour clock). You'll have to do another test with your date variable to determine what format the date is in: dd/mm/yyyy
or yyyy-mm-dd
, which might be as easy as:
if (sDate of theResult) contains "-" then
set [sy, sm, sd] to words of (sDate of theResult)
else if (sDate of theResult) contains "/" then
set [sd, sm, sy] to words of (sDate of theResult)
end if
What @CJK said -- trying to cook up a date string is a losing battle because it depends on user settings, use the components instead -- except that their handler does not always work. If you run it on a day numbered more than the number of days in the month of the input, the result will be the month after the one you asked for. For example, if you ask for "month:2 day:15" on January 31, you'll get March 15, not February 15. There are three factors involved, none of which seem to be explicitly documented:
-
AppleScript
date
objects are stored as an absolute point in time, not a set of components. (Well, mostly absolute: for historical reasons, they're relative to the current time zone.) Components are synthesized on demand from that point in time using the current calendar. -
Setting a component, such as
set month of d to April
, means it decomposes the date into components, changes the one component, and then composes them back into a point in time. What happens if one of the components is out of range is not specified, but in practice, an out-of-range day will wrap to some following month. Consider, continuing the example,set day of d to 41
. April 41st is, logically, 11 days after the last day of April, April 30, so you get May 11. -
Even when using pattern-set syntax
set {x, y} to {1, 2}
, the assignments happen one at a time. (The documentation https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_cmds.html#//apple_ref/doc/uid/TP40000983-CH216-SW52 describes them as "simultaneous", sinceset {x, y} to {y, x}
does in fact exchangex
andy
, but that isn't quite right: what really happens is that it evaluates all the expressions first, storing the results in implicit temporaries, then assigns the temporaries to the variables one at a time. All the evaluations happen before all the assignments, but the order is otherwise unspecified.)
So, say you run this on January 31, 2020:
makeASDate given year:2018, month:2, day:15
What happens is:
-
set ASDate to (current date)
. ASDate is January 31, 2020 at whatever the current time is. -
set year of ASDate to 2018
. ASDate is now January 31, 2018. -
set month of ASDate to 2
. The components specify February 31, 2018, which doesn't exist, so it wraps: d is now March 3, 2018. -
set day of ASDate to 15
. d is now March 15, 2018, not February 15 like you asked for.
There are two ways around this: one is to first set the day
and month
in that order to 1 first, then apply the input parameters. The other is to use a fixed date with low-numbered month and day, say "1/1/1904", instead of current date
as the starting point. A corrected handler might look like this:
to makeASDate given year:y as integer ¬
, month:m ¬
, day:d as integer ¬
, hours:h as integer : 0 ¬
, minutes:min as integer : 0 ¬
, seconds:s as integer : 0
local y, m, d, h, min, s, p
set p to "1/1/1904"
tell the (date p) to set ¬
{ASDate, year, its month, day, time} to ¬
{it, y, m, d, h * hours + min * minutes + s}
ASDate
end makeASDate