RE: Time library discussion (reprise)

On Tue, 11 Nov 2003 12:44:59 -0000 "Simon Marlow"
wrote: This has been demonstrated - recently by Juanma Barranquero's library posted to the Haskell list,
I think you're referring to Stefan Karrmann's library. I just did a couple comments about it.
Oops! My apologies.
IMHO, Haskell should have a simple module which allowed to manipulate dates and times in UTC (and convert to/from localtimes, and perhaps MJD/JD), and, as an extension, a TAI module for the (probably very few) people who needs that kind of precision.
Let's talk in concrete terms. Could you propose an API for what you have in mind for the basic UTC functionality? Presumably, without TAI support, the library will be unable to do accurate calendar calculations at the second level (i.e. "add S seconds to time T"), although it will be able to do larger scale calendar calculations (i.e. adding days, months, etc.). Or maybe you provide the second-level calculations but document that they don't take into account leap seconds?
I know that I've been programming quite a few years and I've never needed to know TAI times, nor ignoring the leap seconds have been a problem. And, till very recently, I was working for a VoIP carrier, so you can bet we manipulated dates, times and durations *constantly*. I'm not saying there aren't problem domains where that level of precision is required. Just they're not that common, I think.
Ok, it's useful to have someone with experience of actually using this stuff for Real Work. As always, I'm happy to go along with a concensus if one can be reached. Cheers, Simon

Simon Marlow writes:
Presumably, without TAI support, the library will be unable to do accurate calendar calculations at the second level [...]
As I understand it, calendar time calculations into the future are _always_ inaccurate, because UTC is not continuous. Either you'll get an incorrect "duration" or you'll get an incorrect "point in time". It can't be helped. The C++ time library of the Boost effort has a very interesting discussion of this topic, available here: http://boost.org/libs/date_time/doc/Tradeoffs.html Pardon me, if this has been brought up already. As for implementing a time library in Haskell, we might be able to base the effort on one of the existing API designs -- or even one of the existing implementations? There are, among others: - The Boost.Time library mentioned above. - A proposed API extension for the ISO C 200X standard: http://www.cl.cam.ac.uk/~mgk25/time/c/ - Proposed ISO C 200X Calendar Library Functions: http://david.tribble.com/text/c0xcalendar.html - Dan Berstein's libtai: http://cr.yp.to/libtai.html Surely there is _something_ out there that can be re-used? Peter

