AST Script Library: A Reluctant Understudy for Applescript Toolbox

Ever since the tragic end announced last October of Bastiaan “DJ Bazzie Wazzie” Boertien’s magical sparkly Applescript Toolbox (https://astoolbox.wordpress.com/2018/10/02/the-end-of-applescript-toolbox/), I have stubbornly refused to upgrade any of my Macs to Mojave. Apple’s decision to disallow all 3rd party scripting additions, even when properly signed by known developers, is at best myopic and at worst contemptuous of AS developers.

And with no real compelling reasons to upgrade to Mojave — Dark Mode is fun, but it can in no way compensate for the loss of essential functionality embedded into scores of Applescripts I use every day — I’ve been very content to continue life at High Sierra ranch this past year. But now that we’re learning more about Marzipan, and the ability to one day hopefully see beloved IOS apps ported for Mac, it was time to begin figuring out a replacement for AST Toolbox, beyond disabling System Integrity Protection.

Below you’ll find:

  1. My first draft of a script library to replace AST’s commands, including 7 of the 20 AST commands I use the most:

    • AST date with format
    • AST format date
    • AST find regex
    • AST guess language for
    • AST mouse point location
    • AST URL decode
    • AST URL encode

2)Just like AST, all functions were built for fast execution speed. You’ll also note that my implementation of AST find regex is very bare bones, with none of the Toolbox’s shiny bells and whistles.

  1. Examples demonstrating how the script library’s syntax compares to AST’s are at the end of this post. I hewed as closely as possible to the original, even throwing in an AST prefix in recognition of Toolbox’s legacy, just like the NS prefix in cocoa pays tribute to NeXTSTEP. I’ve also included a couple of explanations where necessary, but please see Applescript Toolbox’s dictionary for far more helpful and detailed instructions.

  2. For a basic primer on script libraries, see https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/UseScriptLibraries.html.

Please note:

  1. The script library should be saved as “AST.scpt”, in your script folder path.

  2. Scripts invoking the script library will need to include the following statement in their header, and should also declare a global AST variable:
    set AST to load script “Macintosh HD:Your Script Folder Path:AST.scpt”) as alias
    global AST

  3. Due to an insanely busy day job, I won’t be able to participate in any active discussions about these functions, or respond to private messages, but I’ll try and pop in every few weeks or so.

  4. My apologies in advance for any and all errors. These functions are up and running on my Macs, and I hope they’ll run fine on yours, too.

Feel free to suggest corrections / additions, etc. I can only hope DJ Bazzie Wazzie knows how truly wuly thankful I am for all his tremendous efforts.

AST.scpt:


use framework "Foundation"

#AST mouse point location
on ASTmousePointLocation()
	return {item 1 of (current application's NSEvent's mouseLocation() as list), (current application's NSHeight((current application's class "NSScreen"'s mainScreen())'s visibleFrame())) - (item 2 of (current application's NSEvent's mouseLocation() as list))}
end ASTmousePointLocation

#AST guess language for
on ASTguessLanguageFor(theText)
	return (current application's NSLinguisticTagger's dominantLanguageForString:theText) as text
end ASTguessLanguageFor

#AST URL encode
on ASTURLEncode(theText)
	return ((current application's NSString's stringWithString:(theText))'s stringByAddingPercentEscapesUsingEncoding:(current application's NSASCIIStringEncoding)) as text
end ASTURLEncode

#AST URL decode
on ASTURLDecode(theText)
	return ((current application's NSString's stringWithString:(theText))'s stringByReplacingPercentEscapesUsingEncoding:(current application's NSASCIIStringEncoding)) as text
end ASTURLDecode

