Get Last Opened date of the file

The following script works correctly (?) on my Mac.

Last_Opened_OF(choose file) --> date "Sunday, 16 April 2023 - 6:00:51 PM"

on Last_Opened_OF(theAlias)
	set iso8601Text to do shell script "mdls -name kMDItemLastUsedDate -raw " & quoted form of (POSIX path of theAlias)
	if iso8601Text is "(null)" then return "Probably never opened"
	set dt to (current date)
	set savedDelimeters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {"-", ":", space}
	set {dt's year, dt's month, dt's day, dt's hours, dt's minutes, dt's seconds} to text items 1 thru 6 of iso8601Text
	set AppleScript's text item delimiters to savedDelimeters
	return dt
end Last_Opened_OF

I would like to know if it work correctly also on other computers (in a different locale). In general, I would like to have a universal solution with good speed. A good AsObjC solution to this problem would also come in handy.

NOTE: the result should be localized AppleScript date object (not the string)

1 Like

It does so here in a German locale:
date "Montag, 17. April 2023 um 09:57:55". Or rather: Kind of. The output of mdls is
2023-04-17 09:57:55 +0000. So, that’s in GMT. But I’m on GMT+0200, which the script does not consider. See here:

As usual, I don’t know about AS(ObjC). Here’s a version in JXA, I guess the AS variant looks similar (albeit a bit longer). The script foregoes the check if the attribute exists, though.

ObjC.import("CoreServices");
(() => {
  const path = '/Users/....'; /* Insert your POSIX path here */
  const mditem = $.MDItemCreateWithURL($.kCFAllocatorDefault, $.NSURL.fileURLWithPath($(path)));
  const lastUsedDate = $.MDItemCopyAttribute(mditem, $.kMDItemLastUsedDate);
  console.log(lastUsedDate.js)
})()

Here, the result is a JavaScript Date object, which console.log implicitly converts to a String:
Mon Apr 17 2023 11:57:55 GMT+0200 (Mitteleuropäische Sommerzeit)
Which is, coincidentally, the correct time. So, maybe going the ASObjC way is better, not only speed-wise.

1 Like

Before modifying the date you should first set its day to 1, to prevent rollover into the next month:

set dt to (current date)
set dt's day to 1      

And yes, it works here as well [nl locale].

1 Like

Why’s that necessary given that the script sets the day anyway later on? Where could that roll-over happen in this script?

1 Like

So, the NSURLContentAccessKey would be the same as the kMDItemLastOpenDate? Interestingly, not here. For my file, the NSURLContentAccessKey records NSURLContentAccessDateKey:date "Montag, 17. April 2023 um 11:58:10" , whereas the
kMDItemLastOpenDate is 2023-04-17 11:57:55, so about 15 seconds earlier.

I know, nit-picking. But still – the open time is when the file was opened (eg by an application). The contentaccessdate being slightly later might mean that it refers to either the time when the app has read the content or when it has saved the content again.

Apple’s documentation, as usual, is preciously mum about the details.

1 Like

dt is a date object obtained using current date. If the script’s run on the 31st of some month and you set dt’s month to February, April, June, September, or November, the rollover will be into the following month. That’s why you should set the day to < 29 before changing the month.

2 Likes

