Concerning Time.TimeDiff

John Meacham john@repetae.net
Thu, 19 Jun 2003 17:46:06 -0700


On Wed, Jun 18, 2003 at 11:22:55AM +0100, Simon Marlow wrote:

> -- | An empty 'TimeDiff'
> noTimeDiff :: TimeDiff
> 
> -- | Returns the difference between two 'ClockTime's
> diffClockTimes :: ClockTime -> ClockTime -> TimeDiff
> 
> -- | Adds a 'TimeDiff' to a 'ClockTime'
> addToClockTime :: ClockTime -> TimeDiff -> ClockTime

do we need these now that ClockTime is an instance of Num? I am not
opposed to them, they might catch some compile time bugs. A nice thing
about using TAI is now everything is a 'time difference' on the same
scale. (real seconds) as opposed to POSIX time where it is unclear.

> {-
> Rationale:
> 
>   - 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.
>     Our ClockTime is defined so as to avoid this brokenness, but it
>     means that a ClockTime cannot trivially be converted to a POSIX
>     time_t.
> 
>   - TimeDiff is now an absolute measure of time period, as compared to
>     the Haskell 98 TimeDiff which was underspecified in this respect.
>   
> Invariants:
> 
>   t1 `addToClockTime` (t2 `diffClockTimes` t1) == t2
>   t1 `addToClockTime` noTimeDiff == t1
>   t1 `diffClockTimes` t1 == noTimeDiff
> 
> -}

all good. except, perhaps the last sentence of the first paragraph
should read "it means that a ClockTime cannot trivially be converted to
UTC based times such as the POSIX time_t." since there are other systems
which have the same problems as the POSIX system.

> 
> ----------------------------------------------------------------------------
> -- * 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)
> 
> data Timezone -- abstract
> 
> -- | Make a 'Timezone'
> -- TODO: do we need to specify daylight savings time too?
> timezoneFromOffset :: Int    -> Timezone
> timezoneFromName   :: String -> Timezone

I am pretty sure the daylight savings time is specified as part of the
timezone. PDT vs. PST (daylight vs. standard)

we should specify that the 'Offset' is in minutes. not
all timezones are integral hour offsets. plus, we should specify that
timezoneFromName MUST accept at least "UTC" and "TAI".

> -- | Convert a 'ClockTime' to a 'CalendarTime' in the current timezone
> clockTimeToCalendarTime :: ClockTime -> IO CalendarTime
> 
> -- | Convert a 'ClockTime' to a 'CalendarTime' in UTC
> clockTimeToUTCTime :: ClockTime -> CalendarTime

it is VERY important to provide the general functions:

clockTimeToTZCalendarTime :: Timezone -> ClockTime -> CalendarTime
getCurrentTimezone :: IO Timezone

(of which the previous two are simple derivatives)

otherwise it would be impossible to get out the TAI time in a printable
format for instance, or use the calendartime to do conversions between
timezones.

> -- | 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
good, good. this combined with clockTimeToTZCalendarTime is a very
powerful combo, letting you do things like conversion to POSIX time_t
easily,  
(fromJust . calendarTimeToClockTime . \t -> t {ctTZ = stringToTimezone "TAI"} . clockTimeToTZCalendarTime "UTC")

well, maybe not easily, but possibly. which is more than before.


> {-
>  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?
> -}

how about just a single function
normalizeCalendarTime :: CalendarTime -> CalendarTime 
which turns any non-normal calendartimes into normal ones. it would be
easier to specify the behavior and do what people want when they use the
above functions. by normalize i mean add any seconds over 59 (or 60) to
the minutes field, add any minutes over 59 to the hours field and so
forth up the chain... then you can just modify your CalendarTime, then
normalize it to fix up any overflow (or underflow with negative values).
It may not always be exactly what you want, but at least we should be
able to precisly specify it's behavior so people know what to expect.  

        John


-- 
---------------------------------------------------------------------------
John Meacham - California Institute of Technology, Alum. - john@foo.net
---------------------------------------------------------------------------