How to pass date information to AppleScript?

819 views Asked by At

I am making one app that can run AppleScript via NSAppleScript. Everything had been fine but I haven't been able to figure out how to pass date information from my app to AppleScript. (Since AppleScript has date type, I suppose this is possible) The way I pass parameters to AppleScript is through NSAppleEventDescriptor. I learned from Google that I could pass it as typeLongDateTime type:

- (id)initWithDate:(NSDate *)date {
     LongDateTime ldt;  
     UCConvertCFAbsoluteTimeToLongDateTime(CFDateGetAbsoluteTime((CFDateRef)date), &ldt);
     return [self initWithDescriptorType:typeLongDateTime
                                   bytes:&ldt
                                  length:sizeof(ldt)];
}

Unfortunately, the type LongDateTime had long gone, because I am using Swift and under OS X 10.10. Even the Core Services function UCConvertCFAbsoluteTimeToLongDateTime has already been removed from 10.10.3.

Now I am stuck.

Do you have any ideas that inspire?

4

There are 4 answers

9
Martin R On BEST ANSWER

Is seems that LongDateTime is a signed 64-bit integer which represents a date d as the number of seconds since January 1, 1904, GMT, adjusted by the time-zone offset for d in the local time zone (including daylight-savings time offset if DST is active at d).

The following code gives the same result as your Objective-C code for all dates that I tested (winter time and summer time).

class DateEventDescriptor : NSAppleEventDescriptor {

    convenience init?(date : NSDate) {
        let secondsSince2001 = Int64(date.timeIntervalSinceReferenceDate)
        var secondsSince1904 = secondsSince2001 + 3061152000
        secondsSince1904 += Int64(NSTimeZone.localTimeZone().secondsFromGMTForDate(date))
        self.init(descriptorType: DescType(typeLongDateTime),
            bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))
    }
}

Update for Swift 3:

class DateEventDescriptor : NSAppleEventDescriptor {

    convenience init?(date: Date) {
        let secondsSince2001 = Int64(date.timeIntervalSinceReferenceDate)
        var secondsSince1904 = secondsSince2001 + 3061152000
        secondsSince1904 += Int64(NSTimeZone.local.secondsFromGMT(for: date))
        self.init(descriptorType: DescType(typeLongDateTime),
                  bytes: &secondsSince1904, length: MemoryLayout.size(ofValue: secondsSince1904))
    }
}

Update for macOS 10.11:

As of macOS 10.11 there is a

 NSAppleEventDescriptor(date: Date)

initializer, so that the above workaround is no longer necessary. (Thanks to @Wevah for this information.)

0
vadian On

There is a little-known CoreFoundation constant kCFAbsoluteTimeIntervalSince1904 representing the difference between 1904 and 2001. This NSDate extension converts NSDate to NSAppleEventDescriptor and vice versa

extension NSDate {

  func appleScriptDate() -> NSAppleEventDescriptor
  {
    var secondsSince1904 = Int64(self.timeIntervalSinceReferenceDate + kCFAbsoluteTimeIntervalSince1904)
    return NSAppleEventDescriptor(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))!
  }

  convenience init(appleScriptDate : NSAppleEventDescriptor)
  {
    var secondsSince1904 : Int64 = 0
    let data = appleScriptDate.data
    data.getBytes(&secondsSince1904, length: data.length)
    self.init(timeIntervalSinceReferenceDate:NSTimeInterval(secondsSince1904) - kCFAbsoluteTimeIntervalSince1904)
  }

}

If you need to adjust the time zone information (converting to AppleScript date does not preserve the time zone) add NSTimeZone.systemTimeZone().secondsFromGMT in Swift or time to GMT in AppleScript

0
Terry On

Inspired by Martin, I got to know that the LongDateTime type is just something that records time interval since the date 1904-01-01 midnight. And AppleScript utilizes it to represent dates. However, one weird thing in AppleScript is that there is no time zone concept for date type. So, simply passing the time interval since 1904-01-01 00:00:00 +0000, would only make the resulted date in AppleScript show the time in GMT. That was why I tried Martin's suggestion but got wrong time shown from the AppleScript. Since it is a data involving time difference, I got the following way works for me:

convenience init?(date: NSDate) {
    struct StaticWrapper {
        static var longDateTimeReferenceDate: NSDate!
    }
    if StaticWrapper.longDateTimeReferenceDate == nil {
        let formatter = NSDateFormatter()
        let c = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
        formatter.calendar = c
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        StaticWrapper.longDateTimeReferenceDate = formatter.dateFromString("1904-01-01 00:00:00")
    }

    var secondsSince1904 = Int64(date.timeIntervalSinceDate(StaticWrapper.longDateTimeReferenceDate))
    self.init(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))
}

The time zone information is not given in the date formatter, which implicitly includes the current time zone. Therefore, the resulted time interval will make the AppleScript to show the time in local time zone. Which behaves like the AppleScript command "current date".

0
Peakman On

I updated vadian's extension for Swift 3:

extension NSDate {

    func appleScriptDate() -> NSAppleEventDescriptor
    {
        var secondsSince1904 = Int64(self.timeIntervalSinceReferenceDate + kCFAbsoluteTimeIntervalSince1904)
        return NSAppleEventDescriptor(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: MemoryLayout.size(ofValue: secondsSince1904))!
    }

    convenience init(appleScriptDate : NSAppleEventDescriptor)
    {
        var secondsSince1904 : Int64 = 0
        withUnsafeMutablePointer(to: &secondsSince1904) {
            _ = appleScriptDate.data.copyBytes(
                to: UnsafeMutableBufferPointer(start: $0, count: 4),
                from: 0..<4)
        }
        self.init(timeIntervalSinceReferenceDate:TimeInterval(secondsSince1904) - kCFAbsoluteTimeIntervalSince1904)
    }
}