Working with NSDate objects- as readable AS values

The NSDatePicker class has a dateValue method.

Connect a property to the date picker in IB.
Then get the value like this.


property datePicker : missing value
set curDate to datePicker's dateValue()

-- result => 1982-02-12 08:00:00 -0800

Ok thanks, I cleaned up the first post with Shane’s recemmendation.

Also Craig’s in this post to look like:


	property datePicker : missing value
	
	on DoWithTheDate_(sender)
		set curDate to datePicker's dateValue()  ### also tried with "as Text" and with "as string"
		display dialog (curDate)	
	end DoWithTheDate_	

Now I get from the console:

2009-10-16 20:09:52.788 Test[368:a0f] -[__NSCFDate dateValue]: unrecognized selector sent to instance 0x2003df740
2009-10-16 20:09:52.788 Test[368:a0f] *** -[TestAppDelegate DoWithTheDate:]: -[__NSCFDate dateValue]: unrecognized selector sent to instance 0x2003df740 (error -10000)

(I think I’m getting closer :slight_smile: )

I think I’m on to something here, after reading a lot of the documentation, just don’t know how to make the statement in AS:

The documentation suggests adding a date formatter to the control and using it’s instance methods to get the date string. Not a good choice for calculating in AS, but I’ll take it for starters if it let’s us under the hood of the NSDate class lol.

There are 2 methods on the formatter class to extract the date string hence. This being the more relevant I think:

stringFromDate:
Returns a string representation of a given date formatted using the receiver’s current settings.

  • (NSString *)stringFromDate:(NSDate *)date

Parameters
date
The date to format.

Return Value
A string representation of date formatted using the receiver’s current settings.


So now we will have an instance of Date formatter returning from the datePicker.

Any ideas on this or am I off on a tangent?

Thanks

Read Stefan’s comment here => http://macscripter.net/viewtopic.php?id=30384

Hi Craig, Yes I read that earlier, but now it makes more sense after I stumbled into the NSDateFormatter from IB :slight_smile:

So Iare we saying one cannot coerce the strings out of (or into) the Date Picker OR the Date Formatter objects coming out of IB?

I am trying a lot of different things and getting usually a singe error in the log as above, or an execution error:
“Cannot create date from object <NSDateFormatter: 0x20055e800> of class NSDateFormatter”

So there has GOT to be a way to ASOC’ize this code. It’s just not logical to not make such an important function unavailable to ASOC IMHO… is it?

Not at all. I don’t know how long they have been working on this framework, but things like this take time. It might be another year or more before we see things working the way Apple has planned. Until then, learning Objective-C and using it when necessary is the way to go.

You might try creating an Objective-C class, hooking up all that code to the date picker, and calling it from AppleScriptObjC. Have the Objective-C class return a string of the date in the format you want.

I know it is frustrating, but it will get better. For now, I write most everything in Objective-C and only use AppleScriptObjC when I need to use Apple Events. Other than that, it is just easier (to me) to write it all in Objective-C.

Thanks Craig, I will give it a try and post back.

… but before I do :slight_smile:

Have a look at this. the DatePicker object is bound to my datePicker. Tried all variations of the NSDateFormatter too but no go, declared an NSObject class, and put those little | thingies around the description method, coerced the description to text.


property parent : class "NSObject"
	
	property datePicker : missing value
	property curDate : missing value
	
	on DoWithTheDate_(sender)
		set my curDate to datePicker's |description| as text
		log my curDate
		display dialog (my curDate)
		
	end DoWithTheDate_

	on applicationWillFinishLaunching_(aNotification)
		
		-- Insert code here to initialize your application before any files are opened 
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		-- Insert code here to do any housekeeping before your application quits 
		return true
	end applicationShouldTerminate_

Got the long date and long time (with offset from UTC)

:slight_smile:

When I selected the Value Transformers “NSKeyedUnarchiveFromData and NSUnarchiveFromData” the dialog had key coded data in it, encoded I believe.

Is this progress? (before I go the OBJ-C route)

So it seems that NSDates can’t be coerced to AS dates, or even to AS strings easily. You can get the description and parse the resulting string, but that looks fragile to me.

One of the differences between NSDate and AS dates is that AS dates assume the user’s calendar, whereas getting the details out of an NSDate involves specifying the calendar. In fact, it’s a multi-part process.

You can get the calendar in use by calling NSCalendar’s class method currentCalendar:. With that instance of a calendar, you can call the calendar’s components:fromDate: method. This returns an NSDateComponents object, which has methods for returning the year, month, etc.

So let’s assume a date from a date picker:

set theDate to my picker's dateValue()
set theCal to current application's class "NSCalendar"'s currentCalendar()
set theComponents to theCal's components_fromDate_(254, theDate)

