Two questions regarding date handling

Hello Nigel.

I can see that as clean approach; I just took what I got and by the help of Shane Stanley I got that right, the idea behind the setDate() was to not touch the time, of the passed object since it i might use it to calculate the same time 3 days from now.

I proposed a bug when saying use [b]setDate/b to set the initial date (removed) since the current time of the current date would have been retained. :frowning:

I have written accompanying setTimeOfTheDay() and a setDateAndTime() handlers.

The two handler [b]setDate/b and [b]setTime/b still work as the previous one, as I will avoid creating new date objects that rather weird way of calling current date. Besides that; I can mutate time objects in place.

They are however not overly flexible, since I would have to make a copy of a date object I want to keep.
But: I can retain date and retain time whichever is the one I need. :slight_smile:

setDateAndTime() Is another story since this sets all the parameters, so here I return a brand new date
based on current date to return a new date object.

I used the setDateAndTime() in the function xlTimeValueOfNow() in the post above to remove the proposed bug.


tell application "Quicksilver" to show large type "Thank You!"


on setTimeOfTheDay(int23Hour, int59Minuts, int59Seconds, dateObject)
	set hours of dateObject to int23Hour
	set minutes of dateObject to int59Minuts
	set seconds of dateObject to int59Seconds
	return dateObject
end setTime

on setDateAndTime(intYear, intMonth, intDay, int23Hour, int59Minutes, int59Seconds)
	local dateObject
	set dateObject to (current date)
	set day of dateObject to 1 -- thanks to Shane Stanley
	set year of dateObject to intYear
	set month of dateObject to intMonth
	set day of dateObject to intDay
	set hours of dateObject to int23Hour
	set minutes of dateObject to int59Minutes
	set seconds of dateObject to int59Seconds
	return dateObject
end setDateAndTime

Hi, McUsr.

Given that doing integer maths is faster than setting a date’s properties, a quicker (and shorter) version of setTimeOfTheDay() would be:

on setTimeOfTheDay(int23Hour, int59Minuts, int59Seconds, dateObject)
	set time of dateObject to int23Hour * hours + int59Minuts * minutes + int59Seconds
	return dateObject
end setTimeOfTheDay

The ‘return’ line is obviously just a formality, since the calling process already has the date object.

This terse version of setDateAndTime() also appears to be slightly faster:

on setDateAndTime(intYear, intMonth, intDay, int23Hour, int59Minutes, int59Seconds)
	tell (current date)
		set {day, year, its month, day, time} to {1, intYear, intMonth, intDay, int23Hour * hours + int59Minutes * minutes + int59Seconds}
		return it
	end tell
end setDateAndTime

Note that this method doesn’t work in Snow Leopard with dates prior to October (I think) 1582, owing to to the fact that Apple’s tried to be clever switching to the Julian calendar before then.

Hello

Thanks for your help.

The first version ” should have thought of that! :frowning:

I understand that the second version is shorter to type, and I think I finally realized why it is executing faster:

I believe:
It addresses the result variables indirectly, after having set up the respective addresses initially. Then it just traverses the list and puts each result into correct location by indirect addressing. Hard to explain what I mean:
I prearranges the addresses of the partial results into an array or memory block (at this level). then sets up the result register to point at the address the start address of the memory block contains. That is the stack frame I believe.
Then it just creates a new stack frame which points to the desired end result, while executing a list of function calls, or whatever containing the results.

It must be something like saving the full setup of a stack frame with fetching of addresses to store results in and the like, which makes this faster. It is altogether a whole lot shorter to type, which is always a good thing, as long as it doesn’t blur the intentions.

It is a weird construction, you shouldn’t by chance know somewhere it is documented? :slight_smile:

I really got to get some addresses to the old “inside Macintosh” :slight_smile:

My guess ” without setting it all up again to test it ” is that the speed improvement is also due to single setting of the ‘time’ rather than the individual ‘hours’, ‘minutes’, and ‘seconds’ settings. The lists in the setting-by-list construction usually carry a slight speed handicap.

I don’t, actually. :confused: It’s been around since the early days of AppleScript. It’s normally used to set a whole bunch of variables at once, but here I’ve used it on the properties of a date. The values are assigned in order from left to right and the date adjusts itself after each one. All the values in the right-hand list are obtained before any of them are assigned to the destinations in the left-hand list, which can lead to delightful code like this:

