Chronos is one of the many Smalltalk-related blogs syndicated on Planet Smalltalk

Discussion of the Essence# programming language, and related issues and technologies.

Blog Timezone: America/Los_Angeles [Winter: -0800 hhmm | Summer: -0700 hhmm] 
Your local time:  


Chronos Version B1.12 Published--API Changes, New Functionality

Firstly, there have been some significant API changes to CalendarDuration, Timeperiod, SemanticDatePolicy and SemanticAnnualDateRule. Some of the "example do its" in the old version of the "executable examples" will no longer work in this version (so there's also a new version of the "executable examples.") Further details are listed below.

Secondly, the functionality of SemanticDatePolicy and SemanticAnnualDateRule have been enhanced. They are now able to correctly detect all the non-trading days on the New York Stock Exchange, from 1885 to the present--including the fact that from 1873 through 1952, most (but not all) Saturdays were trading days (actually, they only had 2-hour sessions from 10am to Noon (America/New_York time, of course.)

SemanticDatePolicy has two new "example" class methods: #usFederalBusinessDayCountFrom:through:, and #nyseTradingDayCountFrom:through:. The following statement will compute the number of trading days (on the NYSE) from the all-time DJIA high on 14 Jan 2000 to the recent rally high on 11 Jan 2006 (over 300 times/second on my machine):

nyseTradingDayCountFrom: (YearMonthDay year: 2000 month: January day: 14)
through: (YearMonthDay year: 2006 month: January day: 11)

Thirdly, CalendarDuration was refactored into two classes, CalendarDuration and CivilDuration. CalendarDuration itself only represents years, months and days, and does not represent hours, minutes, seconds or fractions of a second. Calendar durations with sub-day temporal extents are now handled by CivilDuration, which inherits from CalendarDuration. CivilDuration should be type-compatible with the old CalendarDuration, and a CalendarDuration will usually auto-convert into a CivilDuration instance when sent any messages that require the bevavior of a CivilDuration (the exceptions will be mentioned below.)

By the way, the reason for the redesign of CalendarDuration was so that when leap seconds are implemented, CivilDurations and CalendarDurations will have the correct behavior. And that probably requires some explanation.

On a normal day, there are 86400 seconds, which are equivalent to 1440 minutes or 24 hours. Each minute of the day has 60 seconds, and each hour has 3600 seconds. Not so on a day with a leap second. A day with a leap second has 86401 seconds. Worse, the minute in which a leap second occurs has 61 seconds, and the hour in which a leap second occurs has 3601 seconds (so that there are still exactly 1440 minutes in the day, and exactly 24 hours.)

So, if one wants to measure time as a scientist would, the easiest approach is to reduce all times (both durations and points in time) to seconds and fractions of a second. Leap seconds (and differences between calendars, for that matter) then disappear. The point-in-time 5000 seconds later than another is a simple matter of adding 5000 to a count of seconds since an epoch. The problems with this approach only arise when one wants to convert from seconds into minutes, hours, days, months and years.

Conversely, if one represents time as years, months, days, hours and minutes, and never deals with seconds, then leap seconds are also invisible, since leap seconds don't change the number of minutes in an hour, the number of hours in a day, the number of days in a month, nor the number of months in a year.

Unfortunately, neither discarding all time units other than seconds, nor ignoring seconds, is a viable option for most people. There's always some reason that a conversion to and/or from seconds and higher-level time units is needed. And leap seconds make that surprisingly tricky.

The old implementation of CalendarDuration stored sub-day time extents internally as just seconds and nanoseconds. That's a reasonable design for a point-in-time value, but in the case of a durational object meant to have civil (business, legal) time semantics, it just doesn't work if leap seconds are supported.

If a duration object is supposed to represent "1 minute, civil time," then when it is added to 2005-12-31T23:59:00Z, the result should be 2006-01-01T00:00:00Z. But if its internal representation of "1 minute" is in fact "60 seconds," then the actual result will be 2005-12-31T23:59:60Z--assuming the point-in-time value correctly implements leap seconds (the final second of 2005 was a leap second.)

The new CivilDuration subclass of CalendarDuration has separate instance variables for hours, minutes, seconds and nanoseconds. So it should also be able to correctly handle "leap hours" or "leap minutes," should those ever be mandated (don't laugh, there's a serious proposal to do just that being hotly debated right now.)

Other New Functionality

  • TemporalCoordinate now implements instance methods such as #addingMinutes:, #addingHours:, #addingDays:, #addingMonths: and #addingYears:, #minutesSince:, #hoursUntil:, #microsecondsSince: and #nanosecondsUntil: (and other similar methods following the same pattern.) In the case of #addingMonths:, #addingYears: and #addingDays:, this just means that TimeOfDay instances will now also understand such messages (and will respond to them as though they were points-in-time of the current day.)

  • TemporalCoordinate now implements instance methods such as #to:every:do:, #through:every:do:, #to:every:daysDo:, #through:every:monthsDo:, and other methods following the same pattern. The "through:" methods enumerate over a closed interval, the "to:" methods enumerate over a left-closed, right-open interval.

  • CalendricalCoordinate now implements instance methods such as #daysSince:, #monthsUntil: and #yearsSince: (and other similar methods following the same pattern.)

  • Core.TimeZone now understands #asChronosValue. It responds with a ChronosTimezone equivalent to itself. Similarly, a ChronosTimezone now understands #asNative. It responds with a Core.TimeZone as congruent to itself as possible (using the rules for the current year.)

  • Core.Time now understands #asChronosValue. It responds with a TimeOfDay equivalent to itself. TimeOfDay now understands #asNative. It responds with a Core.Time representing the same time of day (down to the second, since Core.Time doesn't deal with sub-second times.)

API Changes

  1. The following expression will now result in an MNU: "CalendarDuration days: 0.5." The reason is because the instance of CalendarDuration that is created will now no longer understand the initialization messages needed to set sub-day time periods. Use CivilDuration instead. (In contrast, "CalendarDuration seconds: 5" will still work--but it returns a CivilDuraiton, not a CalendarDuration.) Preventing this problem would have required that the class methods check the input arguments for fractional values--and I really don't want to do that.

  2. The Timeperiod instance methods #seconds, #minutes, #hours, #days, #weeks, #months, #quarters and #years have all been renamed to #secondPeriods, #minutePeriods, #hourPeriods, #dayPeriods, #weekPeriods, #monthPeriods, #quarterPeriods and #yearPeriods (respectively.) Timeperiod>>monthPeriods (for example) answers a collection of the month-long Timeperiods contained by the receiving Timeperiod. Messages named #months (for example) were ambiguous, and conflicted semantically with messages of the same name in other classes.

  3. The CalendricalCoordinate instance methods #calendarDurationSince: and #calendarDurationUntil: still answer instances of CalendarDuration--but that means those methods no longer consider the sub-day temporal extents between the two points in time. The messages #civilDurationSince: and #civilDurationUntil: have been added, and have the same semantics as the first two methods used to have.

  4. The CalendricalCoordinate instance methods #annualDateSemanticKeyFrom:ifNone: and #annualDateSemanticKeyIfNone: have been renamed to #annualDateSemanticKeysFrom:ifNone: and #annualDateSemanticKeysIfNone: (respectively.) There can be more than one "annualDateSemanticKey" associated with the same date.

No comments: