TOP | UP: Data Manipulation | NEXT: Objects |
On internal storage of the date type, and on coercion of dates to and from strings, see Chapter 10, Datatypes.
Recall that on Mac OS, a date1 is stored as an unsigned long representing the number of seconds since the start of 1904. Since an unsigned long is a numeric type, no special date operators are needed in order to perform date arithmetic; math operators can be applied directly to date values. Also, a long and a date can be combined in a math operation, because the long will be implicitly coerced to a date. For example, to find out the number of days between two dates, just subtract and divide by (60*60*24), like this:
long((date("Feb 3 1997") - date("Jan 28 1997")) / (60*60*24))
« 6
The tricky part is that dates cannot be externally represented without coercion. Coercing a date with double() shows its numeric value more readably than coercing it with long(), because a date is an unsigned long; coercing with long() causes wraparound to a negative number for dates after early 1972. However, a double cannot be coerced with date(). The following utility converts a double back to a date.
on doubleToDate(d) |
if d > infinity |
d = d - infinity - infinity - 2 |
return (date(long(d))) |
An unsigned long measuring seconds starting in 1904 can handle only up through early 2040. On the Mac, dates outside this range2 are handled as a pair of longs, called a LongDateTime . Certain foresighted applications have already adopted this format for their internal dates; in order to communicate about dates with such applications, it may be necessary to convert between Frontier's date type and this LongDateTime type. The following scripts (by John Baxter) show how to do this; note that these scripts do not give Frontier itself the power to work with dates outside its range.
on dateToLDT (aDate) |
local |
ldt = binary (string (binary (0)) + string (binary (aDate))) |
setBinaryType (@ldt, 'ldt ') |
return (ldt) |
Sometimes coercion happens behind the scenes; don't be misled. If you say clock.now() in Quick Script, the response is legible; but this is only because the result, which is actually a date, has been coerced to a string for display. Such a string would be useless for further numeric calculation in a script. On the other hand, the real result of clock.now() could be used in numeric calculation.
Beware of making assumptions about the structure of string representations of dates. For example, on my computer, date.shortString(myBirth) yields "8/10/54"; when I lived in New Zealand, it yielded "10/8/54", with the day-number preceding the month-number. These results come from system calls which examine settings in the Date & Time control panel; therefore, they vary from computer to computer.
Nevertheless, Frontier's string-to-date coercion is remarkably intelligent, and can accomplish a great deal on its own. The following, for instance, is a (simplified) utility that converts the date line of an email header to GMT.
To analyze a date into its numerical components:
date.get (theDate, addrDay, addrMonth, addrYear, addrHour,
addrMin, addrSec)
To construct a date from its numerical components:
date.set (day, month, year, hour, min, sec)
It is wise, in specifying a year value for date.set(), to supply a four-digit number (e.g., 1997), because the result of supplying a two-digit number can be unexpected if Frontier guesses wrong about what century you mean.
It is not an error to supply date.set() with values that are out of range for their category, and indeed this can be a useful way to perform date calculation. For example, what is the date 40 days from today?
local (day, mon, yr, hr, min, sec) |
date.get(clock.now(), @day, @mon, @yr, @hr, @min, @sec) |
msg (date.set (day+40, mon, yr, hr, min, sec)) |
Frontier returns a legal date 40 days from now.
The technique is particularly handy when dealing with months and years, which vary in length. Month manipulations are a little tricky; Frontier's algorithm is sensible, but it may not give quite the results you expected. Increasing or decreasing a date's month component returns a date with the same day-number as the first - provided the new month possesses such a day-number. If not, we lapse over into the start of the following month. So, for example:
local (day, mon, yr, hr, min, sec)
date.get("march 31, 1997", @day, @mon, @yr, @hr, @min, @sec)
msg (date.set (day, mon-1, yr, hr, min, sec))
« "3/3/1997; 12:00:00 AM"
Since there is no February 31, we lapse three days into March to get our result.
Given a date, to add or subtract one unit:
date.tomorrow (date)
date.yesterday (date)
date.nextWeek (date)
date.prevWeek (date)
date.nextMonth (date)
date.prevMonth (date)
date.nextYear (date)
date.prevYear (date)
These are just shortcuts, giving exactly the same results as the technique used in Example 16-5. For example, date.tomorrow() could have been implemented as:
on tomorrow(theDate)
local (day, mon, yr, hr, min, sec)
date.get(theDate, @day, @mon, @yr, @hr, @min, @sec)
return date.set (day+1, mon, yr, hr, min, sec)
date.firstOfMonth (date)
date.lastOfMonth (date)
date.daysInMonth (date)
date.weeksInMonth (date)
date.dayOfWeek (date)
Returns a day-of-week number from 1 to 7, where 1 represents Sunday.
To convert from a day-of-week number to a day name:
date.dayString (number)
Implemented as a script which simply uses a case statement.
To obtain formatted date string representations from the system:
date.shortString (date)
date.abbrevString (date)
date.longString (date)
It is easier to list than to describe the results (on my machine):
x=date.set(10,8,1954,3,0,0)
date.shortString(x)
« "8/10/54"
date.abbrevString(x)
« "Tue, Aug 10, 1954"
date.longString(x)
« "Tuesday, August 10, 1954"
To obtain formatted string representations of the current date and time from the system:
string.dateString ()
string.timeString ()
string.dateString()
« "Sun, Jun 8, 1997"
string.timeString()
« "2:03:20 PM"
I believe that saying string.dateString() is equivalent to saying date.abbrevString(clock.now()).
There are constants (at system.verbs.constants) that allow you to type the full English name of a month instead of its month number, as follows:
x=date.set(10,August,1954,3,0,0)
«"8/10/54; 3:00:00 AM"
TOP | UP: Data Manipulation | NEXT: Objects |