#AST date with format (Returns date created with the given format. For information on formats, please see http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns)
on ASTDateWithFormat(theFormat, theDate, theLocale)
	set dateFormatter to current application's NSDateFormatter's alloc()'s init()
	set dateFormatter's locale to current application's NSLocale's localeWithLocaleIdentifier:theLocale
	set dateFormatter's dateFormat to theFormat
	set theDate to (dateFormatter's dateFromString:theDate)
	set theDate to theDate as date
	return theDate
end ASTDateWithFormat

#AST format date (Returns string representation of a date in the given format. For information on formats, please see http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns)
on ASTformatDate(theFormat, theDate, theLocale)
	set {theYear, theMonth, theDay, theSeconds} to theDate's {year, month, day, time}
	if theYear < 0 then
		set theYear to -theYear
		set theEra to 0
	else
		set theEra to 1
	end if
	set theCalendar to current application's NSCalendar's currentCalendar()
	set theDate to theCalendar's dateWithEra:theEra |year|:theYear |month|:(theMonth as integer) |day|:theDay hour:0 minute:0 |second|:theSeconds nanosecond:0
	set theFormatter to current application's NSDateFormatter's new()
	theFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:theLocale)
	theFormatter's setDateFormat:theFormat
	set theString to theFormatter's stringFromDate:theDate
	return theString as text
end ASTformatDate