tell (current date)
	set {day, day} to {32, day}
	return it --> One calendar month from this instant.
end tell

-- ie.
-- Tell the date-object returned by the 'current date' function
--   get 32 and the current value of its day
--   set its day to the 32 (overflows it into the following month)
--   and then to the original day number (gives this day next month ”
--   unless of course this day doesn't exist in the following month,
--   in which case it'll be some time in the month after that!)
--   Return the date object in its modified state.
-- end tell

You can also use lists to extract multiple property values from things which have properties:

set {y, m, d, t} to (current date)'s {year, month, day, time}

Or there’s an analogous method with records:

set {year:y, month:m, day:d, time:t} to (current date)
return {y, m, d, t}

By “this method”, I mean the whole idea of changing the current date to another, not just the “list vs. individual lines” detail.

Hello

The constructs are really beautiful, one could really create some heavy stuff with those I have also read that you can add a list to its own tail. The list is a vector but can simulate a nested structure like the one Prolog uses. It is quite possible to really write advanced stuff with AppleScript. (I don’t have to say that this is my favorite language :slight_smile: The hot headed sexy clever versatile Brazilian Woman amongst the programming languages! :smiley: )

I have pondered this a little bit more about speed gains using the set { x,y,z} to {a,b,c} construct and I think that if there is a speed improvement in this, then there is something similar to the reference trick; that AppleScript bypasses some memory address validation in combination with indirect addressing.

Have a nice Sunday and thanks for the information. I finally have universal date routines that works - fast!

Nigel’s “One calendar month from this instant.” surely is delightful code.

Is there a similar delightful code for " “One calendar month previous to this instant.”

Val

Hello Val

Something like this? But no niceties.


tell (current date)
	set its month to (its month as integer) - 1
	return it --> One calendar month before this instant.
end tell

And then we had constructs for previous month and next mont, the similar for years are obvious, as long as we operate before the calendar reform of october 1582.

not that easy, set your system date to some date in january and run the script. :wink:

Hello.

I believed it to be that simple. Well, here is a correction. I don’t tamper with the system date, but I guess this should work.
Thank you for your correction Stefan

@Val the “rollover” trick only works forwardly for months, within a certain range, I wonder if it was 18 or 19 months or so.


tell (current date)
	if its month is not January then
		set its month to (its month as integer) - 1
	else
		set its month to December
		set its year to (its year as integer) - 1
	end if
	return it --> One calendar month before this instant.
end tell

tell (current date)
	set {year:year, month:its month} to it - (its day) * days
	return it
end tell

. with the same proviso about non-existent days.

Wow! :lol:

I fiddled with your last example, and by doing that I figured out how the date functions.

Every property is a separate of seconds or can be viewed as seconds.
Every larger property contains the next lower property.

The year contains the seconds of the month: every brand new second is added to every property of the date object (the year contains the date value in seconds effectively).

They are however separate entities, -they share no common seconds.
Thats why a month will be the next month when adding a non existent number of days, and previous when telling it that it contains nil days. And if it were the first month, it will rollover and become the last month, when operating with seconds.

And your brilliant example worked and will always work because you subtract from the year first. Very clever indeed.

And thanks for the insights.

I love these threads. :lol: Nigel was on a roll…

How would this be done with Nigel’s code ?

I think (maybe) this works a la McUsr’s code at post 21.

But Is this jumping unnecessary hoops when it probably can be done in one elegant line ?

set Priormonthsa to text returned of (display dialog "How many months prior to this date do want to investigate ? - Please Insert Number" with title "How far back do you want to go?" default answer 12) as number

if Priormonthsa > 12 then
	set n to Priormonthsa div 12
	set m to n
	set Priormonths to Priormonthsa mod 12
else
	set Priormonths to Priormonthsa
	set m to 0
	set n to 1
end if


tell (current date)
	if Priormonths is not greater than ((its month as integer) - 1) then
		set its month to (its month as integer) - Priormonths
		set its year to (its year as integer) - m
	else
		set its month to (its month as integer) + (12 - Priormonths)
		set its year to (its year as integer) - (Priormonthsa / 12 as integer)
	end if
	return it
end tell

Val

Hi, McUsr.

I was told many years ago that a date (in AppleScript) was related to a record. In fact another term for “date object” is “date record”. However, I also understand a date to be a very large integer representing seconds from 1st January 1904 00:00:00, with negative numbers representing dates and times before that. I imagine that the various “properties” are actually functions which perform the necessary maths to extract the relevent figures or to fold them into the integer value.

A more self-explanatory rendition of this would be:

set aDate to (current date)
set lastDayOfPreviousMonth to aDate - (aDate's day) * days
set aDate's year to lastDayOfPreviousMonth's year
set aDate's month to lastDayOfPreviousMonth's month
return aDate

The idea was to modify and return the ‘current date’ date object, but of course you could just as easily return another:

tell (current date) to return it - ((it - (its day) * days)'s day) * days

Maybe this ?

One line is a step way too far for me !

set n to text returned of (display dialog "How many months prior to this date do want to investigate ? - Please Insert Number" with title "How far back do you want to go?" default answer 12) as number
set aDate to (current date)

repeat n times
	set lastDayOfPreviousMonth to aDate - (aDate's day) * days
	set aDate's year to lastDayOfPreviousMonth's year
	set aDate's month to lastDayOfPreviousMonth's month
end repeat
return aDate

Many thanks.

Val

set n to text returned of (display dialog "How many months prior to this date do want to investigate ? - Please Insert Number" with title "How far back do you want to go?" default answer 12) as number


tell (current date) to return it - (((it - (its day) * days)'s day) * days) * (n+1)

Nearly but Not quite there.

Val

Sorry to keep you waiting, Val. I’ve long had a handler for adding or subtracting calendar months, but it’s broken in Snow Leopard. Here’s a fixed version which should work on any system (so far!).

on addMonths onto oldDate by m
	copy oldDate to newDate
	
	-- Convert the "months" parameter to years and months
	set {y, m} to {m div 12, m mod 12}
	-- If the month offset is negative (-1 to -11), make it positive from a year previously
	if (m < 0) then set {y, m} to {y - 1, m + 12}
	-- Add the year offset into the new date's year
	set newDate's year to (newDate's year) + y
	-- Add the odd months (at 32 days per month) and set the day
	-- if (m is not 0) then tell newDate to set {day, day} to {32 * m, day} -- Broken in SL.
	-- Fix:
	repeat m times
		set newDate's day to 32
	end repeat
	set newDate's day to oldDate's day
	-- End of fix.
	
	-- If the day's now wrong, it doesn't exist in the target month
	-- Subtract the overflow into the following month to return to the last day of the target month instead.
	if (newDate's day is not oldDate's day) then set newDate to newDate - (newDate's day) * days
	
	return newDate
end addMonths

-- Add 40 years (480 calendar months)
addMonths onto (current date) by 480

-- Subtract 18 months
addMonths onto (current date) by -18

A gem!

Many thanks,

Val

My original addMonths handler used day overflow because it was written before it was possible to set months by number. Here’s a more modern version. I don’t know if it works in Panther or not. Certainy not before.

on addMonths onto oldDate by m
	copy oldDate to newDate
	set d to oldDate's day
	
	-- Get the new month number relative to this year.
	set m to (oldDate's month) + m
	-- Derive the actual year and month number.
	set {y, m} to {(oldDate's year) + m div 12, m mod 12}
	-- If the month number is less than 1, get the complementary positive number in the previous year.
	if (m < 1) then set {y, m} to {y - 1, m + 12}
	-- Set the required values in the new date.
	tell newDate to set {its day, its year, its month, its day} to {1, y, m, d}
	
	-- If the day's now wrong, it doesn't exist in the target month
	-- Subtract the overflow into the following month to return to the last day of the target month instead.
	if (newDate's day is not d) then set newDate to newDate - (newDate's day) * days
	
	return newDate
end addMonths

-- Add 40 years (480 calendar months)
addMonths onto (current date) by 480

-- Subtract 18 months
addMonths onto (current date) by -18

Hello

I’m positively sure that months as number was a feature added in Tiger