Formatting Dates

how-to
foundation
asobjc

(Mark Alldritt) #1

Converting dates to and from ISO 8601 format strings – and more specifically RFC 3339 format strings – is easily done using a date formatter, but there is a catch.

Here’s a pair of handlers:

use framework "Foundation"
use scripting additions

set theDate to current application's NSDate's |date|() -- now
set dateString to my rfc3339FromDate:theDate
--> "2017-11-27T06:38:31Z"
set theDate to (my dateFromRfc3339String:dateString) as date

on rfc3339FromDate:aDate
	set theFormatter to current application's NSDateFormatter's new()
	theFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:"en_US_POSIX")
	theFormatter's setTimeZone:(current application's NSTimeZone's timeZoneWithAbbreviation:"GMT") -- skip for local time
	theFormatter's setDateFormat:"yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX"
	return (theFormatter's stringFromDate:aDate) as text
end rfc3339FromDate:

on dateFromRfc3339String:theString
	set theFormatter to current application's NSDateFormatter's new()
	theFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:"en_US_POSIX")
	theFormatter's setTimeZone:(current application's NSTimeZone's timeZoneWithAbbreviation:"GMT") -- skip for local time
	theFormatter's setDateFormat:"yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX"
	return theFormatter's dateFromString:theString
end dateFromRfc3339String:

The catch: the handlers deal in NSDates. Under 10.11 and later, you can pass AppleScript dates, and coerce the result to AppleScript dates, but under earlier versions you need to add code to do the conversion.

Note that setting the locale to en_US_POSIX matters, otherwise the date format string might be interpreted differently depending on your locale.

See https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table for details of the various characters’ meaning in a format string.

You can of course use similar methods to format date strings however you wish.


(Jim Underwood) #2

Thanks for sharing, Mark.

If anyone wants a simple conversion from AppleScript date to date formated as ISO, but at the local time. you can use this:


set todayDate to current date
set todayISO to (todayDate as «class isot» as string)
-->2017-12-02T17:39:21

I often use this to just get the date in ISO format:

set dateISOStr to text 1 thru 10 of (todayDate as «class isot» as string)

(Shane Stanley) #3

For the clock-watchers…

Using «class isot» is considerably faster, although of course no help if you need time zone information.

But if you are using the date formatter method, and you’re likely to call the handler multiple times, using a property will save considerable overhead. So:

use framework "Foundation"
use scripting additions
property theFormatter : missing value

-- your code in here

on rfc3339FromDate:aDate
	if theFormatter is missing value then
		set theFormatter to current application's NSDateFormatter's new()
		theFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:"en_US_POSIX")
		theFormatter's setTimeZone:(current application's NSTimeZone's timeZoneWithAbbreviation:"GMT") -- skip for local time
		theFormatter's setDateFormat:"yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX"
	end if
	return (theFormatter's stringFromDate:aDate) as text
end rfc3339FromDate:

on dateFromRfc3339String:theString
	if theFormatter is missing value then
		set theFormatter to current application's NSDateFormatter's new()
		theFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:"en_US_POSIX")
		theFormatter's setTimeZone:(current application's NSTimeZone's timeZoneWithAbbreviation:"GMT") -- skip for local time
		theFormatter's setDateFormat:"yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX"
	end if
	return theFormatter's dateFromString:theString
end dateFromRfc3339String:

In some simple tests that speeds things up by a factor of about 6x. Creating date formatters is a relatively time consuming thing in Objective-C.

Mind you, even without this change you’re probably looking at around half a millisecond per handler call.


(Jim Underwood) #4

ISO 8601 vs RFC 339

I had not heard of “RFC 339”, so I was initially confused.
I could not find an “official” statement of difference, but did find this:

What’s the difference between ISO 8601 and RFC 3339 Date Formats? – StackOverflow.com

RFC 3339 is listed as a profile of ISO 8601.

Most notably RFC 3339 requires a complete representation of date and time (only fractional seconds are optional). The RFC also has some small, subtle differences.

For example truncated representations of years with only two digits are not allowed – RFC 3339 requires 4-digit years, and the RFC only allows a period character to be used as the decimal point for fractional seconds.

The RFC also allows the “T” to be replaced by a space (or other character), while the standard only allows it to be omitted (and only when there is agreement between all parties using the representation).

I wouldn’t worry too much about the differences between the two, but on the off-chance your use case runs in to them, it’d be worth your while taking a glance at:

RFC 3339
Wikipedia entry on ISO 8601

So, Mark, if I understand things correctly (please correct me if not), then your handlers produce a date format that meets both standards.

One other note: I read that if the “Z” is not at the end of the ISO 8601 date/time, then it is assumed to be in local time, which fits what AppleScript provides.

I’m not sure if this is pure ISO 8601 or not, but I like this format:
2017-12-02 19:51 GMT-0600

which shows me the time in my local time, but let’s other know the TZ offset.

It would be nice if your handler had a parameter for TZ. Something like:
L, Z, or ±nnn
for Local, Zulu, or specific time offset in HHMM format.


(Shane Stanley) #5

Obviously you’re welcome to make your own for your own use, but the names of the handlers — rfc3339FromDate: and dateFromRfc3339String: — imply conformance to RFC 3339.

One of the places I think people might want to use these sorts of handlers is in parsing JSON. There’s no standard defined for JSON dates, but many claim to be using RFC3339.

However, many are actually using "yyyy'-'MM'-'dd'T'HH':'mm':'ssXX" rather than "yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX" (the difference is that RFC 3339 specifies time zones other than GMT as -06:00 rather than -0600). But as long as you stick to GMT-based values, the difference doesn’t matter.

The joy of standards that aren’t…