#AST find regex (Returns a list of regex matches in a string.  Variable 'theGroup' is an integer referencing the parts of the matches which correspond to a particular group in the regex. Group 1 references the whole regex, group 2 is the first parenthesised subgroup, and so on, tracking the parenthesis openings from left to right.)
on ASTfindRegex(theRegex, theText, theGroup)
	set theText to current application's NSString's stringWithString:theText #encoding:(current application's NSUTF8StringEncoding)
	set theRegex to current application's NSRegularExpression's regularExpressionWithPattern:(theRegex) options:(current application's NSRegularExpressionAnchorsMatchLines) |error|:(missing value)
	set theFinds to theRegex's matchesInString:theText options:0 range:{0, theText's |length|()}
	set theResults to current application's NSMutableArray's array()
	repeat with aFind in theFinds
		if theGroup is 1 then
			(theResults's addObject:(theText's substringWithRange:(aFind's range())))
		else if theGroup > 1 then
			(theResults's addObject:(theText's substringWithRange:(aFind's rangeAtIndex:(theGroup - 1))))
		end if
	end repeat
	return theResults as list
end ASTfindRegex

Examples: Applescript Toolbox vs. AST Script Library


#IMPORTANT: BE SURE TO UPDATE THE LINE BELOW WITH YOUR SCRIPT FOLDER PATH
set AST to load script  "Macintosh HD:Your Script Folder Path:AST.scpt") as alias

global AST

set {theX1, theY1} to AST mouse point location
tell AST to set {theX2, theY2} to ASTmousePointLocation()

set theLanguage1 to AST guess language for ("eins, zwei, drei, vier")
tell AST to set theLanguage2 to ASTguessLanguageFor("eins, zwei, drei, vier")

set theEncodedText1 to AST URL encode "u / \\ doing & why?"
tell AST to set theEncodedText2 to ASTURLEncode("u / \\ doing & why?")

set theDecodedText1 to AST URL decode "u%20/%20%5C%20do%20&%20why?"
tell AST to set theDecodedText2 to ASTURLDecode("u%20/%20%5C%20do%20&%20why?")

set theRegex1 to AST find regex "(\\d)(\\.)(\\d)" in string "3.4, 3.9 and 4.1" regex group 4
tell AST to set theRegex2 to ASTfindRegex("(\\d)(\\.)(\\d)", "3.4, 3.9 and 4.1", 4)

set theDateString1 to AST format date "EEEE d MMMM yyyy" of date current date using locale "en_US"
tell AST to set theDateString2 to ASTformatDate("EEEE d MMMM yyyy", current date, "en_US")

set theDate1 to AST date with format "EEEE d MMMM yyyy HH:mm" date string "Saturday 20 April 2019 15:30" using locale "en_US"
tell AST to set theDate2 to ASTDateWithFormat("EEEE d MMMM yyyy HH:mm", "Saturday 20 April 2019 15:30", "en_US")

1 Like

Hi. Welcome to MacScripter, Happy Easter, and thanks for posting your library! I’m sure DJ (if he’s still around) will be gratified to read your comments about AppleScript Toolbox. I know he put a lot of thought, time, and effort into it.

MacScripter’s forum for posting code out of the blue which others may find useful or informative is Code Exchange. If you find this thread disappears from here, don’t worry. It will just have been moved to there!

Looking through the library code quickly, I’d point out that NSString’s stringByAddingPercentEscapesUsingEncoding: and stringByReplacingPercentEscapesUsingEncoding: methods are deprecated as from macOS 10.12. We’re told to use stringByAddingPercentEncodingWithAllowedCharacters: and stringByRemovingPercentEncoding instead. The first allows certain groups of characters not to be escaped, depending on which part of the URL you’re encoding. The second doesn’t require any parameters. Both methods were introduced with Mac OS 10.9 (Mavericks), which is also when the ability to use library scripts containing ASObjC code was introduced. So no additional version checking’s required.

In the examples script, an alternative to using load script would be to put AST.scpt in ~/Library/Script Libraries/ and replace this …

#IMPORTANT: BE SURE TO UPDATE THE LINE BELOW WITH YOUR SCRIPT FOLDER PATH
set AST to load script "Macintosh HD:Your Script Folder Path:AST.scpt") as alias

global AST

… with this at the top of the script:

use AST : script "AST"

The library has to be in place when scripts using it are compiled, but it’s apparently reloaded each time they’re run, so they don’t need to be recompiled whenever the library’s updated.

Even earlier: 10.11. If anyone’s interested in why, the 10.11 release notes have a detailed explanation:

https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_11Notes

Ah. Thanks, Shane. I was looking at the Xcode 10.2.1 documentation, which has the earlier methods as being current from 10.0 to 10.11 (but now deprecated). Interestingly (confusingly?) those release notes describe them as NSURL methods, while the Xcode documentation lists all four methods only under NSString.

It’s a bit confusing, but that macOS 10.0-10.11 means introduced in 10.0 and deprecated in 10.11. I suspect it’s auto-generated from the header files and the API_DEPRECATED notation. Odd about NSURL instead of NSString.

Hi jeromecarney.

NSEvent and NSScreen are AppKit classes, so you need a ‘use’ statement for that framework too.

Your ASTmousePointLocation() handler executes the mouseLocation() method twice, coerces the result to list twice, and makes the mistake of assuming that the items in a record coerced to list will be in a particular order. Also, a screen’s visibleFrame() excludes the menu bar and any edge occupied by the Dock. I’d be inclined to base the calculation on the frame() instead, which covers the entire screen area:


use framework "Foundation"
use framework "AppKit"

#AST mouse point location
on ASTmousePointLocation()
	tell (current application's NSEvent's mouseLocation()) to return {its x, (current application's NSHeight((current application's class "NSScreen"'s mainScreen())'s frame())) - (its y)}
end ASTmousePointLocation

According to the Xcode documentation, the dominantLanguageForString: method used in your ASTguessLanguageFor() handler was only introduced in High Sierra, so ideally you should include a comment saying it won’t work on earlier systems and/or, better still, include code which returns this information if the library’s tried on such. I know it’s intended for use on Mojave or later, but it wouldn’t hurt to cover the possibility of someone trying to use on an earlier system. I forget what the AppleScript version is in High Sierra. I assume it’s 2.6 as Sierra’s version was 2.5 and Mojave’s is 2.7. You could perhaps add a minimum version to the ‘use’ statements at the top, although this will stop any of the other handlers being usable either on pre-High Sierra systems:


use AppleScript version "2.6"
use framework "Foundation"
use framework "AppKit"

PS. According to the documentation, a “Natural Language” framework has been introduced in Mojave which includes an NLLanguageRecognizer class which also offers a dominantLanguageForString: method. This may be one to watch in the future. It certainly seems to make better guesses. :wink:

use AppleScript version "2.6" -- High Sierra (10.13) or later
use framework "Foundation"

(current application's NSLinguisticTagger's dominantLanguageForString:("Hello")) as text -- "it"
use AppleScript version "2.7" -- Mojave (10.14) or later
use framework "Foundation"
use framework "NaturalLanguage" -- "Natural Language" doesn't compile.

(current application's class "NLLanguageRecognizer"'s dominantLanguageForString:("Hello")) as text -- "en"

Nigel, Shane: Thanks for all the very helpful comments and suggestions.

I will be travelling for work for the next couple of weeks, with a pretty extensive schedule, but in early-mid May I’ll begin delving into everything, with an eye on updating the code posted in my original post.

Thank you Jerome!

It’s better to use the higher Mac OS X foundation frameworks since they’ve adopted many features of the lower core frameworks I was using. I like it!

I’ll post an link to this topic on my website.

DJ,

One thing you might consider: you could turn your osax into a script library using the same .sdef, or part thereof. You could then either include your code as a framework in the library, or translate it to ASObjC. The only catch to the framework approach is that it wouldn’t work in Script Editor.

Just a thought…

Thanks for this post. I see that Nigel Garvey has said to use
“stringByAddingPercentEncodingWithAllowedCharacters: and stringByRemovingPercentEncoding instead.”

May I ask for an updated script for URL encoding and decoding? My interest is in sending text with Unicode characters to the Google Translate URL.

MacOS 12.3 Monterey

Hi sysmod.

An update to the library script would require slightly different code for each part of the URL, since each part needs different characters to be escaped if they occur. As a crude demo of how to escape just a query part, you can either escape it directly using stringByAddingPercentEncodingWithAllowedCharacters: and the appropriate NSCharacterSet preset …

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

set textToTranslate to "«Qui était la dame avec laquelle j'ai vous vu hier soir?» «Cela n'était pas de dame. Cela était ma femme!»"
-- The following two lines should be set as required for the job in hand.
set baseURL to "https://translate.google.co.uk/"
set query to "?hl=en&sl=auto&tl=en&text=" & textToTranslate & "&op=translate"
set query to current application's class "NSString"'s stringWithString:(query)
set qacs to current application's class "NSCharacterSet"'s URLQueryAllowedCharacterSet()
set theURL to baseURL & (query's stringByAddingPercentEncodingWithAllowedCharacters:(qacs))

… or set it as the query in an NSURLComponents object and have it escaped automatically:

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

set textToTranslate to "«Qui était la dame avec laquelle j'ai vous vu hier soir?» «Cela n'était pas de dame. Cela était ma femme!»"
-- The base URL and query string below should be set as required for the job in hand.
set baseURL to "https://translate.google.co.uk/"
set URLComponents to current application's class "NSURLComponents"'s componentsWithString:(baseURL)
tell URLComponents to setQuery:("hl=en&sl=auto&tl=en&text=" & textToTranslate & "&op=translate")
set theURL to URLComponents's |string|() as text

Thank you Nigel, great to get two possible solutions.

Here’s what I am using in VBA at present:

Function MacURLEncode(ByVal original As String, ByRef encoded As String) As Long
ScriptToRun = “use framework ““Foundation””” & vbCrLf _
& “return ((current application’s NSString’s stringWithString:(theText))'s " _
& “stringByAddingPercentEscapesUsingEncoding:(current application’s NSASCIIStringEncoding)) as text”
ScriptToRun = Replace(ScriptToRun, “(theText)”, “( “”” & original & “””)")
encoded = MacScript(ScriptToRun)

Jerome’s not returned to this site (not while logged in anyway) since posting his library script three years ago, so I’ve been tinkering with the code myself in spare moments over the past few days to implement corrections and improvements that were suggested then.

ASTmousePointLocation(): now supported by a use framework “AppKit” declaration at the top of the script and calculates the vertical coordinate translation from the screen’s full frame height instead of the “visible” one, which doesn’t include the Dock and menu bar areas.
ASTURLEncode() and ASTURLDecode(): now use non-deprecated methods.
ASTformatDate(): BC year numbers now match those in the proleptic Gregorian calendar.
ASTguessLanguageFor(), ASTdateWithFormat(), and ASTfindRegex(): unchanged (or essentially so) for now.

The only change from a use perspective is that the parameter for ASTURLEncode() is now a record or NSDictionary with properties specifying all or some of a URL’s individual components. This is because different components have slightly different encoding criteria and delimiter characters must only be encoded where they’re not actually delimiting. The recognised component labels for the input are |scheme|, user, |password|, |host|, |port|, |path|, query, and fragment. The bars in |scheme|, |password|, |host|, and |port| aren’t needed if the record’s compiled outside the influence of the StandardAdditions or if the input’s an NSDictionary. Properties corresponding to unused components can be either omitted or set to missing value and of course the order within a record or dictionary is immaterial.

Delimiters will inserted between components automatically in the output text and mustn’t be included in the input. An exception to this is the path component, which NSURLComponents expects to begin with “/” for some reason. To forgive any confusion, the handler will prepend the “/” itself if necessary. It’ll also ensure that the NSURLComponents instance receives any port number in the form of an integer.

An alternative to setting several individual input properties for components definitely not requiring encoding is to run some or all such components together into a single, technically-legal URL text and supply this as an input property labelled nocode. For example:

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use AST : script "AST Script Library"
use scripting additions

set theComponents to {|scheme|:"https", |host|:"en.wikipedia.org", |path|:"/wiki/Kīlauea", fragment:"Future_threats"}
-- Or:
set theComponents to {nocode:"https://en.wikipedia.org#Future_threats", |path|:"/wiki/Kīlauea"}

AST's ASTURLEncode(theComponents)
--> "https://en.wikipedia.org/wiki/K%C4%ABlauea#Future_threats"

So. Here’s my reworking of the library. Suggestions and comments welcome.

(* AST Script Library.
	Recreation of seven of the commands from Bastiaan Boertien's "AppleScript Toolkit" OSAX.
	Library created by Jerome Carney, 2019. Partly reworked by NG, 2022.
*)
use AppleScript version "2.5" -- OS X 10.11 (El Capitan) or later.
use framework "Foundation"
use framework "AppKit" -- For NSEvent and NSScreen.

#AST mouse point location
on ASTmousePointLocation()
	set {x:x, y:y} to current application's class "NSEvent"'s mouseLocation()
	-- Output the coordinates in list form after inverting the y for a top-of-screen origin.
	tell current application to set screenHeight to (its NSHeight((its class "NSScreen"'s mainScreen())'s frame()))
	return {x, screenHeight - y}
end ASTmousePointLocation

#AST guess language for.  NB. THIS REQUIRES MACOS 10.13 (HIGH SIERRA) OR LATER.
-- Input: text or NSString. Output: text.
on ASTguessLanguageFor(theText)
	return (current application's NSLinguisticTagger's dominantLanguageForString:theText) as text
end ASTguessLanguageFor

#AST URL encode
-- Input: record or NSDictionary containing URL components.
-- Recognised labels: nocode, |scheme|, user, |password|, |host|, |port|, |path|, query, fragment.
-- Output: percent-encoded URL text.
on ASTURLEncode(theComponents)
	-- Get the input as a record and check for an 'nocode' property.
	set inputRecord to (theComponents as record) & {nocode:missing value}
	-- Initialise an NSURLComponents object accordingly.
	if (inputRecord's nocode is missing value) then
		set URLComponentsObj to current application's class "NSURLComponents"'s new()
	else
		set URLComponentsObj to current application's class "NSURLComponents"'s componentsWithString:(inputRecord's nocode)
	end if
	-- Where individual components aren't specified, expand the input record with values from the NSURLComponents object.
	tell URLComponentsObj to set inputRecord to inputRecord & ¬
		{scheme:its scheme(), user:its user(), password:its password(), host:its host(), port:its port(), |path|:missing value, query:its query(), fragment:its fragment()}
	-- Set the NSURLComponents object's properties from the expanded record.
	tell URLComponentsObj to setScheme:(inputRecord's scheme)
	tell URLComponentsObj to setUser:(inputRecord's user)
	tell URLComponentsObj to setPassword:(inputRecord's password)
	tell URLComponentsObj to setHost:(inputRecord's host)
	-- The port value, if specified, has to be an integer.
	if (inputRecord's class is in {integer, text}) then tell URLComponentsObj to setPort:(inputRecord's port as integer)
	-- If relevant and necessary, prepend "/" to the path.
	set thePath to inputRecord's |path|
	if (thePath's class is text) then
		if (thePath does not start with "/") then set thePath to "/" & thePath
		tell URLComponentsObj to setPath:(thePath)
	end if
	tell URLComponentsObj to setQuery:(inputRecord's query)
	tell URLComponentsObj to setFragment:(inputRecord's fragment)
	-- Get a correctly-ordered, percent-encoded URL from the URLComponents object.
	return URLComponentsObj's |string|() as text
end ASTURLEncode

#AST URL decode
-- Input: percent-encoded text or NSString. Output: text.
on ASTURLDecode(theText)
	return (current application's NSString's stringWithString:(theText))'s stringByRemovingPercentEncoding() as text
end ASTURLDecode

#AST date with format
-- Input: date format text or NSString, date text or NSString in that format, local identifier as text or NSString.
-- Output: AppleScript date created with the given format. For information on formats, please see
-- http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns.
on ASTdateWithFormat(theFormat, dateString, localeID)
	set dateFormatter to current application's NSDateFormatter's new()
	tell dateFormatter to setLocale:(current application's NSLocale's localeWithLocaleIdentifier:localeID)
	tell dateFormatter to setDateFormat:(theFormat)
	return (dateFormatter's dateFromString:dateString) as date
end ASTdateWithFormat

#AST format date
-- Input: date format text or NSString, AppleScript date object, local identifier as text or NSString.
-- Output: text representation of the date in the given format. For information on formats, please see
-- http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns.
on ASTformatDate(theFormat, theDate, theLocale)
	set {theYear, theMonth, theDay, theSeconds} to theDate's {year, month, day, time}
	if (theYear < 1) then
		-- Although nominally outside AppleScript's date range, zero and negative 'year' values
		-- represent 1 BC and earlier in the proleptic Gregorian calendar.
		set theYear to 1 - theYear
		set theEra to 0 -- BC.
	else
		set theEra to 1 -- AD.
	end if
	set theCalendar to current application's NSCalendar's currentCalendar()
	set theDate to theCalendar's dateWithEra:theEra |year|:theYear |month|:(theMonth as integer) |day|:theDay hour:0 minute:0 |second|:theSeconds nanosecond:0
	set theFormatter to current application's NSDateFormatter's new()
	theFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:theLocale)
	theFormatter's setDateFormat:theFormat
	return (theFormatter's stringFromDate:theDate) as text
end ASTformatDate

#AST find regex (Returns a list of regex matches in a string.  Variable 'theGroup' is an integer referencing the parts of the matches which correspond to a particular group in the regex. Group 1 references the whole regex, group 2 is the first parenthesised subgroup, and so on, tracking the parenthesis openings from left to right.)
on ASTfindRegex(theRegex, theText, theGroup)
	set theText to current application's NSString's stringWithString:theText #encoding:(current application's NSUTF8StringEncoding)
	set theRegex to current application's NSRegularExpression's regularExpressionWithPattern:(theRegex) options:(current application's NSRegularExpressionAnchorsMatchLines) |error|:(missing value)
	set theFinds to theRegex's matchesInString:theText options:0 range:{0, theText's |length|()}
	set theResults to current application's NSMutableArray's array()
	repeat with aFind in theFinds
		if theGroup is 1 then
			(theResults's addObject:(theText's substringWithRange:(aFind's range())))
		else if theGroup > 1 then
			(theResults's addObject:(theText's substringWithRange:(aFind's rangeAtIndex:(theGroup - 1))))
		end if
	end repeat
	return theResults as list
end ASTfindRegex
1 Like

Nigel. Thanks for revising this script library. It will be very useful and is also excellent for learning purposes.

I tested a number of the handlers in the script and had a question about ASTformatDate. It works fine if the AS date object is AD, but I’m uncertain how to create a BC AS date object for use as a parameter. The comments state:

However, if I attempt to create an AS date object with a negative year, the result is an AD date.

set theDate to "1/1/-800" -- my date format is m/d/y
set theDate to date theDate --> date "Wednesday, January 1, 800 AD at 12:00:00 AM"
year of theDate --> 800

FWIW, I can create a BC AS date object by use of date math:

set theDate to "1/1/1"
set theDate to date theDate
set newDate to theDate - (3650 * days) --> date "Wednesday, January 4, 10 BC at 12:00:00 AM"
year of newDate --> -9

What’s the simplest method to create a BC AS date object for use as a parameter with this handler?

Hi peavine. Thanks for trying the script.

I used the date math method to get an AS BC date for testing. I don’t think (or recommend) that many people will have one they’ll want formatted. But since it’s possible and Jerome’s handler allows for it, I made the adjustment to get the year number right.

When I started doing AppleScript [cough] years ago, the official AppleScript date range was only from 1 January 1000 00:00:00 to 31 December 9999 23:59:59, but there was leeway to stray outside this range within date calculations. Apple hasn’t mentioned these limits for many years and its possible to set year values as low as 1. The highest they can be set still seems to be 9999. The leeway still exists and dates either side of these years can be derived through date arithmetic rather than by property setting.

While this works (I don’t know for how far) for date objects’ integer and enum properties, AppleScript’s own translations between date object and text can be somewhat off, certainly at the lower end:

set theDate to "1/1/1"
set theDate to (date theDate)
--> date "Saturday 1 January 1 at 00:00:00"

theDate's {date string, short date string, time string}
--> {"Saturday 1 January 1", "01/01/1", "00:00:00"}

-- But:
theDate's {weekday, day, month, year, hours, minutes, seconds}
--> {Saturday, 30, December, 0, 0, 1, 15}

Experimentation has shown that the integers and enums are correct with regard to an AD date’s value and it’s the translations to and from text that are wrong. (The year integer value for a BC date will be out by 1 because the integer goes down to 0, whereas the calendar goes straight from 1 AD to 1 BC.) Also, previous discussion here has revealed that the degree of error depends on the time zone to which a computer’s set. The results above are for Mojave in the UK. You may get something different.

While I’ve strayed this far from your question, I may was well mention that dates derived from strings with two-digit years are transposed to the current century or an adjacent one, depending on the two digits and where we are now:

set theDate to "1/1/50"
set theDate to (date theDate)
--> date "Sunday 1 January 1950 at 00:00:00"

set theDate to "1/1/49"
set theDate to (date theDate)
--> date "Friday 1 January 2049 at 00:00:00"

-- But:
set theDate to "1/1/0049"
set theDate to (date theDate)
--> date "Wednesday 1 January 49 at 00:00:00"

Thanks Nigel. I’ve been working to learn AS and NS dates and appreciate your detailed response.

This is good news–I was sure I missed something. I did give some thought to who would want to convert an AS BC date object to a string and couldn’t think of many. I certainly understand your decision not to alter the basic functionality of Jerome’s script.

I wasn’t aware of the above, but one issue I’ve been trying to resolve is the relative merits of using AS versus NS dates, and the above scripts (and mine below), demonstrate how confusing this can be for anyone not an expert on this topic. Anyways, I guess that makes the time I’ve spent learning the NSDate and NSCalendar classes worthwhile.

set theDate to "1/1/1"
set theDate to (date theDate)
--> date "Saturday, January 1, 1 AD at 12:00:00 AM"

set theDate to "1/1/01"
set theDate to (date theDate)
--> date "Monday, January 1, 2001 AD at 12:00:00 AM"

I have a current thread about NS dates in another forum, and I’ll extend that to include era 0 dates. Thanks again.