The 254 is a mask for which components we want the result to contain; 254 means year, month, day, hour, minute, second, but there are others.

The NSDateComponents class has methods for extracting the components individually, so:

set theYear to theComponents's |year|()
set theMonth to theComponents's |month|()
set theDay to theComponents's |day|()
set theHour to theComponents's hour()
set theMinute to theComponents's minute()
set theSecond to theComponents's |second|()

Now you need to make an AS date, and set the various components:

set theDate to current date
set day of theDate to 1 -- important
set seconds of theDate to theSecond
set year of theDate to theYear
set month of theDate to theMonth
set day of theDate to theDay
set hours of theDate to theHour
set minutes of theDate to theMinute
log (theDate as text)

It’s a fair bit of code to do what I suspect should happen automatically, but on my MacBook it takes all of 0.003 seconds, so there’s no real performance hit. If you need to use it often, you could easily wrap it up in a handler.

Edited: I’ve inserted the line “set day of theDate to 1” to avoid problems when the day of the current date exceeds the number of days in the new date.

Thanks Shane. This is getting very cool.

I am continuing to experiment in IB as I absorb what’s been learned so far, and today I took some time in IB and saw that I could also connect a datePicker’s string value directly to a text field and got the string value to display in that text field with no further coding… just the connections… I had no luck with the formatters, but was able to manipulate the display of the text field easily, using the datePicker’s attributes to set the date to long, short, or none… and same with time. So in one case all I wanted in the text field was 5:23 PM, and setting the datePicker’s attributes and connecting the two I got just that. I am thinking this may work for other IB items also?

I ran out of time today but will pick up tomorrow. I think by connection to a property I should be able to take the string value easily out of the text field again for processing/conversion to AS for use in a NStimer for example. The only issue I had was populating the value of a property with the new value of the text field.
Again, this would be for parsing and processing in AS from the values input by the user into a control.

I agree with Shane that this should be automatic. In the absence of any significant ASOC documentation, maybe it’s there, just undiscovered country? :slight_smile:

Play as I may in IB with connections, I really got nowhere beyond the description field functioning as an output string in AS. But I was finally able to do some processing of date/times using Shane’s great example code above, using the resultant conversion to do math in AS.

A couple questions.
In the case of the theDate initially being an instance of NSDate coming from the datePicker, could I do my math directly between that instance and and another i.e against the result of a current time method in AS that modifies the components of a new NSDate?

Is this method (reversed), a logical way to set the datePicker to “now” for first presentation to the user? I tried the init method, but the “id” parameter got me lost. One would think it would default to that but in IB there is no “now” default setting for the control that I can see.

What does the 254 mean in the code, could you give me a reference to the documentation for that?

In one case I am using the datePicker to specify a time only, setting its attributes accordingly, using AS to calculate time since midnight to process the task once a day at that time… Would this method, without the extra code (day,month,year etc) be an appropriate use for getting that time into AS as well? or would there be something more efficient.

Many thanks!

Here is a quote from Chris Nebel from the AppleScript Studio mailing list.

You might want to look at my follow-up to Chris’s post – there seems to be a potential 64-bit issue involved.

I don’t follow…

The NSDate equivalent of current date is NSDate’s “date” class method, and you can use the picker’s setDateValue: method:

		set theDate to current application's class "NSDate"'s |date|()
		my picker's setDateValue_(theDate)

Object-C uses enumerations that represent individual bits in a byte for a lot of these flags. They are usually listed under "Constants’ in the docs. So in NSCalendar’s Calendar Units, you’ll see that NSYearCalendarUnit = kCFCanlendarUnitYear, and if you click on that you’ll eventually come to the CFCalendarUnit constants. kCFCalendarUnitYear is listed as (1 << 2), which translates to 4 as an integer. Similarly, kCFCalendarUnitMonth is (1 << 3), which means 8 ( 1* 2^3). You just add up the total of all the ones you want to include.

There may be something more efficient in typing terms, but it’ll do exactly what you want.

I meant something like:

property theDate:<> --1/1/2010
property userzDate:<>-- 1/1/2008

Code would be in the proposal

Set difDate to theDate - userzDate
log difDate (result would be either an NSdate or a string value equivalent to 2 years)

Something like that was what I was getting at… could I do math directly against the instance and not the value? (I’m guessing the answer is no)

On the post regarding using the DateFormatter to get strings from NSDates, through at least the 60 or so exercises I have done in the past 3 days on this, I was never able to get a string from a formatter, or when I did get a string from a NSDate (via the picker or a text field), it was never in the attached formatter’s … format. I couldn’t find what I was not doing right so ultimately gave up on it.

