' range of valid Microsoft Access dates. If YYYY is omitted, ' then YYYY is assumed to be the year of the current system ' date. ' ' RETURNS: A date for a valid Microsoft Access year and Julian day, ' or a Null value for an invalid Julian Day.
This article is heavily dependent on contributions by Nigel Garvey & Kai Edwards Scripters often need to identify, predict, or calculate dates in their scripts - for publishing, accounting, labeling files and archives, or finding special dates. This is often just a minor part of what the script actually does, but achieving the result can become a production number in its own right. For instance, AppleScript doesn't (at the time of writing) provide numerical equivalents for months, so it seems at first that the only way to handle them is to use a look-up list, a repeat loop, and lots of 'if-thens'. However, it's usually possible - with a little mathematical forethought and the exploitation of some of the peculiarities of date variables and AppleScript coercion - to get the required results by much shorter means. These are solutions to a few problems that have cropped up in various AppleScript fora over the past few years.
Possibly no one else in the world will have exactly the same requirements, but hopefully the examples explained in this article will give some idea of the sort of thing that can be done and with minor modifications can be adapted to the reader's task. Most of the scripts here are 'plain vanilla' AppleScript and do not rely on Scripting Additions or other tools. Where possible, I have credited the authors of these ideas although they may appear here in slightly modified form. The Fundamentals of AppleScript's Dates & Time The AppleScript's Standard Additions Scripting Addition adds support for a 'date' class to AppleScript and date-like strings of text can be coerced to dates by preceding them with the word 'date' or following them with 'as date'.
In addition, AppleScript will return the current date and time with a simple instruction: 'current date' that often needs parentheses around it. Applescript: time of date 'Saturday, April 15, 2006 4:29:04 PM' - 59344 - This is the number of seconds since midnight rather than 4:29:04 PM. But ask for 'time string': time string of (current date) - '4:29:04 PM' - the time without the date - Similarly, we can get the 'date string', without the time appended to it.
Date string of (current date) - 'Saturday, April 15, 2006' - but note that in both cases, the result is btext/b, not a bdate/b AppleScript is rather flexible about what it will coerce to a date as well, but the results depend on your settings in International System Preferences. All of the numerical forms must follow the International preferences set for the user. The numerical examples below work if you have selected 'United States' in the System Preferences/International/Formats pane and have not customized them or chosen a calendar other than 'Gregorian'. In the United Kingdom and much of Europe, the day of the month should precede the month if the month is given numerically, again presuming that the reader has not customized the order, and of course, the name of the month must be in the appropriate language. When the name of the month is given, the day-month order is arbitrary but the year must be last and a period is not required but may be used if the month is abbreviated.
AppleScript's Gregorian date range is from midnight of January 1, 1000 to the second before midnight (23:59:59) on December 31, 9999. Applescript: date '12/25/04' date '12-25-04' date '12 25 04' date 'dec 25 04' date '25 DEC 2004' - with or without commas - All compile to: date 'Saturday, December 25, 2004 12:00:00 AM' Manipulating Dates and Times As we saw above, pure vanilla AppleScript returns the month of a date as an AppleScript key word like April. Often we want the month as a number. Fortunately, AppleScript will coerce a month name (not in quotes, but as an AppleScript word) to a number if it is multiplied by 1 (or any other number) in any order. Applescript: - If you set the day of any AppleScript date to 32, the date overflows into the - following month. You can then set the day again to whichever day you want in - that month. This is the insight that got me going in AppleScript, so it's my - particular favourite.
On firstOfNextMonth from theDate - returns a date copy theDate to d set d's day to 32 set d's day to 1 d - return the resulting date end firstOfNextMonth firstOfNextMonth from (current date) set wd1 to weekday of (firstOfNextMonth from (current date)) - Thursday - and for some other day added by Bell: set wd12 to weekday of (twelveNextMonth from (current date)) - Monday on twelveNextMonth from aDate copy aDate to d set d's day to 32 set d's day to 12 d end twelveNextMonth - First or Third Wednesday of the Month. Applescript: - This is another application of the date-overflow method of my '1st of next - month' handler. Overflow to a date in the following month by setting its day 32.
The day of the resulting date is the amount of the overflow. Subtract this from - 32 for the length of the current month.
On daysInMonth for theDate - returns an integer copy theDate to d set d's day to 32 32 - (d's day) end daysInMonth daysInMonth for (date 'Friday, February 22, 2008 12:00:00 AM') - 29 - Alternatively, here's a one-liner that uses arithmetic instead of overflow: on daysInMonth2 for theDate 32 - ((theDate + (32 - (theDate's day)). days)'s day) end daysInMonth2 As a final offering from Mr. Garvey's set of DateTips, this script calculates the date of a given day of the week a number of weeks before or after a given starting date.
I used it in a script for calculating the dates and doses of a medication that was to be tapered off over a number of weeks. I've given it Nigel's name for it here, but I used 'TGIF' - Thank God It's Friday. Date Hence (or Ere Now) of an Instance of a Given Weekday. Applescript: - This was written at the suggestion of, and in collaboration with, Arthur Knapp. Its purpose is similar to that of 'Next Thursday's date', but it's far more - flexible.
It returns the date of.any. instance of.any. weekday before or after - the input date - eg.
The 2nd Saturday after the date. Its parameters are the - reference date, the weekday required, and the number of the instance. Positive - instance parameters refer to the future; negatives to the past. An instance - parameter of 0 returns the given weekday that occurs in the same Sunday-to- - Saturday week as the reference date. More fun with Dates & Times in AppleScript.
Here's a few more tricks with dates that I find useful and thought would be good additions to this thread. Pretty much everything here has been uncovered by trial and error. I own a number of books on AppleScript, but have never come across anything that covers these topics. If someone has somewhere else, I don't mean to discredit them, but to the best of my knowledge I worked this all out on my own. Though I'm sure pretty much anyone else here could have done the same if they wanted to.
UPDATE: Post edited slightly to include some of Nigel Garvey's excellent feedback & information below. Also, as he mentioned, be aware that these scripts will all compile if you have similar (US-format) Time & Date localization settings. The hard-coded dates I've used will be misinterpreted and/or fail if a non-US date format is your preference. But of course, all dates and scripts can be easily modified or adapted to any date & time format. Simple method for including a hardcoded string in your code. We all know that if you compile the following.
Applescript: date string of date ( '8/14/2008' as string) as number - AppleScript Error! There's a variety of ways to explain why this would be useful. The easiest way for me to explain comes from my use of FileMaker Pro (which I like to use a lot). It has both date & time fields as well as a timestamp field (which is practically identical to AppleScript's date value). The key notion being that a date is really just an integer that indicates the number of days elapsed from '1/1/0001', and time is just an integer indicating the number of seconds elapsed since '12:00:00 AM'.
And the timestamp is nothing more than the combination of a date & time value. AppleScript's date values are basically identical to a FileMaker timestamp in that regard. Because it combines both date & time information. Fortunately in FileMaker it is easy to extract and coerce dates & times into integers and combine them into timestamps.
As we see here, AppleScript allows you to easily extract the time value, but doesn't let you extract the date alone. There are many good reasons for wanting to do this in AppleScript, and there is a way. We'll get to it soon. Time differences As I mentioned, AppleScript seems to store dates in a similar manner to FileMaker's timestamp: the number of seconds elapsed from a starting date. This can be demonstrated by running the following script. Applescript: ( date ( '1/1/2001' as string) - date ( '1/1/2000' as string) ) div days - Returns: 366 (days) 4.
Coercing a date to a number (the brute force approach) So we just found it's possible to determine the difference (in seconds or days) between any two dates. That's real progress! Unfortunately, there just doesn't seem to be a direct way to determine the exact numerical value for a given date.
At least I can't find a way. All of the following look like good coercions that should work, but all fail and result in an AppleScript error. Applescript: return (current date) as integer - AppleScript Error! Return (current date) as number - AppleScript Error! Return (current date).
1 - AppleScript Error! So, how can we determine the exact number assigned to a particular date? Well, my approach is a bit of a kludge, but it works for me. I prefer to assume that AppleScript uses the exact same formula as FileMaker so I'm going to create a handler that makes it the same. As I mentioned, FileMaker considers a one (1) to be the date '1/1/0001'.
The lowest date that I can type in Script Editor (more on this next) is '1/1/1000'. And FileMaker represents that date by the number 364878. So given those two facts, we can forge a handler for coercing dates to numbers. Applescript: date ( ' as string) - Returns: date 'Friday, December 31, 9999 12:00:00 AM' date ( ' as string) + 1. days - Returns: date 'Saturday, January 1, 0000 12:00:00 AM' date ( ' as string) + 366. days - Returns: date 'Sunday, December 31, 0000 12:00:00 AM' date ( ' as string) + 367. days - Returns: date 'Monday, January 1, 0001 12:00:00 AM' Notice how the weekdays increment correctly (without weekday discontinuity).
The last day of year 9999 is a Friday and the first day of year 0000 is a Saturday, and so forth. Therefore, year '0000' could also represent year '10000' and all the weekedays should be correct for Gregorian time. This could be extended so that years 0001 thru 9999 could represent years 10001 thru 19999 respectively, and on and on. Though, I can't imagine why anyone would ever need that for normal use, but it does point out an interesting property of the proleptic Gregorian calendar. Coercing numbers to dates Coercing numbers to dates is just as problematic as the reverse.
None of the following works. Applescript: on coerceNumToTime(theNum) set x to date ( '0' as string) set hours of x to theNum div hours set minutes of x to theNum mod hours div minutes set seconds of x to theNum mod hours mod minutes return time string of x end coerceNumToTime return coerceNumToTime(12345) - Returns: '3:25:45 AM' That works, but notice the math to extract the hours, minutes and seconds. We can take advantage of 'seconds overflow' when assigning times that will allow us to write this handler a little more concisely. You see, we can assign a value up to 32767 (or 2^15 - 1) to the seconds property, and AppleScript willl overflow the time into the minutes & hours.
That's about 9 hours worth of seconds, so it's not enough to allow us to eliminate the hours assignment, but we can eliminate the minutes assignment. So we'll end up with the following equivalent handler. Applescript: on setOldDate(theMonth, theDay, theYear) set t to date ((theMonth as string) & '/' & theDay & '/' & theYear) set t's year to theYear return t end setOldDate return setOldDate(1, 2, 34) - Returns: date 'Monday, January 2, 0034 12:00:00 AM' Who knows, that could be helpful if you're working on some sort of ancient AppleScript? NOTE: It should be mentioned again that the Gregorian calendar took effect on approximately October 15th, 1582. So any date calculated prior to that will not correspond to actual historical dates since most countries were using some interpretation of the Julian calendar before that time. For that reason, historians and astronomers don't actually use the Gregorian Calendar for tracking dates. Anyway, one step better, the following handler accepts a short date string (forward slash delimited) as the parameter.
Applescript: on getDatesMonthNum(theDate) return ((year of theDate) - 1). 12 + (month of theDate) end getDatesMonthNum return getDatesMonthNum ( date ( '8/14/2008' as string )) - Returns: 24092 (months) Both the getDatesWeekNum & getDatesMonthNum handlers can come in handy by finding the differences between their results when operating on two different dates (often the current date and some milestone in the past). That can be very useful in determining whether a specific weekly or monthly task has been performed. It can also be used to group data into weeks or months for the purposes of data processing or reporting functions. So there you have it. That's my contribution to the Time & Date discussion. It was a lot of fun uncovering and gathering this information together, and thanks definitely go to Adam Bell and the MacScripter site for hosting the wealth of information in this unScripted forum.
I know I've used it on many occasions. Hopefully this will help someone else with a future project someday. Don Aehl All these scripts were written and tested to work on the following platform: International Format Region: United States (default) 'MM/DD/YYYY HH:MM:SS 12HR' Script Editor: 2.1.1 (81) AppleScript: 1.10.7 Operating System: Mac OS X (10.4.11) Model: iMac G5 (PowerPC) Last edited by daehl (2008-08-15 09:29:24 pm). Applescript: date '1/1/2000' - Returns: date 'Saturday, January 1, 2000 12:00:00 AM' - But, somewhat annoyingly, your code also changes to that as well.
More accurately, date '1/1/2000' compiles on your machine (and on mine) to an AppleScript date object representing midnight on 1st January 2000. This compiled object is presented to your view in Script Editor as date 'Saturday, January 1, 2000 12:00:00 AM', because that's the way dates are configured in your Date and Time preferences.
On my machine, I see date 'Saturday 1 January 2000 00:00:00'. But if I sent my compiled script to you, you'd see the date in your own format, not mine, when you looked at it in Script Editor.
The point is that, once compiled, the date is usable on anyone's machine. Applescript: date ('1/1/2000' as string) - You get the same result, but you code doesn't change. Here the date isn't realised until the script's run (adding slightly to the running time). It's interpreted according to the preferences on the running machine, so the above works for you and me, but would error on a machine configured for yyyy/mm/dd short dates. Similarly, if you'd written date ('1/2/2000' as string), your machine would produce date 'Sunday, January 2, 2000 12:00:00 AM' when it ran the script, mine would produce date 'Tuesday 1 February 2000 00:00:00'. Hard-coded date strings aren't transportable.
There is, of course, a problem when posting to a forum like this, since you can only post the source code, not the compiled script. However you write a date, it's bound not to work for someone, unless they edit it for themselves. Before Leopard, it was possible to use this unofficial coercion. Applescript: date ('1/2/34' as string) - Returns: date 'Monday, January 2, 2034 12:00:00 AM' AppleScript (it's not Script Editor) doesn't mind if years less than 1000 are entered, but it interprets them as shorthand for nearby four-digit years. When I first started AppleScripting in 1997, two digits were taken to mean years beginning with '19' and three digits as years beginning with '1'. Shortly after that, the interpretation depended on the system date at the time of the interpretation.
(I'm not sure if this was introduced in time for the end of the century or whether AppleScript could do this already.) Currently, both my Jaguar and Tiger machines interpret year numbers between '91' and '99' in date strings as years between 1991 and 1999, and any other two-figure year numbers as years between 2000 and 2090. Thanks, Nigel! Very good points, and well worth mentioning. I see what you mean about hard-coded dates and localization preferences. Both are important considerations that I hadn't previously needed to reckon with.
And the use of days as a constant is something I overlooked and will definitely use from now on. In fact, I'm going to edit my post above to fix that. However, regarding AppleScript's Time & Date limits.
It seems clear that the true limits are: 1/1/0000 12:00:00 AM thru 11:59:59 PM (using a 12HR US Time format) I have no idea why, but it seems to be just an arbitrary limitation to prevent years earlier than 1000 being typed into a script. My guess is it's a negative side-effect of their particular implementation of support for 2-digit year date entry. But the underlying Time & Date data structure fully supports the complete range of 4-digit years. That, along with the inexplicable lack of a numeric coersion for dates is a bit odd, but it's fun to find the workarounds. Last edited by daehl (2008-08-15 10:05:52 pm). No year 0 in the Gregorian calendar.
Well, technically there's also no year 1 or any years before 1582 in the Gregorian calendar (when it started use). That's why the Proleptic Gregorian Calendar was invented (otherwise known as international standard ISO 8601), which does have a year zero, and it's a leap year But I think you're right in that there's no year '0' in AppleScript.
Originally, I thought that the year 'wrapped-around' from 9999 to 0000, but now I see that year '1/1/0000' is actually equivalent to '1/1/10000' So the range of dates that AppleScript can be made to display is more like 1/1/0001 12:00:00 AM BC thru 1/1/0001 12:00:00 AM AD thru 11:59:59 PM thru 11:59:59 PM (using a 12HR US Time format) But, clearly AppleScript (under the hood) is capable of storing dates well beyond those limits. Based on my tests, it allows advancement of up to 9.07E+18 seconds from 1/1/0001 and subtraction of up to -9.02E+18 seconds from it as well. That would yield a data range spanning somewhere around 584 Billion Years! Of course, AppleScript can only store about 15 significant digits in a number, so that limits the fully accessible date range to around 63 Million Years! (wherein every second can be specifically addressed) But, in practice the limit is even lower, because AppleScript will only let you set the year property of a date to as low as 0 (1 BC, though it reports that as year 0001) and to a maximum of year 32,767 (or 2^15 - 1).
And finally, it's possible to demonstrate a failure in AppleScript's date formula above year 29,940. Hi there, I'm searching for a script that will repeat an event in iCal on the first weekday of every month. I need a reminder on the first weekday (ie Monday to Friday, excluding Saturday and Sunday) of every month to go online to make some payments. On some months, the first of the month may fall on a Saturday or Sunday - say 1 Jul - in which case I want iCal to move the event to the next weekday which will be Monday, ie 2 or 3 Jul automatically when I set up the event in iCal.
I can do this quite easily in Entourage but somehow it doesn't sync over well in iCal and subsequently my iPhone. I would like to set up the event inside iCal and get it to repeat the way I want it to through a script. Appreciate if anyone who knows it could help me out.
Thank you so much, Nigel, for this simple yet effective script. It worked nicely and got me my first event in August right where I wanted it. I'm just not sure how to make it repeat itself perpetually but it shouldn't be difficult figuring it out following your instructions. The hardest part is over. I actually have quite a few events that are similar in nature but on different dates, so this is just great for me!
I'm sorry about where I posted my request for help. Thanks for pointing it out to me. Would it be possible then to move this discussion to the OS X forum? I know in some other forums that it's possible for the administrator to move posts or threads. Thank you so much, Nigel, for this simple yet effective script. It worked nicely and got me my first event in August right where I wanted it.
I'm just not sure how to make it repeat itself perpetually but it shouldn't be difficult figuring it out following your instructions. The idea is that when date of the August event actually arrives, the alarm attached to the event automatically runs the script again (more precisely, an invisible helper application that's part of the iCal software, but which runs independently of iCal itself, reacts to the stored alarm data and runs the script at the required time) to create the September event, and so on. Sorry my explanation wasn't clear. Also, the script writes own its location into the alarm so that the alarm can find it next time. For this to work, the script has to be saved as an application in the location where you want to keep it before it's run for the first time. Do let me know if there are any problems, or if it's not quite what you need.
Last edited by Nigel Garvey (2009-07-06 02:16:17 am). Thanks again Nigel. I got it now. There are two more things I need to sort with this script: 1) I had preferred that the event gets populated throughout the calendar rather than just the first month. It worked very well according to the way you designed it. In fact, I quite like the way it automatically creates next month's event when the alarm goes off (it's quite cool actually) but I guess I'm used to seeing recurrent events actually appearing in the subsequent months when I create them. I'm thinking that in some other events where I will deploy this script, it is important that I see the event appear in subsequent months in case this recurrent event is a meeting or something like that and I need to be sure I don't schedule another appointment which clashes with the timing.
2) I sync-ed this over to my iPhone and as you probably already know, it doesn't quite work in iPhone, that is, iPhone doesn't run scripts. So the alarm came and went and that's about it. While I could get it back to the iPhone every time I sync it, I would prefer that it works in iPhone because I use my iPhone a lot more than iCal. So what I'm hoping for is a script that will sort of permanently create all these first weekday events inside iCal as normal recurrent events and then when I sync it over to iPhone, I'll get all these dates populated into iPhone's calendar as recurrent events. I can then manipulate them as normal recurrent events - like delete a single occurrence or the whole series.
So the script is a run-once kind of script. I'm sorry for this long posting but I am a little disappointed with Apple that such a function in iCal needs such a workaround. I used to use Outlook and Entourage and probably got pampered by these apps. This whole thread is VERY intriguing but, unfortunately, I am too limited in my AppleScript knowledge to fully grasp all the intricacies of the coercion of dates. Currently, I am trying to coerce a string from an astrology program ('Io Sprite') and add either a day, month or year to advance or regress by any of these intervals. I can get the LocalDate (app class) out as a string but adding or subtracting from it and and putting it back in the InfoCardWindow is eluding me. Any ideas or help would be greatly appreciated.
Applescript: -According to ISO 8601 the week 1 of a year is the one with 4 Jan in it: (urlon weekNumber from inputDate -First get the number of days since 4 Jan, then divide that by seven to get the number of full weeks passed. Finally round the number up to get the week currently in progress.
Round ((inputDate) - (date ( '4 Jan' & year of inputDate))) / days / 7 rounding up end weekNumber Last edited by solidfox (2010-02-28 05:40:35 am). Hi, solidfox. Thanks for posting your suggestion. However, there are a few issues with it. Firstly, the 'date ' construction only works where the user's Date and Time preferences are compatible with the format of the date string given. As from Snow Leopard, 'date ('4 Jan' & year of inputDate)' will error for users in the USA. Secondly, your calculation works directly from 4th January, not from the beginning of the week in which it occurs.
Thirdly, you need to make provision for when the input date comes before 4th January and for when it's one of the last three days of December and might be in the first week of the following year. Applescript: on weekNumber from inputDate against weekStart - Get a known date in the past with the week-start weekday. This was compiled in Snow Leopard, where AppleScript dates are Julian rather than Gregorian before some date in the 1500s. With Leopard or earlier, use date 'Monday 6 January 1000'. Set baseDate to (date 'Monday 1 January 1000 00:00:00') + (offset of (text 1 thru 2 of (weekStart as text)) in 'MoTuWeThFrSaSu') div 2. days - Get 4th January in the year of the input date. Copy inputDate to Jan4 set Jan4's day to 4 set Jan4's month to January - If the input date's in the last three days of December, initially try with 4th January the following year.
If (inputDate's month is December) and (inputDate's day 28) then set Jan4's year to (Jan4's year) + 1 - Derive the beginning of the week of the 4th January date. Set yearStart to Jan4 - (Jan4 - baseDate) mod weeks - If the input date comes before this, it's counted as being in the previous year. If (inputDate comes before yearStart) then set Jan4's year to (Jan4's year) - 1 set yearStart to Jan4 - (Jan4 - baseDate) mod weeks end if - Derive and return the relevant week number for the input date. Return (inputDate - yearStart + weeks) div weeks end weekNumber weekNumber from (current date) against Monday - or 'Monday' Last edited by Nigel Garvey (2010-02-28 11:21:30 am).