As I understand it, calendar time calculations into the future are _always_ inaccurate, because UTC is not continuous. Either you'll get an incorrect "duration" or you'll get an incorrect "point in time". It can't be helped.
The C++ time library of the Boost effort has a very interesting discussion of this topic, available here:
Thanks for the link, that's a very good summary of some of the issues. The tradeoffs page seems to indicate that you can do calculations with TAI times, but I couldn't see how to do that with the library, and it looks like ptime is equivalent to a POSIX time_t. They have an interesting separation between calendar calculations (day resolution) and time calculations (sub-second resolution?). I couldn't immediately see the reason for that. Is this something we should be doing too? Anyway, I've attached the current state of the proposal below. As far as I'm aware, this proposal is just fine with the caveats listed in the comments, and additionally with the caveat that I'm not sure if the full generality of the timezone manipluation provided can be implemented on top of the existing Unix APIs, so we might have to scale it back a bit. Cheers, Simon -- ------------------------------------------------------------------------- -- * ClockTime -- | A representation of absolute time, measured as picoseconds since -- the epoch, where the epoch is 1 January 1970 00:10 TAI. data ClockTime -- abstract instance of (Eq, Ord, Num, Enum, Integral, Show, Read) -- | returns the current absolute time getClockTime :: IO ClockTime {- Rationale: - Our ClockTime is defined in terms of TAI, because this provides an absolute time scale and can be used for accurate time calculations. However, this is not always implementable. Many systems run their system clocks on a time scale that ignores leap seconds. For example, POSIX's time_t uses a broken notion of "seconds since the epoch", defined by a formula in terms of UTC time ignoring leap seconds. On systems which run their clocks on time_t time, the library will do its best to convert to TAI time for a ClockTime. The effect is that the ClockTime might be incorrect by up to 1 second around the time of a leap second (it depends on how your system adjusts its clock when a leap second occurs). Regardless of what the system supports, calculations on values of type ClockTime are well-defined and deterministic. Inaccuracies only occur at the boundaries: - getClockTime might be inaccurate on Unix systems, for the reasons mentioned above. - Converting a ClockTime representing a future time into a UTC-based CalendarTime might be inaccurate because of the lack of knowledge of future leap seconds. This problem will be present in any library providing UTC operations. -} {- TODO: maybe also provide toPosixTime :: ClockTime -> Integer fromPosixTime :: Integer -> ClockTime -} -- ------------------------------------------------------------------------- -- * Timezone data Timezone -- abstract -- | Make a 'Timezone' from an offset, in seconds relative to UTC, -- which must be smaller in magnitude than @+/-12*60*60@. timezoneFromUTCOffset :: Int -> Timezone -- | Make a 'Timezone' from a standard timezone name (eg. GMT, PDT). -- TAI is a valid timezone name. timezoneFromName :: String -> Maybe Timezone -- | Return the offset in seconds of the specified timezone relative -- to UTC. If the timezone is TAI, returns 'Nothing', because TAI -- cannot be represented as a fixed offset relative to UTC. timezoneUTCOffset :: Timezone -> Maybe Int -- | Return the timezone name corresponding to a 'Timezone' value. -- -- Some timezones may not correspond to a name, or the name of the timezone -- may not be known (some systems cannot convert easily from UTC offsets to -- timezone names), in which case 'timezoneName' returns 'Nothing'. timezoneName :: Timezone -> Maybe String -- | Returns the current timezone from the environment. On Unix, the -- current timezone is taken from the @TZ@ environment variable, or -- the system default if @TZ@ is not set. getCurrentTimezone :: IO Timezone {- TODO; we also might want to allow rfc2822 style timezones. of the form "+nnnn" where nnnn is the offset from GMT. convienince routines to convert to/from rfc2822 time strings might be handy too. this is all not as important as it could be done in an add-in library, but might get common usage. -} -- ------------------------------------------------------------------------- -- * CalendarTime data CalendarTime = CalendarTime { ctYear :: Int, ctMonth :: Month, ctDay :: Int, ctHour :: Int, ctMin :: Int, ctSec :: Int, ctPicosec :: Integer, ctTZ :: Timezone } deriving (Eq, Ord, Read, Show) -- | Converts a 'ClockTime' to a 'CalendarTime' in UTC. -- -- Note that this function may produce unpredictable results for -- times sufficiently far in the future, because it is not known -- when leap seconds will need to be added to or subtracted from -- UTC. Note that this doesn't apply if the timezone is TAI. -- clockTimeToUTCTime :: ClockTime -> CalendarTime -- | Converts a 'ClockTime' to a 'CalendarTime' in the current timezone. -- Caveats for 'clockTimeToUTCTime' also apply here. clockTimeToCalendarTime :: ClockTime -> IO CalendarTime -- | Converts a 'ClockTime' to a 'CalendarTime' in the specified timezone. -- Caveats for 'clockTimeToUTCTime' also apply here. clockTimeToCalendarTimeTZ :: Timezone -> ClockTime -> CalendarTime -- | Convert a 'CalendarTime' to a 'ClockTime'. Some values of -- 'CalendarTime' do not represent a valid 'ClockTime', hence this -- function returns a 'Maybe' type. calendarTimeToClockTime :: CalendarTime -> Maybe ClockTime {- TODO: add isDSTCalendarTime? (returns True if the specified CalendarTime is in daylight savings). How do we say "what's the current timezone in X", taking into account DST? -} {- TODO: should we have getLeapSeconds :: [ClockTime] a possibly infinite list of leap seconds in strictly increasing order. This would allow simple conversion between TAI and UTC. -} {- OPTIONAL: these are hard to implement, and require careful specification (see rationale below): addPicoseconds :: CalendarTime -> Integer -> CalendarTime addSeconds :: CalendarTime -> Integer -> CalendarTime addMinutes :: CalendarTime -> Integer -> CalendarTime addDays :: CalendarTime -> Integer -> CalendarTime addWeeks :: CalendarTime -> Integer -> CalendarTime addMonths :: CalendarTime -> Integer -> CalendarTime addYears :: CalendarTime -> Integer -> CalendarTime Rationale: - Adding "irregular" time differences should be done on CalendarTimes, because these operations depend on the timezone. - Need to define the meaning when the offset doesn't exist. eg. adding a day at the end of the month clearly rolls over into the next month. But what about adding a month to January 31st? - Note that addPicoseconds and addSeconds cannot be implemented without access to leap second tables. However, all the others can be implemented using simple calendar arithmetic (including leap years). If the timezone is TAI, then addPicoseconds and addSeconds can be implemented without leap second knowledge, of course. OR: we could provide normalizeCalendarTime :: CalendarTime -> CalendarTime where the following invariant holds: forall t . isJust (calendarTimeToClockTime (normalizeCalendarTime t)) that is, normalizeCalendarTime turns a possibly invalid CalendarTime into a valid one. The intention is that addDays could be implemented as: addDays t days = normalizeCalendarTime t{ ctDays = ctDays t + days } We still need to specify what exactly normalizeCalendarTime does, however. Presumably it needs to know about leap seconds, for example, but only for rolling over the seconds and picoseconds fields. The other fields of CalendarTime can be normalised using ordinary calendar calculations. -}

"Simon Marlow"
-- | A representation of absolute time, measured as picoseconds since -- the epoch, where the epoch is 1 January 1970 00:10 TAI. data ClockTime -- abstract instance of (Eq, Ord, Num, Enum, Integral, Show, Read)
-- | returns the current absolute time getClockTime :: IO ClockTime
The advantage (what I tried to say in my previous message) is that something like t1 <- getClockTime : t2 <- getClockTime print ("Elapsed time: "++ show (t2-t1)++" seconds") would work correctly, and not just work "most of the time", like it would with UTC.
data Timezone -- abstract
-- | Make a 'Timezone' from an offset, in seconds relative to UTC, -- which must be smaller in magnitude than @+/-12*60*60@. timezoneFromUTCOffset :: Int -> Timezone
Don't you also need a specific time? We have UTC+0100 in the winter, but +0200 in summer (or was it the other way around? :-) Perhaps we should separate between Timezones (the TLAs) and their offsets. We could have a localTime :: Timezone -> ClockTime -> CalendarTime and make the offset, but not TZ, a part of CalendarTime. Would it then make (more) sense to compare CalendarTimes? At least it wouldn't need to involve timezone tables. data Timezone = UTC | GMT | PDT ... deriving ... data TZOffset = -- abstract (or just an Int) instance Num TZOffset -- auto conversion from int, etc tzToOffset :: Timezone -> TZOffset
TODO: add isDSTCalendarTime? (returns True if the specified CalendarTime is in daylight savings). How do we say "what's the current timezone in X", taking into account DST?
Exactly.
- Need to define the meaning when the offset doesn't exist. eg. adding a day at the end of the month clearly rolls over into the next month. But what about adding a month to January 31st?
Perhaps it would be possible to have a more flexible system, where one could specify things like the last day of the month, the second Wednesday every second month, and so on? Not sure how it would look, though. -kzm -- If I haven't seen further, it is by standing in the footprints of giants

In article
-- | Converts a 'ClockTime' to a 'CalendarTime' in UTC. -- -- Note that this function may produce unpredictable results for -- times sufficiently far in the future, because it is not known -- when leap seconds will need to be added to or subtracted from -- UTC. Note that this doesn't apply if the timezone is TAI. -- clockTimeToUTCTime :: ClockTime -> CalendarTime
Bear in mind that six months is "sufficiently far". That's all the notice we get of leap seconds (IERS Bulletin C). You can guess, of course, but that won't buy you more than a couple of years. How do you intend to keep the leap second table up to date? Also note that leap seconds are allowed to be negative. There's actually a possibility that that might happen in the next few years, and of course programmers who weren't paying attention to the rules will be caught out if that happens (though I've heard NTP is OK). The rotation of the earth is quite unpredictable: indeed, such things as damming bodies of water close to the equator is enough to make a significant difference in "delta T". -- Ashley Yakeley, Seattle WA

Ashley Yakeley writes:
How do you intend to keep the leap second table up to date?
You could run an automated cron job (or whatever your OS uses for these purposes) and update it automatically from http://cr.yp.to/libtai/leapsecs.txt. Or, if your OS supports the mechanisms described in http://www.ntp.org/ntpfaq/NTP-s-algo.htm#S-ALGO-KERNEL, leap seconds will be announced to the OS by the Network Time Protocol daemon. Or one could argue that this problem is beyond the scope of a "programming language" and that the users are on their own. I'm sure the vast majority of the population _will_ survive, even if their computer system misses a leap second. OK, maybe not the vast majority ... But what the heck, this planet is too crowded anyway. :-) Peter

On Tue, 11 Nov 2003 13:34:16 -0000, "Simon Marlow"
Let's talk in concrete terms. Could you propose an API for what you have in mind for the basic UTC functionality?
I'm not knowledgeable enough in Haskell to be able to propose a sensible API, but still..
although it will be able to do larger scale calendar calculations (i.e. adding days, months, etc.). Or maybe you provide the second-level calculations but document that they don't take into account leap seconds?
...that's the idea. A simple Calendar class with instances UTC, Gregorian/ISO, JD and/or MJD perhaps. Simple operations to add and substract periods, compare dates/times, convert between them, ask for miscellaneous info (leap year, day of week, etc.); with support for timezones (offsets from UTC) of course. That's all I've ever needed from a datetime library. As Peter Simmons has said, there are several libraries available for other languages which already implement a sensible set of operations for dates (boost has a good example). We should just borrow from them. /L/e/k/t/u
participants (5)
-
Ashley Yakeley
-
Juanma Barranquero
-
Ketil Malde
-
Peter Simons
-
Simon Marlow