Thanks for all this great information Craig and Shane. It is truly appreciated.

No – you’d use something like:

set theDiff to theDate’s timeIntervalSinceDate_(userzDate)

Try something like this:

		set now to current application's class "NSDate"'s |date|() -- current date
		set myFormatter to current application's class "NSDateFormatter"'s alloc()'s init()
		myFormatter's setDateStyle_(1) -- 0 = none, 1 = short, 2 = med, 3 = long, 4 = full
		myFormatter's setTimeStyle_(3) 
		set theString to myFormatter's stringFromDate_(now)

In case anyone is after the code for Chris’s method:

set theDate to current application's class "NSDate"'s |date|() -- current date
set theDiff to theDate's timeIntervalSinceReferenceDate()
set ASDate to (date "Monday, January 1, 2001 12:00:00 AM") + theDiff div 1 + (time to GMT)

Unbelievable! it works! Your advice here Shane and Craig has made working with dates so flexible, and much easier to understand.

I can now:

  1. Present a DatePicker to the user with today’s date and/or time as the picker’s default.

  2. Based on the user’s input calculate a difference ,using AS seconds serial value as an option to tell the user how much time left before the task is performed.

  3. Set up an NSTimer for the scheduled task (which I learned in my previous posts)

  4. Save the user’s date setting as a datePicker object in the preferences file (also learned in previous posts regarding NS User deaults) which loads at run time to populate the item’s above during app initialization.

  5. Use the NSdateFormatter to update the log in natural language.

I am putting the finishing touches on this utility app which as I stated in my other post in the forum, was a hard coded AS app running headless Now it has a really elegant UI with a drawer for optional preferences, and a single window with live updating etc.

When I am finished I will put a sample in this thread to show what the above assistance has helped me to accomplish regarding dates and date math, but would also like to give Shane and Craig a copy of the finished product to show what I’ve done in the 21 days (in my spare time) since I came here looking for help as a complete amateur with no AS Studio or XCode knowledge. I am sure you will probably chuckle at some things that could be done easier, but the version of the app without this date option has been running non stop for 2 weeks on 3 local machines and 2 beta tester’s equipment (and they love it!) so the result is that this product is very robust.

There are still a few things to do, like make an icon, and implement help although it is very straight forward. I have the “About” code finished, and I need to find a place to host it as a freeware utility (for non-commercial anyway) and also I need to learn how to obscure my script/code in the bundle as it seems to be there in plain text format for all to see to that’s not a desirable feature but may be that I need to set something to get the script to compile into the OBJ-c code in the executable or something but I haven’t approached that issue yet so research may solve that for me.

So although I am sure I will be coming back to the forum to learn more from you folks as other challenges present themselves, just wanted you to know that I would not have even bothered to move forward with this app until I saw a way to do it with the new ASOC which is awesome, and I discovered this website with the tutorials that Craig and Shane provide, as well as the advice I have received. About 30 hours (in 3 weeks) later and I can’t believe what was accomplished. Thanks to you all who post to MacSripter.net for making this possible.

Ok, as promised, here is a code sample that covers the major concepts in this thread that has made working with dates, and converting them back and forth to AS values as required, really quite easy in ASOC, thanks to Craig and Shane’s help above.

In the sample code all that is needed in IB is for a Date Picker(NSDatepicker) control to be placed in the window and connected (control-drag) to the app delegate class, selecting the DoWithTheDate_(sender) as the IB Action. Also connect FROM app delegate back to the datepicker selecting as the IB Outlet" datePicker " You’re done, then build and run. I suggest you follow along in the code as the dialog’s present the variations on the date you selected to, prevent “death by dialog”, or you can replace the Dislay Dialog command with log, and view the results in the Run Menu> Console.
I have also commented the code where appropriate. Note that there are no date comparision method, but they should be easy to cobble together with the key code from the above posts as well as from the code snippets in this sample.

Once again, thanks all for the help on this thread.

All the best
Jim

