Suchen / SearchNavigation |
Essential AppleScript Calendar Handlers (v.2)14 Jan 2012 - 13:23 This set of AppleScript handlers provides tools for Holiday calculations and calendar conversions. Supported are the calendars Gregorian, Julian (both including Easter), Hebrew / Jewish and the ISO week calendar. A few general tools are found at the beginning. © Jürgen Schell 2009 – 2012 Documentation and Code provided unter Lesser GNU Public License. No guarantee for proper behaviour of these functions is given whatsoever, regard all code as training implementations. Essential calendar functions might be useful for some people. Several functions here have been inspired by the book "Calandrical Calculations" by Nachum Dershowitz and Edward M. Reingold, Cambridge 2008. Particularly the Easter algorithms are very close. Some parts use ideas from the source of the PHP calender extension. Still, this is a different implementation. The AppleScript code as HTML is on this code page. Jürgen Schell, Langenfeld, Germany, May 2009 - 2012 Some General Notes on the FunctionsIf an argument is an integer, the code does a type cast. If you provide a floating point value, the value will be rounded to an integer. Arguments of the type date are checked for their class. If an argument is of the wrong class or out of range, an error 1700 will be raised. The error message is more detailed. Actually, all errors I raise are 1700. If you need other numbers, search for the number and change it. Where a function name has the word "date" in it, the function requires an AppleScript date object as argument or delivers one, as appropriate. Where a function name has the word "cdn" in it, the function requires a chronological Julian day number as argument or delivers one, as appropriate. Both are just two different concepts to represent a day in some general way. A note on chronological Julian day numbers is at the end. When a function needs to create an AppleScript date object, it uses “current date” and changes the properties. The reason is simple: Only this way, the functions survive copy / paste as text. In a compiled script, an expression like “date "Saturday, 1 January 2000 00:00:00"” is perfectly stable, but it may break, if the code is copied as text and inserted into some script and the text does not fit the system settings for date formats. There are some strange thing about date display in AppleScript for dates sufficiently far in the past. Check note “AppleScript Dates in the Past” at the end. I tried to keep handlers as self contained as possible. That is: Simple code that is needed in several handlers is rather repeated than turned into a separate handler. Only if a complex code is used several times, I use a separate handler. That should make it simpler to copy / paste the handlers needed. Functions for the Hebrew calendar count months from Nisan as 1. This is consistent with most calendar calculations I have seen, but different from Mac OS or PHP. Both count from Tishri as 1. Changes in this VersionIn this second instance of the calendar function. This is an overview of changes compared to the first version:
(The documentation for the first version has been archived.) List of all Handler Names
List of Handler DescriptionsnewDate( Year, Month, Day ) Result: AS date object Year, Month and Day are integers, floats will be rounded. An error will occur if an argument is out of range. Creates a date by setting the properties. The time is set to midnight. Months and days may be any integer. Allowing any integer for month and day has some nice effects. E.g. to find the last day of month M in year Y, use newDate( Y, M + 1, 0 ). newDateTime(Year, Month, Day, Hour, Minute, Second) Result: AS date object Year, Month and Day, Hour, Minute, Second are integers, floats will be rounded. An error will occur if an argument is out of range. Date behaviour for Month and Day is like in newDate. Time elements are added to that date. Hence, 48 hours add two days to the date. DateToISOdayofweek ( date object ) Result: integer Returns the number of a day in the week, according to the ISO way of counting, Monday = 1, ..., Sunday = 7. DateToISOweekofyear ( date object ) Result: integer The function returns the number of the week the date belongs to, counted in the ISO fashion. (An ISO week has seven days always. January 1 of year XXXX may belong to the first week of XXXX, or to the last week of the preceeding year. The first week of the year is the first one, having 4 or more days in year XXXX.) preceding_day( day number, date object or cdn ) Result: date object or integer, depending on second argument. The function returns the date of the given weekday preceding the given date. day number can be an integer from 1 to 7. ISO convention is used, i.e. Monday is 1, Sunday is 7. AppleScript day names (Sunday, Monday...) may be used instead. date object or cdn is either an AppleScript date object or a chronological Julian day number. If a date object is used, the function returns a date object. If a number is used, the function returns a number. date_to_cdn ( date object ) Result: Integer Returns the chronological Julian day number of the date. greg_to_cdn( Year, Month, Day ) Result: integer Arguments are integers. The function returns the chronological Julian day number for a date in the Gregorian calender, given as year, month and day. Day and month values spill over, i.e. February 29 of a common year will yield the same result as March 1, March 0 returns the last of February in any year. Adding 30 to a month is two and a half years later. Since it is more plausible for computations, we allow for a year 0. That is: 0 stands for year 1 BCE, -1 for year 2 BCE and so on. (For the Gregorian calendar, that notation is consistent with ISO.) cdn_to_date( day_number ) Result: AS date object Argument is integer. If day_number is a float, it will be rounded. Returns a date object for the given chronological Julian day number. cdn_to_greg( day_number ) Result: Record day_number is an integer. Returns a record with the elements year, month and day. Year in the result record uses the ISO concept allowing for a year 0. That is: Year -99 in this numbering scheme is the same as Year 100 BCE in the conventional system. julian_to_cdn( Year_ad, Month, Day ) Result: Integer Arguments are integers The function returns the chronological Julian day number for a date in the Julian calender, given as year anno domini, month and day. Days and months may be any integer. E.g. Day 0 refers to the last day of the previous month. Adding 30 to a month is two and a half year later. Since it is more plausible for computations, we allow for a year 0. That is: 0 stands for year 1 BCE, -1 for year 2 BC and so on. This is not standard for the Julian calendar, but convenient. cdn_to_julian( day_number ) Result: Record {year, month, day} The argument is an integer. day_number is the requested chronological julian day number. Negative arguments raise an error. The result is a record with the components year, month and day, an integer each. Years BC are returned as 0 or negative values. Hence "0" means year 1 BC, "-1" means year 2 BC… easter_greg_date ( Year_ad ) Result: AS date object The argument Year is an integer Valid range: For a year, given as an integer, the function returns the date of easter in that year as an AppleScript date object. The code is heavily based on Dershowitz/Reingold. easter_greg_cdn( Year_ad ) Result: Integer Basically the same code as easter_greg_date, but the function does not return an AppleScript date object. Rather it works within the concept of Chronologial Julian day numbers. Argument is an integer. easter_jul_date ( Year_ad ) Result: AS date object Argument is an integer. Floating point numbers will be rounded. Returns an AppleScript date object for the orthodox Easter date of the given year anno domini. Properties of that date object belong to the Gregorian calendar, just as in any AS date object. easter_jul_cdn( Year_ad ) Result: Integer Argument is an integer. Floating point numbers will be rounded. Basically the same code as easter_jul_date, but the function does not return an AppleScript date object. Rather it works within the concept of Chronologial Julian day numbers. rosh_hashanah_cdn( Year_am ) Result: Integer The argument is an integer (year number in the Hebrew calendar). The function returns the chronological Julian day number of the day the year starts. (Note: Year AM = Year AD + 3760 for days from January 1 to Elul 29. Year AM = Year AD + 3761 for days from Tishri 1 to Decembre 31.) heb_to_cdn( Year_am, Month, Day ) Result: Integer all arguments are integers in the following valid ranges: Year_am >= 1 Year_am is the anno mundi year of the Hebrew date. Month is the month counting from Nisan as 1. Day is the day number within the month. Month and leap year behaviour: Month 12 is Adar in common years, Adar I in leap years. Month 13 is Adar in common years, Adar II in leap years. Spill over behaviour of Day: Day value - 1 is simply added to the beginning of the month. Examples: “heb_to_cdn(5772, 9, 32)” will yield 2nd of Tevet, “heb_to_cdn(5773, 9, 32)” will yield 3rd of Tevet (both end of Hanukkah). Giving month as 10 and day as 0 will return the last day of Kislev. cdn_to_heb( day number ) Result: Record of year, month and day Argument is integer. Returns a record with the elements year, month and day, representing the date in the Hebrew calendar. For months, Nisan is counted as 1, Adar Sheni in leap years is 13. cdn_to_iso_week_cal( cdn ) Result: Record of year, week, day and iso_string All arguments are integers. Returns a record with the elements year, week and day, representing the day in the ISO week calendar. iso_week_cal_to_cdn( year, week, day ) Result: Integer. Argument is integer. Returns the chronological Julian Day number for the date given als an ISO week calendar date. Days and weeks spill over. Hence, day 0 of week 1 of a year will return the last day of the previous week year. iso_week_cal_to_date(year, week, day) Result: AppleScript date object All arguments are integer, day may be an AS day constant. Returns an AppleScript date object for the day given as year, week and day in the ISO week calendar. date_to_iso_week_cal ( date object ) Result: Record of year, week, day and iso_string. Argument is an AS date object Returns a record with the elements year, week, day, representing the date in the ISO week calendar. iso_string is an ISO formatted string version of the date. is_leap_greg ( Year ) Result: boolean The argument is an integer, giving the year. The result is true if that year is a leap year, otherwise it is false. is_leap_jul ( Year ) Result: boolean The argument is an integer, giving the year. The result is true if that year is a leap year, otherwise it is false. is_leap_heb ( Year ) Result: boolean The argument is an integer >= 1, giving the year anno mundi. (Floats will be rounded.) The result is true if that year is a leap year, otherwise it is false. is_long_iso_week_year( year ) Result: boolean The argument is an integer, giving the year. The result is true if that year has 53 calendar weeks, according to ISO week counting. Otherwise it is false. day_exists_greg( Year, Month, Day ) Result: boolean Arguments are integers. The handler checks, if Month is in the legal range and if day exists in that month. Hence, “day_exists_greg(2012, 2, 29)” returns true but “day_exists_greg(2013, 2, 29)” returns false. day_exists_jul( Year, Month, Day ) Result: boolean Arguments are integers. The handler checks, if Month is in the legal range and if day exists in that month. Hence, “day_exists_jul(2100, 2, 29)” returns true but “day_exists_jul(2101, 2, 29)” returns false. (The only difference to “day_exists_greg” is in the leap year rule for centuries.) on day_exists_heb( Year_am, Month, Day ) Result: boolean Arguments are integers. Checks, if the given date exists in the Hebrew calendar. 12 means Adar in common years, Adar I in leap years. 13 means Adar II. Examples: “day_exists_heb(5774, 12, 30)” returns true. Leap year, so month 12 has 30 days. “day_exists_heb(5775, 12, 30)” returns false. No leap year, Month 12 has only 29 days. “day_exists_heb(5777, 9, 30)” returns false, Kislev is short in that year. day_exists_iso_week_cal( Year, Week, Day ) Result: boolean Arguments are integers. The handler checks, if Day and Week are in the legal range. If week is 53, it is checked if the year has 53 weeks. _______________________________ NotesSpill over Behaviour for Days and MonthsFunctions that take a year, month (or week in case of the ISO week calendar) and a day as arguments, day may be any integer. The semantics is: The beginning of the month (week) is calculated, and day - 1 is added. This allows for some nice tricks: The last day of any month can be specified by day 0 of the following month. This includes months with variable length like February. If for example some event spans 8 days and starts February 25, the end can be specified as February 32. If an invoice is created December 5th 2013 and should be payed within four weeks, that day could be specified as year 2013, month 12, day 5 + 4 * 7. Months in the Gregorian calendar and Julian calendar behave the same. Months outside the range 1 to 12 are first converted to a year difference and a month within that range. The year is adjusted. Finally the beginning of that month is calculated and the days are added. A subscription starting May 15th 2013 and is for 18 months, the end is year 2013, month 5 + 18, day 15. Weeks in the ISO week calendar behave like days: They are simply calculated as seven days. Due to the special interpretation of month 12 and 13 in the Hebrew calendar, this logic is not used in that case. Time values in the function newDateTime are first converted to seconds and added finally. Again, every integer value is possible. (Built in spill over in AppleScript is different. Months seem to behave OK up to 13. Less then 1 is not possible. Days spill over as expected in the range 1 - 127. For higher values, things get strange. It seems that the lowest 8 bits if the integer are intepreted in a two's complement fashion: 256 behaves like 0 and yields the last day of the previous month, 255 is -1 and yields two days before... I am not aware of any technical documentation specifying this, so be very careful when using this "feature". hour: Same two's complement behaviour as day. I.e. behaviour is reasonable in the range 0 to 127. minute: Same as hour second: Range is 0 to 32767) Chronological Julian Day numbers"Julian Dates" count time in days elapsed since noon January 1 4713 BC of the Julian Calendar (= November 24 4714 BCE). When used as a calender, these values are normalized to midnight, resulting in values having a ".5" decimal value. To simplify matters for calendrical calculations, the concept of a chronological day number has been added, using integer values. These functions use Chronological Julian Day numbers as an intermediate calender. This allows conversion of dates between any two implemented calenders. AppleScript Dates in the PastThe following tests have been performed under OS 10.7.2 “Lion”. The strange behaviour of time was introduced with Lion. The automatic switch to the Julian calendar before the introduction of the Gregorian calendar has been introduces with system 10.6 “Snow Leopard”. That latter change was intentional. Some really strange thing happens with AppleScript dates on April 1st 1893 and before. Look at the following code:
set x to date ("1-4-1893") -- British format
log x
log x as text
log minutes of x
log seconds of x
set time of x to 0
log x
log x as text
log minutes of x
log seconds of x
Running it will create the following log: (*date Saturday, 1 April 1893 00:06:32*) (*Saturday, 1 April 1893 00:06:32*) (*6*) (*32*) (*date Friday, 31 March 1893 23:53:28*) (*Friday, 31 March 1893 23:53:28*) (*0*) (*0*) The resulting date is 6 minutes, 32 seconds later than midnight. You can set the time property to 0. That date will behave OK in calculations, but it will convert to a string wrongly. I have no idea what happens in this case. Another behaviour that might look strange, is intentional: The Gregorian calendar became official October 15th 1582 – for the Vatican at least. For all dates prior to that, the internal properties are Gregorian still, but the string conversion will use the Julian calendar. Again some code:
set x to date ("15-1-1000") -- British format
log x
log x as text
log day of x
log month of x
log year of x
log minutes of x
log seconds of x
The resulting log is: (*date Monday, 15 January 1000 00:00:00*) (*Monday, 15 January 1000 00:00:00*) (*20*) (*January*) (*1000*) (*6*) (*32*) Note that the conversions string to date and date to string are consistent in the Julian calendar. But the actual properties are Gregorian. The error about minutes and seconds is still there. What does that mean for the functions here? Some sample code using the newDate function from this collection: set x to newDate(1000, 1, 15) log x log x as text log day of x log month of x log year of x log minutes of x log seconds of x The resulting log (I printed just the part with the log commands) is: (*date Tuesday, 9 January 1000 23:53:28*) (*Tuesday, 9 January 1000 23:53:28*) (*15*) (*January*) (*1000*) (*0*) (*0*) The display and the string conversion are using the Julian calendar and the time display is wrong. But the properties themselves are OK.
|
Calendar Functions - Kalenderfunktionen |