Yes it is not Last Opened Date. The correct AsObjC solution should read kMDItemLastUsedDate (like in your JsObjC code. The result is ISO8601 string. Then should exist additional step: converting ISO8601 string to NSDate, then to AppleScript date.

I just take a big doubt that you need to take into account the GMT - offset. After all, the last opening of the file is entered locally, that is, mdls should always reflect +0000, which it does. For new files and files downloaded somewhere (+ not opened yet), the result is the string “(null)”.

The correct test would not be what you think is correct, but whether the Last Opened file in the Finder window matches the result of my script. So far I have put a like to everyone - for participation.

Nope. The value returned from $.MDItemCopyAttribute is an NSDate (in that case). Which is converted to a JavaScript Date object by appending .js to the value.
In my script, This Date object is then implicitly converted to a String by passing it as a parameter to console.log().

I suppose that in the case of ASObjC, the value returned by MDCopyItemAttribute() would be an AppleScript date object/structure/record/whatever, not a string. In any case, the return value of MDCopyItemAttribute() is a CFTypeRef, not a CFString.

Right. It does. But my timezone is not +0000. Your script takes the output of mdls and doesn’t consider the local timezone. So, the resulting date string is off by two hours (in my case). One can, of course, argue that the date should always be in UTC. But that might be a bit confusing, since Finder and other apps do consider the timezone when displaying the time.

Which means that you could end up with a “last accessed time” that is before the “created time” shown in Finder. Leading to more confusion. Therefore, I suggest to

  • either use the MDItem approach with AS/JSObjC (which at least for JS does return the time in the local timezone)
  • or manually correct the timezone returned by mdls (which is the approach taken in the post I linked to before).

In my case, I opened a 32 MByte raw image in Photoshop and then closed it again (!). Which resulted in the two time stamps differing by 15 seconds.

The issue has been discussed before:

https://discussions.apple.com/thread/1339416

and

(scroll down to the end for this one).

It’s not “in terminal” that you use a different timezone, it’s globally on your machine. If you run a date in terminal, you’ll see the timezone your machine is currently in. In my case, it’s CEST, aka central european summer time. I guess your’s is the same.

The timezone is set with the file /etc/localtime, and you can see it with

ls -l /etc/localtime
lrwxr-xr-x  1 root  wheel  39 12 Apr 18:33 /etc/localtime -> /var/db/timezone/zoneinfo/Europe/Berlin

That’s a binary file, though! When you move to another TZ, your Mac automatically adjusts the value (unless you’ve changed your global Time/Date preferences)

Thanks to everyone for participating in the discussion. Special thanks to @chrillek who helped me clear up some mishaps with the mdls result. For some reason, mdls does not take into account localization. That is, my original script also works inaccurately.

The JsObjC code provided by @chrillek is the solution, so I’ll accept it. The person is just an ace in JavaScript, but not familiar with AsObjC.

For those who prefer AsObjC, I’ve written here an AsObjC counterpart for the JsObjC code provided by @chrillek. It also works correctly and is the solution.
.

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

set posixPath to POSIX path of (choose file)

set theURL to (current application's |NSURL|)'s fileURLWithPath:posixPath
set mdItem to current application's NSMetadataItem's alloc()'s initWithURL:theURL
(mdItem's valueForAttribute:"kMDItemLastUsedDate") as date
1 Like

Odd.

Your script fails on my mac.
When i run script below, you will notice that ‘kMDItemLastUsedDate’ is not one of the keys available.

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

set posixPath to POSIX path of (choose file)

set theURL to (current application's |NSURL|)'s fileURLWithPath:posixPath
set mdItem to current application's NSMetadataItem's alloc()'s initWithURL:theURL
mdItem's attributes() as list

I’m assuming it would be one of there (‘kMDItemContentModificationDate’ or ‘kMDItemUserModifiedDate’ or ‘kMDItemFSContentChangeDate’)
The first two return a date. The last one returns an array(list) with 1 date in it.
(They are all the same date/time)

Hi @kniazidisR.

Just a minor point. I believe the coercion from NSDate to AS date requires AppleScript 2.5 (OS X 10.11, El Capitan) or later.

1 Like

Which happens if the file has not been opened before. Like an image downloaded from a camera.

1 Like

Ahhhh! Thanks for the clarification

.
So, my AsObjC script needs little correction:
.

use AppleScript version "2.5" -- EI Capitan (10.11) or later
use framework "Foundation"
use scripting additions

set posixPath to POSIX path of (choose file)

set theURL to (current application's |NSURL|)'s fileURLWithPath:posixPath
set mdItem to current application's NSMetadataItem's alloc()'s initWithURL:theURL

try
	return (mdItem's valueForAttribute:"kMDItemLastUsedDate") as date
on error
	return missing value -- not opened yet
end try

Hi @KniazidisR.

Did you see my comment in post 15 above?

1 Like

Thanks for this correction. I updated my last script in this thread.

FWIW, I would use Fredrik71’s solution. It returns the exact same result as the NSMetadataItem ASObjC solution (to the second) on my Ventura computer, and it works with files on drives that are not indexed (important to me). My test files were Shane’s ASObjC book, and a text file I created. I opened these files by double-clicking on them in Finder, and by opening them from within Preview/TextEdit.

My test implementation of Fredrik71’s suggestion:

use AppleScript version "2.5" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set theFile to (choose file)
set theURL to current application's |NSURL|'s fileURLWithPath:(POSIX path of theFile)
set theKey to current application's NSURLContentAccessDateKey
set {theResult, theDate} to (theURL's getResourceValue:(reference) forKey:theKey |error|:(missing value))
return theDate as date
2 Likes

Well, the times are not always the same. But the “not indexed” argument is a strong one - kMDItemLastOpenDate might only be available for spotlighted files.

Peavine, No need for the second line

set theKey to current application’s NSURLContentAccessDateKey

just do this

use AppleScript version "2.5" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set theFile to (choose file)
set theURL to current application's |NSURL|'s fileURLWithPath:(POSIX path of theFile)
set {theResult, theDate} to (theURL's getResourceValue:(reference) forKey:"NSURLContentAccessDateKey" |error|:(missing value))
return theDate as date
1 Like