script TestAppDelegate
	
	property parent : class "NSObject"
	
	property datePicker : missing value
	property theDate : ""
	property theCal : ""
	property theComponents : ""
	property dateFormatter : ""
	property theString : ""
	
	on DoWithTheDate_(sender)
		
		--PART 1 - Changing the seconds in a date instance to zero
		
		-- set theDate property after user makes selection from the picker
		set theDate to my datePicker's dateValue()
		
		--set theCal var to a new(empty) instance of the calendar
		set theCal to current application's class "NSCalendar"'s currentCalendar()
		
		-- unpack the components of theDate to a variable
		set theComponents to theCal's components_fromDate_(254, theDate)
		
		-- Don't bother with seconds so set them to :00 (top of the clock)
		-- this will allow the user to pick a time to the minute
		-- for a task and prevent the task from firing at anytime 
		-- but on the top of the minute
		theComponents's setSecond_(0)
		
		-- reset theDate variable with the with new seconds we set above
		-- by rolling the components back up into a new NSDate
		set my theDate to theCal's dateFromComponents_(theComponents)
		
		-- Finally adjust the datePicker to the new value
		-- you should adjust the datepicker's attributes in IB to not show the seconds
		my datePicker's setDateValue_(theDate) --
		
		-- this dialog shows the resut from above with the seconds set to :00
		display dialog ("Part one - Result of changing seconds to :00  - Date is shown in International format   
		" & my datePicker's dateValue()'s |description| as string)
		-- END OF PART 1
		
		--PART 2 - Converting the NSDate to an AS  date and working with it
		
		-- Set new variables with values unpacked from theComponents 
		-- variable we worked with above and use them
		-- to make a new AS date
		set theYear to theComponents's |year|()
		set theMonth to theComponents's |month|()
		set theDay to theComponents's |day|()
		set theHour to theComponents's hour()
		set theMinute to theComponents's minute()
		set theSecond to theComponents's |second|()
		
		-- instantiate a new AS date to work with
		set theDate to current date
		display dialog ("PART 2 - Dialog 1 - This is the new AS date instance before revision 
		" & theDate as string)
		
		display dialog ("Part 2 - Dialog 2 - This is the AS Date's time string " & time string of (theDate))
		
		-- now revise theDate with our variables from above
		set day of theDate to 1 -- important
		set seconds of theDate to theSecond
		set year of theDate to theYear
		set month of theDate to theMonth
		set day of theDate to theDay
		set hours of theDate to theHour
		set minutes of theDate to theMinute
		
		display dialog ("PART 2 - Dialog 3 - This is the new AS date with Datepickers components and seconds revised  from above " & theDate as string)
		display dialog (("PART 2 - Dialog 4 - This is the value for the seconds from midnight for processing in AS if desired " & theDate's time) as string)
		--END OF PART 2
		
		-- PART 3 - Using a formatter to get a string value directly from an NSDate
		
		-- first set up a new variable
		set theString to theDate
		
		-- put a fresh NSDate instance from the datePicker into theDate replacing the AS date from PART 2
		set theDate to my datePicker's dateValue()
		
		-- Instantiate a dateFormatter. Note there is no formatter required in IB in the datepicker
		-- as we are creating it in the code
		set dateFormatter to current application's class "NSDateFormatter"'s alloc()'s init()
		
		-- set the dateformatter's attributes. Play with them to change the
		-- values in the dialog.
		dateFormatter's setDateStyle_(3) -- 0 = none, 1 = short, 2 = med, 3 = long, 4 = full
		dateFormatter's setTimeStyle_(4) -- 0 = none, 1 = short, 2 = med, 3 = long, 4 = full
		
		-- use the dateFormatter's get method to get a string from theDate
		set theString to dateFormatter's stringFromDate_(theDate)
		display dialog ("PART 3 - a nicely formatted date string for presentation to the user -
		" & theString as string)
	end DoWithTheDate_
	
	
	on applicationWillFinishLaunching_(aNotification)
		-- set the datePicker to now so the user doesn't see a strange date in the picker
		set theDate to current application's class "NSDate"'s |date|()
		--initialize the date property we will be working with
		my datePicker's setDateValue_(theDate)
		-- Insert code here to initialize your application before any files are opened 
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		-- Insert code here to do any housekeeping before your application quits 
		return true
	end applicationShouldTerminate_
	
end script

northbridge -

Thank you for your sample code! By combining your code with other bits, it was really useful to my project. Since it wasn’t super obvious to me at first, I’d like to contribute a handler that converts a UTC date to an NSDate object. I really want to “give back” to this great forum - so hopefully it helps someone out. Thanks again.


to convertUtcToAsDate(x)
		tell x to set {y, m, d, h, mn, s} to {text 1 thru 4, text 6 thru 7, text 9 thru 10, text 12 thru 13, text 15 thru 16, text 18 thru 19}
		set theCal to current application's class "NSCalendar"'s currentCalendar()
		set theComponents to theCal's components_fromDate_(254, theDate)
		theComponents's setYear_(y as integer)
		theComponents's setMonth_(m as integer)
		theComponents's setDay_(d as integer)
		theComponents's setHour_(h as integer)
		theComponents's setMinute_(mn as integer)
		theComponents's setSecond_(s as integer)
		set theDate to theCal's dateFromComponents_(theComponents)
		return theDate
end convertUtcToAsDate