AbstractTime.st
author Claus Gittinger <cg@exept.de>
Tue, 09 Jul 2019 20:55:17 +0200
changeset 24417 03b083548da2
parent 24117 c1a02a758d28
child 24423 27a335bed151
permissions -rw-r--r--
#REFACTORING by exept class: Smalltalk class changed: #recursiveInstallAutoloadedClassesFrom:rememberIn:maxLevels:noAutoload:packageTop:showSplashInLevels: Transcript showCR:(... bindWith:...) -> Transcript showCR:... with:...

"{ Encoding: utf8 }"

"
 COPYRIGHT (c) 1995 by Claus Gittinger
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libbasic' }"

"{ NameSpace: Smalltalk }"

Magnitude subclass:#AbstractTime
	instanceVariableNames:''
	classVariableNames:''
	poolDictionaries:''
	category:'Magnitude-Time'
!

!AbstractTime class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1995 by Claus Gittinger
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    This is an abstract class providing common protocol for Time (time in day)
    and Timestamp (time plus day).
    There are no instances of this class in the system.
    It is meant as a home for methods common to time handling classes.

    [author:]
        Claus Gittinger

    [See also:]
        Time Date Timestamp
        Delay ProcessorScheduler
"
!

iso8601FormatDocumentation
"
  Abstract

    This document defines a profile of ISO 8601, the International Standard for the representation of dates and times. ISO
    8601 describes a large number of date/time formats. To reduce the scope for error and the complexity of software, it is
    useful to restrict the supported formats to a small number. This profile defines a few date/time formats, likely to satisfy
    most requirements. 


  Formats

    Different standards may need different levels of granularity in the date and time, so this profile defines six levels.
    Standards that reference this profile should specify one or more of these granularities. If a given standard allows more
    than one granularity, it should specify the meaning of the dates and times with reduced precision, for example, the result
    of comparing two dates with different precisions.

    The formats are as follows. Exactly the components shown here must be present, with exactly this punctuation. Note
    that the 'T' appears literally in the string, to indicate the beginning of the time element, as specified in ISO 8601. 

       Year:
          YYYY (eg 1997)
       Year and month:
          YYYY-MM (eg 1997-07)
       Complete date:
          YYYY-MM-DD (eg 1997-07-16)
       Complete date plus hours and minutes:
          YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
       Complete date plus hours, minutes and seconds:
          YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
       Complete date plus hours, minutes, seconds and a decimal fraction of a
    second
          YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)

    where:

         YYYY = four-digit year
         MM   = two-digit month (01=January, etc.)
         DD   = two-digit day of month (01 through 31)
         hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
         mm   = two digits of minute (00 through 59)
         ss   = two digits of second (00 through 59)
         s    = one or more digits representing a decimal fraction of a second
         TZD  = time zone designator (Z or +hh:mm or -hh:mm)

    This profile does not specify how many digits may be used to represent the decimal fraction of a second. An adopting
    standard that permits fractions of a second must specify both the minimum number of digits (a number greater than or
    equal to one) and the maximum number of digits (the maximum may be stated to be 'unlimited').

    This profile defines two ways of handling time zone offsets:

       1.Times are expressed in UTC (Coordinated Universal Time), with a special UTC designator ('Z'). 
       2.Times are expressed in local time, together with a time zone offset in hours and minutes. A time zone offset of
         '+hh:mm' indicates that the date/time uses a local time zone which is 'hh' hours and 'mm' minutes ahead of
         UTC. A time zone offset of '-hh:mm' indicates that the date/time uses a local time zone which is 'hh' hours and
         'mm' minutes behind UTC. 

    A standard referencing this profile should permit one or both of these ways of handling time zone offsets.

  The ISO8601 printString are generated with:

       Year:
          YYYY (eg 1997)
                Date today printStringFormat:'%(year)'
                Timestamp now printStringFormat:'%(year)'  

       Year and month:
          YYYY-MM (eg 1997-07)
                Date today printStringFormat:'%(year)-%(month)'  
                Timestamp now printStringFormat:'%(year)-%(month)'  

       Complete date:
          YYYY-MM-DD (eg 1997-07-16)
                Date today printStringFormat:'%(year)-%(month)-%(day)'    
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)'  

       Complete date plus hours and minutes:
          YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m%(TZD)'  

       Complete date plus hours, minutes and seconds:
          YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m:%s%(TZD)'  

       Complete date plus hours, minutes, seconds and a decimal fraction of a second
          YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m:%s.%(milli2)%(TZD)'  
"
!

printFormatDocumentation
"
     bindings:
        %h      hours, 00..23 (i.e. european)  0-padded to length 2
        %u      hours, 00..12 (i.e. us)        0-padded to length 2
        %m      minutes, 00..59                0-padded to length 2
        %s      seconds, 00..59                0-padded to length 2
        %i      milliseconds, 000..999         0-padded to length 3
        %a      am/pm

     Timestamp only:
        %(day)   day, 00..31                    0-padded to length 2
        %(month) month, 00..12                  0-padded to length 2
        %(year)  year, 4 digits                 0-padded to length 4

     special:
        %H      24-hours - unpadded
        %U      12-hours - unpadded
        %M      minutes - unpadded
        %S      seconds - unpadded
        %I      milliseconds, unpadded
        %A      AM/PM   - uppercase

        %t      seconds within hour  (unpadded)
        %T      seconds from midNight  (unpadded)

        %(TZD)  timeZone delta of the receiver from UTC in the format +/-hh:mm

        %(milli1) milliseconds, truncated to 1/10th of a second 0..9
        %(milli2) milliseconds, truncated to 1/100th of a second 00..99 0-padded to length 2
        %(milli3) milliseconds, same as %i for convenience

     Timestamp only:
        %(Day)         - day - unpadded
        %(Month)       - month - unpadded
        %(yearOrTime)  - year or time 5 digits    as in unix-ls:
                                                  year if it is not the current year;
                                                  time otherwise
        %(weekDay)      - day in week (1->monday, 2->tuesday, ... ,7->sunday)

        %(dayName)      - full day name
        %(DayName)      - full day name, first character uppercase
        %(DAYNAME)      - full day name, all uppercase

        %(monthName)    - full month name
        %(MonthName)    - full month name, first character uppercase
        %(MONTHNAME)    - full month name, all uppercase

        %(shortDayName) - short (abbreviated) day name
        %(ShortDayName) - short (abbreviated) day name, first character uppercase
        %(SHORTDAYNAME) - short (abbreviated) day name, all uppercase

        %(shortMonthName) - short (abbreviated) month name
        %(ShortMonthName) - short (abbreviated) month name, first character uppercase
        %(SHORTMONTHNAME) - short (abbreviated) month name, all uppercase

        %(nth)          - counting day-in-month (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')
        %(weekDayNth)   - counting day-in-week (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')
        %(weekNth)      - counting week-in-year (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')

        %(yearRoman)    - year, in roman letters
        %(monthRoman)   - month, in roman letters


     The ISO8601 printString are documented in iso8601FormatDocumentation:
"
! !

!AbstractTime class methodsFor:'instance creation'!

dateAndTimeNow
    "return an array filled with the current date and time.
     As these provide no timezone info, this should be only used for user interface purposes.
     See also: Date today / Time now / Timestamp now."

    ^ Array with:(Date today) with:(Time now)

    "
     Time dateAndTimeNow       
     Date dateAndTimeNow       
    "

    "Modified: 19.4.1996 / 15:23:37 / cg"
!

epoch
    "answer the time when we start counting"

    ^ self new setSeconds:0

    "
     Timestamp epoch
     Time epoch     
    "
!

now
    "return an instance of myself representing this moment with at least second precision.
     Timestamps will redefine this to always return millisecond precision."

    ^ self basicNew fromOSTime:(OperatingSystem getOSTime)

    "
     Timestamp now   
     Time now   
    "

    "Modified: 1.7.1996 / 15:20:10 / cg"
!

nowWithMicroseconds
    "return an instance of myself representing this moment with at least microsecond precision."

    |osTime millis micros|

    osTime := OperatingSystem getOSTimeWithMicros.
    millis := osTime at:1.
    micros := osTime at:2.
    ^ self basicNew fromOSTimeWithMilliseconds:millis additionalPicoseconds:(micros*(1000*1000)).

    "
     Timestamp now   
     Time now
     Timestamp nowWithMilliseconds
     Timestamp nowWithMicroseconds
    "

    "Modified: 1.7.1996 / 15:20:10 / cg"
!

nowWithMilliseconds
    "return an instance of myself representing this moment with at least millisecond precision."

    ^ self basicNew fromOSTimeWithMilliseconds:(OperatingSystem getOSTime)

    "
     Time now
     Time nowWithMilliseconds 

     Timestamp now   
     Timestamp nowWithMilliseconds
     Timestamp nowWithMicroseconds
    "

    "Modified: 1.7.1996 / 15:20:10 / cg"
!

utcNow
    "return an instance of myself representing this momentin the UTC timezone."

    ^ self subclassResponsibility

    "
     Timestamp utcNow   
     Timestamp utcNow

     Time now   
     Time utcNow   
    "

    "Modified: 1.7.1996 / 15:20:10 / cg"
! !

!AbstractTime class methodsFor:'Compatibility-Squeak'!

dateAndTimeFromSeconds:secondCount
    "set date and time from seconds since 1901-01-01 00:00 UTC"

    |timestamp|

"   
    secondsBetween1901and1970 := 
        (Timestamp epoch asDate subtractDate:(Date day:1 month:1 year:1901))
        *  (24 * 60 * 60)
"

    "stc cannot make large integers"
    timestamp := Timestamp fromSeconds:secondCount - 2177452800.
    ^ Array
        with: (timestamp asDate)
        with: (timestamp asTime)

    "
     Timestamp dateAndTimeFromSeconds: (Time primSecondsClock) 
     Time dateAndTimeFromSeconds: (Time totalSeconds) 
     Date dateAndTimeFromSeconds: (Time totalSeconds)
    "
!

milliseconds:msTime1 since:msTime2
    "return the number of milliseconds between two
     millisecond time values, compensating for roll-over.
     The same as millisecondsBetween:and: for Squeak compatibility."

    ^ OperatingSystem millisecondTimeDeltaBetween:msTime1 and:msTime2
!

millisecondsSince: lastTime
        "Answer the elapsed time since last recorded in milliseconds.
        Compensate for rollover."

        ^self milliseconds: self millisecondClockValue since: lastTime
!

primSecondsClock
    "returns the number of seconds since 1.1.1901 UTC"

    ^ Timestamp now utcSecondsSince1901

    "
     Timestamp primSecondsClock
    "
! !

!AbstractTime class methodsFor:'Compatibility-VW'!

totalSeconds
    "returns the number of seconds since 1.1.1901 UTC"

    ^ Timestamp now utcSecondsSince1901
! !

!AbstractTime class methodsFor:'error handling'!

conversionErrorSignal
    "return the signal used for conversion error handling"

    ^ TimeConversionError
! !

!AbstractTime class methodsFor:'format strings'!

defaultFormatString
    ^ '%h:%m:%s'
! !


!AbstractTime class methodsFor:'private-instance creation'!

fromOSTime:osTime
    "return a time, representing the time given by the operatingSystem time.
     Not meant for public use."

    ^ self basicNew fromOSTime:osTime.

    "Modified: 1.7.1996 / 15:09:54 / cg"
!

fromSeconds:seconds
    "return an instance that is constructed from seconds.
     This method is only allowed for second values as returned by
     getSeconds, possibly adding/subtracting to that.
     Never depend on any specific interpretation of the seconds,
     since it depends on how the OperatingSystem counts time
     (some start at 1900, others with 1970 ...)"

   ^ self basicNew setSeconds:seconds

    "
     Time fromSeconds:0             should return midnight
     Timestamp fromSeconds:0     on UNIX: returns 1st. Jan 1970
                                    on others: don't know
     (Timestamp day:1 month:1 year:1970 hour:1 minutes:0 seconds:0)
        getSeconds                  on UNIX: returns 0
                                    on others: don't know
    "

    "Modified: 1.7.1996 / 13:39:30 / cg"
! !

!AbstractTime class methodsFor:'queries'!

isAbstract
    ^ self == AbstractTime
!

microsecondClockValue
    "return microseconds seconds of now"

    ^ OperatingSystem getMicrosecondTime

    "
     Time microsecondClockValue
    "

    "
     |t1 t2 overhead|

     t1 := Time microsecondClockValue.
     t2 := Time microsecondClockValue.
     overhead := t2 - t1.  

     t1 := Time microsecondClockValue.
     100 factorial.
     t2 := Time microsecondClockValue.
     t2 - t1 - overhead        
    "
!

millisecondClockValue
    "return the millisecond clock - since this one overruns
     regularly, use the value only for short timing deltas.
     Also remember that it wraps when comparing these values."

    ^ OperatingSystem getMillisecondTime.

    "
     Time millisecondClockValue 
    "
!

secondClock
    "return seconds of now - for GNU-ST compatibility"

    ^ OperatingSystem getOSTime // 1000

    "
     AbstractTime secondClock    
    "

    "Modified: 1.7.1996 / 15:20:14 / cg"
! !

!AbstractTime class methodsFor:'reading'!

readFrom:aStringOrStream format:formatString
    "see format description in readFrom:format:language:onError:"

    ^ self 
        readFrom:aStringOrStream format:formatString language:nil 
        onError:[
            self conversionErrorSignal raiseErrorString:' - Timestamp format error'
        ].
!

readFrom:aStringOrStream format:formatString language:languageString onError:exceptionalValue
    ^ self subclassResponsibility
!

readFrom:aStringOrStream format:formatString onError:exceptionalValue
    "see format description in readFrom:format:language:onError:"

    ^ self readFrom:aStringOrStream format:formatString language:nil onError:exceptionalValue
! !

!AbstractTime class methodsFor:'timing evaluations'!

microsecondsToRun:aBlock
    "evaluate the argument, aBlock; return the number of microseconds it took"

    |startTime endTime|

    startTime := OperatingSystem getMicrosecondTime.
    aBlock value.
    endTime := OperatingSystem getMicrosecondTime.
    ^ endTime - startTime

    "
     Time microsecondsToRun:[1000 factorial]  
    "

    "Modified (comment): / 01-07-2014 / 15:57:13 / az"
!

millisecondsToRun:aBlock
    "evaluate the argument, aBlock; return the number of milliseconds it took"

    |startTime endTime|

    startTime := OperatingSystem getMillisecondTime.
    aBlock value.
    endTime := OperatingSystem getMillisecondTime.
    ^ self milliseconds:endTime since:startTime

    "
     Time millisecondsToRun:[1000 factorial]  
    "

    "Modified: 17.6.1996 / 16:57:37 / cg"
!

secondsToRun:aBlock
    "evaluate the argument, aBlock; return the number of seconds it took"

    |startTime endTime|

    startTime := self secondClock.
    aBlock value.
    endTime := self secondClock.
    ^ endTime - startTime

    "
     Time secondsToRun:[1000 factorial]  
    "
! !

!AbstractTime methodsFor:'Compatibility-ANSI'!

hour12
    "return the hour (1..12)."

    ^ self hours - 1 \\ 12 + 1.

    "
     Time now hour12   
     Time now hour24   
     (Time hours:0 minutes:0 seconds:0) hour24   
     (Time hours:0 minutes:0 seconds:0) hour12   
     (Time hours:1 minutes:0 seconds:0) hour24   
     (Time hours:1 minutes:0 seconds:0) hour12   
     (Time hours:12 minutes:0 seconds:0) hour24 
     (Time hours:12 minutes:0 seconds:0) hour12   
     (Time hours:13 minutes:0 seconds:0) hour24 
     (Time hours:13 minutes:0 seconds:0) hour12   
     (Time hours:23 minutes:0 seconds:0) hour24 
     (Time hours:23 minutes:0 seconds:0) hour12   
     (Time hours:24 minutes:0 seconds:0) hour24 
     (Time hours:24 minutes:0 seconds:0) hour12   
    "
!

hour24
    "return the hour (0..23)."

    ^ self hours

    "
     Time now hour12   
     Time now hour24   
     (Time hours:0 minutes:0 seconds:0) hour24 
     (Time hours:0 minutes:0 seconds:0) hour12 
     (Time hours:1 minutes:0 seconds:0) hour24 
     (Time hours:1 minutes:0 seconds:0) hour12 
     (Time hours:12 minutes:0 seconds:0) hour24 
     (Time hours:12 minutes:0 seconds:0) hour12 
     (Time hours:13 minutes:0 seconds:0) hour24 
     (Time hours:13 minutes:0 seconds:0) hour12 
     (Time hours:23 minutes:0 seconds:0) hour24 
     (Time hours:23 minutes:0 seconds:0) hour12 
     (Time hours:24 minutes:0 seconds:0) hour24 
     (Time hours:24 minutes:0 seconds:0) hour12 
    "
!

meridianAbbreviation
    "am/pm"

    self hours // 12 == 0 ifTrue:[
        ^ 'am'.
    ] ifFalse:[
        (self hours == 12 and:[self minutes == 0 and:[self seconds == 0]]) ifTrue:[
            ^ 'noon'
        ].
        ^ 'pm'.
    ].

    "
     Time now meridianAbbreviation   
     (Time hours:0 minutes:0 seconds:0) meridianAbbreviation  
     (Time hours:11 minutes:59 seconds:59) meridianAbbreviation  
     (Time hours:12 minutes:0 seconds:0) meridianAbbreviation    
     (Time hours:12 minutes:0 seconds:1) meridianAbbreviation  
    "
! !

!AbstractTime methodsFor:'Compatibility-ST80'!

hour
    "return the hour (0..23).
     ST-80 Timestamp compatibility 
     (I'd prefer the name #hours, for Time compatibility)."

    ^ self hours

    "Created: 1.7.1996 / 15:14:50 / cg"
    "Modified: 1.7.1996 / 15:15:32 / cg"
!

minute
    "return the minute (0..59).
     ST-80 Timestamp compatibility 
     (I'd prefer the name #minutes, for Time compatibility)."

    ^ self minutes

    "Created: 1.7.1996 / 15:14:29 / cg"
    "Modified: 1.7.1996 / 15:15:37 / cg"
!

second
    "return the second (0..59).
     ST-80 Timestamp compatibility 
     (I'd prefer the name #seconds, for Time compatibility)."

    ^ self seconds

    "Created: 1.7.1996 / 15:14:19 / cg"
    "Modified: 1.7.1996 / 15:15:49 / cg"
! !

!AbstractTime methodsFor:'abstract'!

hours
    "return the hour of time (0..23)"

    ^ self subclassResponsibility

    "
     Timestamp now hours 
     Time now hours 
    "
!

milliseconds
    "return the milliseconds since the start of the second (0..999)"

    ^ self subclassResponsibility

    "
     Timestamp now milliseconds 
     Time now milliseconds 
    "
!

minutes
    "return the minutes since the start of the hour (0..59)"

    ^ self subclassResponsibility.

    "
     Timestamp now minutes 
     Time now minutes 
    "
!

seconds
    "return the seconds since the start of the minute (0..59)"

    ^ self subclassResponsibility

    "
     Timestamp now seconds. 
     Time now seconds 
    "
! !

!AbstractTime methodsFor:'accessing'!

hourInDay
    "return the hours (0..23)"

    ^ self hours

    "
     Timestamp now hourInDay 
     Time now hourInDay 
    "
!

microseconds
    "return the microseconds within the current second (0..999999)"

    ^ (self milliseconds * 1000) + (self additionalPicoseconds // (1000*1000)).

    "
     Timestamp now microseconds   
     Timestamp nowWithMicroseconds microseconds   
    "
!

minuteInDay
    "return the minutes (0..59)"

    ^ self minutes.

    "
     Timestamp now minuteInDay 
     Time now minuteInDay 
    "
!

nanoseconds
    "return the nanoseconds within the current second (0..999999)"

    ^ (self milliseconds * 1000000) + (self additionalPicoseconds // (1000)).

    "
     Timestamp now nanoseconds   
     Timestamp nowWithMicroseconds nanoseconds   
    "
!

picoseconds
    "return the picoseconds within the current second (0..999999999).
     notice: that is NOT the total number of picoseconds,
     but the fractional part (within the second) only. 
     Use this only for printing.
     Here, fall back and generate something based on the milliseconds"

    ^ (self milliseconds * 1000 * 1000 * 1000) + (self additionalPicoseconds).

    "
     Timestamp now picoseconds
     Timestamp nowWithMicroseconds picoseconds

     (TimeDuration fromPicoseconds:100) picoseconds
     (TimeDuration fromPicoseconds:100000) picoseconds
     (TimeDuration fromPicoseconds:100000) nanoseconds
     (TimeDuration fromPicoseconds:100000000) picoseconds
     (TimeDuration fromPicoseconds:100000000) nanoseconds
     (TimeDuration fromPicoseconds:100000000) microseconds
     (TimeDuration fromPicoseconds:100000000000) picoseconds
     (TimeDuration fromPicoseconds:100000000000) nanoseconds
     (TimeDuration fromPicoseconds:100000000000) microseconds
     (TimeDuration fromPicoseconds:100000000000) milliseconds
    "
!

secondInDay
    "return the seconds (0..59)"

    ^ self seconds

    "
     Timestamp now secondInDay 
     Time now seconds 
    "

    "Created: 22.10.1996 / 09:27:47 / stefan"
!

timeZoneDeltaInMinutes
    "answer the number of minutes between local time and utc time.
     Delta is positive if local time is ahead of utc, negative if behind utc."

    ^ 0
!

timeZoneName
    ^ 'UTC'
!

utcOffset
    ^ 0
! !

!AbstractTime methodsFor:'arithmetic'!

+ aNumberOrTimeDuration
    "Add aNumber (numberOfSeconds) or, if it's a timeDuration, add it's value"

    ^ aNumberOrTimeDuration sumFromTimestamp:self.

    "
     Timestamp now to:(Timestamp now + 30) by:2 do:[:time|
        Transcript showCR:time.
     ].
     Timestamp now to:(Timestamp now + 30) by:2 seconds do:[:time|
        Transcript showCR:time.
     ].
     (Timestamp now + 30 seconds) to:(Timestamp now) by:-2 seconds do:[:time|
        Transcript showCR:time.
     ].
     (Timestamp now + 30 seconds) to:(Timestamp now) by:-2 do:[:time|
        Transcript showCR:time.
     ].

     (Timestamp now + 20)  -  Timestamp now  
     (Timestamp now + 0.5)  -  Timestamp now  
     (Timestamp now + (TimeDuration fromString:'1m 10s'))  -  Timestamp now  
     (Timestamp now + (10 seconds))  -  Timestamp now  
    "

    "Modified (comment): / 13-02-2017 / 19:55:04 / cg"
!

- aTimeOrTimeDurationOrNumberOfSeconds
    "return the delta in seconds between 2 times or subtract a number of seconds."

    "/ Q: for Time - x hours
    "/ should we convert to timestamp i.e. caring for time-wrapping at midnight?
    "/ no - for compatibility, we'll get the time-within-the day again
    "/ if you need the real timestamp, a for it explicitly

    ^ aTimeOrTimeDurationOrNumberOfSeconds differenceFromTimestamp:self.

    "
     Timestamp now - 3600.                          -> 2018-05-09 15:37:57.485
     Timestamp now - 3600 seconds.                  -> 2018-05-09 15:38:04.665
     (Timestamp now addSeconds:10) - Timestamp now  -> 10s

     Time now - 3600.                               -> 03:38:23 PM
     Time now - 3600 seconds.                       -> 03:38:35 PM
     (Time now addSeconds:10) - Time now            -> 10s 
     Time now - 1 hours.                            -> 11:04:02
     Time now - 24 hours.                           -> 11:04:02

     (TimeDuration fromString:'1.5hr') - 3600.                           -> 30m 
     (TimeDuration fromString:'1.5hr') - (TimeDuration fromString:'1hr') -> 30m  

     |t1 t2|

     t1 := Timestamp now.
     Delay waitFor:10 seconds.
     t2 := Timestamp now.
     Transcript showCR:('seconds passed: ' , (t2 - t1) printString).
    "

    "Modified (comment): / 08-05-2019 / 12:49:59 / Claus Gittinger"
!

addDays:numberOfDays
    "return a new instance of myself, numberOfDays afterwards."

    ^ self addSeconds:(numberOfDays * (60 * 60 * 24))

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t addDays:7)
    "
!

addHours:numberOfHours
    "return a new instance of myself, numberOfHours afterwards."

    ^ self addSeconds:(numberOfHours * (60 * 60))

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t addHours:12).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t addHours:12).
    "
!

addMicroseconds:numberOfMicroSeconds
    "return a new instance of myself, numberOfMicroSeconds afterwards."

    ^ self speciesNew 
        setMilliseconds:(self getMilliseconds + (numberOfMicroSeconds // 1000))
        additionalPicoseconds:(self additionalPicoseconds 
                                + ((numberOfMicroSeconds \\ 1000)*1000000) truncated)

    "
     |t1 t2|

     t1 := Timestamp now.
     Transcript showCR:t1.
     t2 := t1 addMicroseconds:1000.
     Transcript showCR:t2.
     t2 := t1 addMicroseconds:1010.
     Transcript showCR:t2.
     Transcript showCR:(t2 - t1).
     self halt.
     Transcript showCR:(t2 - t1) asMicroseconds.
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t addMilliseconds:1000).
    "

    "Modified: / 22-05-2018 / 16:52:37 / Stefan Vogel"
!

addMilliseconds:numberOfMilliSeconds
    "return a new instance of myself, numberOfMilliSeconds afterwards."

    ^ self speciesNew 
        setMilliseconds:(self getMilliseconds + (numberOfMilliSeconds // 1 "this is inlined #truncated"))
        additionalPicoseconds:(self additionalPicoseconds 
                                + ((numberOfMilliSeconds \\ 1)*1000000000) truncated)

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t addMilliseconds:100).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t addMilliseconds:1000).
    "

    "Modified: / 22-05-2018 / 17:03:20 / Stefan Vogel"
!

addMinutes:numberOfMinutes
    "return a new instance of myself, numberOfMinutes afterwards."

    ^ self addSeconds:(numberOfMinutes * 60)

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t addMinutes:60).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t addMinutes:60).
    "
!

addSeconds:numberOfSeconds
    "return a new instance of myself, numberOfSeconds afterwards."

    ^ self addMilliseconds:(numberOfSeconds * 1000) 

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t addSeconds:60).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t addSeconds:60).
    "
!

addTime:timeAmount
    "return a new instance of myself, timeAmount seconds afterwards.
     Provided for ST-80 compatibility.
     WARNING:
        AddTime is a bad name - it does not add a time, but expects
        a numberOfSeconds as argument. 
        Use any of addSeconds/addHours etc. to make things clear"

    timeAmount isNumber ifFalse:[
        ^ self addSeconds:(timeAmount asSeconds).
    ].
    ^ self addSeconds:timeAmount
!

deltaFrom:aTimeOrInteger
    "return the delta as a timeDuration between 2 timeStamps.
     The argument is supposed to be BEFORE the receiver,
        computes self - aTimestamp"

    ^ aTimeOrInteger differenceFromTimestamp:self

    "
        Time now deltaFrom:10 minutes.
        Time now deltaFrom:3600.
        Time now deltaFrom:3600 seconds.

        Timestamp now deltaFrom:10 minutes.
        Timestamp now deltaFrom:3600.
        Timestamp now deltaFrom:3600 seconds.
    "

    "
     |t1 t2|

     t1 := Timestamp now.
     Delay waitForSeconds:0.5.
     t2 := Timestamp now.
     t2 deltaFrom:t1   
    "

    "Created: / 04-10-2007 / 13:34:28 / cg"
    "Modified: / 10-07-2010 / 09:37:01 / cg"
!

roundTo:aTimeDuration
    "round the receiver to the next multiple of a TimeDuration"
    
     ^ self class new setMilliseconds:(self getMilliseconds roundTo:aTimeDuration getMilliseconds)

    "
      (TimeDuration fromMilliseconds:25234) roundTo:2 seconds
      Time now roundTo:10 minutes
      Timestamp now roundTo:10 minutes
    "
!

subtractDays:numberOfDays
    "return a new instance of myself, numberOfDays before."

    ^ self subtractSeconds:(numberOfDays * (60 * 60 * 24))

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t subtractDays:50)
    "
!

subtractHours:numberOfHours
    "return a new instance of myself, numberOfHours before."

    ^ self subtractSeconds:(numberOfHours * (60 * 60))

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t subtractHours:12).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t subtractHours:12).
    "
!

subtractMilliseconds:numberOfMilliSeconds
    "return a new instance of myself, numberOfMilliSeconds before."

    ^ self addMilliseconds:numberOfMilliSeconds negated 

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t subtractMilliseconds:100).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t subtractMilliseconds:1000).
    "
!

subtractMinutes:numberOfMinutes
    "return a new instance of myself, numberOfMinutes before."

    ^ self subtractSeconds:(numberOfMinutes * 60)

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t subtractMinutes:60).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t subtractMinutes:60).
    "
!

subtractSeconds:numberOfSeconds
    "return a new instance of myself, numberOfSeconds before."

    ^ self addMilliseconds:(numberOfSeconds * -1000) 

    "
     |t|

     t := Timestamp now.
     Transcript showCR:t.
     Transcript showCR:(t subtractSeconds:60).
    "

    "
     |t|

     t := Time now.
     Transcript showCR:t.
     Transcript showCR:(t subtractSeconds:60).
    "
!

subtractTime:timeAmount
    "return a new instance of myself, timeAmount seconds before myself.
     Provided for ST-80 compatibility.
     WARNING:
        SubtractTime is a bad name - it does not add a time, but expects
        a numberOfSeconds as argument. 
        Use any of addSeconds/addHours etc. to make things clear"

    timeAmount isNumber ifFalse:[
        ^ self subtractSeconds:(timeAmount asSeconds).
    ].
    ^ self subtractSeconds:timeAmount

!

truncateTo:aTimeDuration
    "truncate the receiver to the next multiple of a TimeDuration"

     ^ self class new setMilliseconds:(self getMilliseconds truncateTo:aTimeDuration getMilliseconds)

    "
      (TimeDuration fromMilliseconds:25234) truncateTo:2 seconds
      Time now truncateTo:10 minutes
      Timestamp now truncateTo:10 minutes
    "
! !

!AbstractTime methodsFor:'comparing'!

< aTimeOrTimeDurationOrNumberOfSeconds
    "return true if the receiver is before the argument"

    |myMilliseconds otherMilliseconds myPicos otherPicos|

    myMilliseconds := self getMilliseconds.
    myPicos := self additionalPicoseconds.

    aTimeOrTimeDurationOrNumberOfSeconds isNumber ifTrue:[
        "backward compatibility for old code, which expects (time1 - time2) to return seconds"    
        otherMilliseconds := (aTimeOrTimeDurationOrNumberOfSeconds * 1000) asInteger.   "integer seconds"
        otherPicos := 0.
    ] ifFalse:[
        otherMilliseconds := aTimeOrTimeDurationOrNumberOfSeconds getMilliseconds.
        otherPicos := aTimeOrTimeDurationOrNumberOfSeconds additionalPicoseconds.
    ].
    ^ (myMilliseconds < otherMilliseconds)
    or:[ myMilliseconds = otherMilliseconds and:[myPicos < otherPicos ]].

    "
        (Timestamp now + 10) - Timestamp now  < 10
        (Timestamp now + 10) - Timestamp now  < 11
    "
!

<= aTimeOrTimeDurationOrNumberOfSeconds
    "return true if the receiver is before or the same time as the argument"

    |myMilliseconds otherMilliseconds myPicos otherPicos|

    myMilliseconds := self getMilliseconds.
    myPicos := self additionalPicoseconds.

    aTimeOrTimeDurationOrNumberOfSeconds isNumber ifTrue:[
        "backward compatibility for old code, which expects (time1 - time2) to return seconds"    
        otherMilliseconds := (aTimeOrTimeDurationOrNumberOfSeconds * 1000) asInteger.   "integer seconds"
        otherPicos := 0.
    ] ifFalse:[
        otherMilliseconds := aTimeOrTimeDurationOrNumberOfSeconds getMilliseconds.
        otherPicos := aTimeOrTimeDurationOrNumberOfSeconds additionalPicoseconds.
    ].
    ^ (myMilliseconds < otherMilliseconds)
    or:[ myMilliseconds = otherMilliseconds and:[myPicos <= otherPicos ]].

    "
        (Timestamp now + 10) - Timestamp now  <= 10
        (Timestamp now + 10) - Timestamp now  <= 11
        (Timestamp now + 10) - Timestamp now  <= 9
    "
!

= aTimeOrTimeDurationOrNumberOfSeconds
    "return true if the receiver is before or the same time as the argument"

    |myMilliseconds otherMilliseconds myPicos otherPicos|

    myMilliseconds := self getMilliseconds.
    myPicos := self additionalPicoseconds.

    aTimeOrTimeDurationOrNumberOfSeconds isNumber ifTrue:[
        "backward compatibility for old code, which expects (time1 - time2) to return seconds"    
        otherMilliseconds := (aTimeOrTimeDurationOrNumberOfSeconds * 1000) asInteger.   "integer seconds"
        otherPicos := 0.
    ] ifFalse:[
        self speciesForCompare = aTimeOrTimeDurationOrNumberOfSeconds speciesForCompare ifFalse:[   
            ^ false        
        ].
        otherMilliseconds := aTimeOrTimeDurationOrNumberOfSeconds getMilliseconds.
        otherPicos := aTimeOrTimeDurationOrNumberOfSeconds additionalPicoseconds.
    ].
    ^ (myMilliseconds = otherMilliseconds) and:[myPicos = otherPicos ].

    "
        (Timestamp now + 10) - Timestamp now  = 10
        (Timestamp now + 10) - Timestamp now  = 9

        (Timestamp now) = (Timestamp now + 10 - 10)
        (Timestamp now) = (UtcTimestamp now)

        (Time now + 10 seconds) - Time now  = 10
        (Time now + 10) - Time now          = 10

        (Time now + 10 seconds) > Time now 
        (Time now + 10)         > Time now      

        (Time now + 10 milliSeconds) > Time now 
        (Time now + 0 milliseconds)  > Time now      
    "
!

> aTimeOrTimeDurationOrNumberOfSeconds
    "return true if the receiver is after the argument"

    |myMilliseconds otherMilliseconds myPicos otherPicos|

    myMilliseconds := self getMilliseconds.
    myPicos := self additionalPicoseconds.

    aTimeOrTimeDurationOrNumberOfSeconds isNumber ifTrue:[
        "backward compatibility for old code, which expects (time1 - time2) to return seconds"    
        otherMilliseconds := (aTimeOrTimeDurationOrNumberOfSeconds * 1000) asInteger.   "integer seconds"
        otherPicos := 0.
    ] ifFalse:[
        otherMilliseconds := aTimeOrTimeDurationOrNumberOfSeconds getMilliseconds.
        otherPicos := aTimeOrTimeDurationOrNumberOfSeconds additionalPicoseconds.
    ].
    ^ (myMilliseconds > otherMilliseconds)
    or:[ myMilliseconds = otherMilliseconds and:[myPicos > otherPicos ]].

    "
        (Timestamp now + 10) - Timestamp now  > 10
        (Timestamp now + 10) - Timestamp now  > 9
    "
!

>= aTimeOrTimeDurationOrNumberOfSeconds
    "return true if the receiver is after the argument or the same"

    |myMilliseconds otherMilliseconds myPicos otherPicos|

    myMilliseconds := self getMilliseconds.
    myPicos := self additionalPicoseconds.

    aTimeOrTimeDurationOrNumberOfSeconds isNumber ifTrue:[
        "backward compatibility for old code, which expects (time1 - time2) to return seconds"    
        otherMilliseconds := (aTimeOrTimeDurationOrNumberOfSeconds * 1000) asInteger.   "integer seconds"
        otherPicos := 0.
    ] ifFalse:[
        otherMilliseconds := aTimeOrTimeDurationOrNumberOfSeconds getMilliseconds.
        otherPicos := aTimeOrTimeDurationOrNumberOfSeconds additionalPicoseconds.
    ].
    ^ (myMilliseconds > otherMilliseconds)
    or:[ myMilliseconds = otherMilliseconds and:[myPicos >= otherPicos ]].

    "
        (Timestamp now + 10) - Timestamp now  >= 11
        (Timestamp now + 10) - Timestamp now  >= 10
        (Timestamp now + 10) - Timestamp now  >= 9
    "
!

hash
    ^ self getMilliseconds
! !

!AbstractTime methodsFor:'converting'!

asAbsoluteTime
    "deprecated, use #asTimestamp"
    <resource:#obsolete>

    self obsoleteMethodWarning:'use #asTimestamp'.
    ^ self asTimestamp
!

asLocalTimestamp
    "represent myself as a timestamp in the local timezone"

    ^ self subclassResponsibility
!

asMilliseconds
    "return the number of milliseconds elapsed since some starttime,
     which is subclass specific (i.e. Time: since midnight; Timestamp: since the epoch).
     Use this only to compute relative millisecond deltas."

    ^ self getMilliseconds

    "
     Time now asMilliseconds
     Timestamp now asMilliseconds
     (TimeDuration days:1) asMilliseconds
     (TimeDuration hours:1) asMilliseconds
    "

    "Created: / 05-09-2011 / 10:40:15 / cg"
!

asSeconds
    "get the seconds since some point of time in the past.
     For Time instances, this is the number of seconds elapsed since midnight;
     For TimeDurations, that is the duration in seconds (truncated);
     For TimeStamps, that is the number of seconds since the epoch"

    ^ self getSeconds

    "
     Timestamp now asSeconds
     Time now asSeconds
     (TimeDuration days:1) asSeconds
    "

    "Modified (comment): / 21-09-2017 / 18:49:31 / cg"
!

asTZTimestamp
    "raise an error: must be redefined in concrete subclass(es)"

    ^ self subclassResponsibility
!

asTime
    "return a Time object from the receiver."

    ^ Time hours:(self hours) minutes:(self minutes) seconds:(self seconds) milliseconds:(self milliseconds)

    "
     (1 seconds + 0.01 seconds) asTime
    "

    "Created: / 17-07-2017 / 13:57:54 / cg"
!

asTimestamp
    "represent myself as a Timestamp"

    ^ self subclassResponsibility
!

asUtcTimestamp
    "represent myself as a timestamp in the local timezone"

    ^ self subclassResponsibility
! !

!AbstractTime methodsFor:'double dispatching'!

differenceFromTimestamp:aTimestamp
    "return the time difference as a timeDuration instance"

    ^ self subclassResponsibility.

    "Modified: / 27-07-2018 / 11:30:27 / Stefan Vogel"
! !


!AbstractTime methodsFor:'printing & storing'!

addBasicPrintBindingsTo:aDictionary language:languageOrNil
    "private print support: add bindings for printing to aDictionary.
     languageOrNil can be #en, #fr, #de or nil for the current language.
     Here only basic bindings are added - no timezone am am/pm stuff,
     which doesn't make sense for TimeDuration.

     bindings:
        %h      hours, 00..23 (i.e. european)  0-padded to length 2
        %m      minutes, 00..59                0-padded to length 2
        %s      seconds, 00..59                0-padded to length 2
        %i      milliseconds, 000..999         0-padded to length 3
        %j      microseconds, 000000..999999   0-padded to length 6

     Timestamp only:
        %(day)   day, 00..31                    0-padded to length 2
        %(month) month, 00..12                  0-padded to length 2
        %(year)  year, 4 digits                 0-padded to length 4

     special:
        %H      24-hours - unpadded
        %M      minutes - unpadded
        %S      seconds - unpadded
        %I      milliseconds, unpadded
        %J      microseconds, unpadded
        %F      subsecond fraction, unpadded with as many post-digits as appropriate
                (i.e. for .1 , .01 , .001 etc.)

        %t      seconds within hour  (unpadded)
        %T      seconds from midNight  (unpadded)

        %(milli) milliseconds unpadded - alias for %I for convenience
        %(milli1) milliseconds, truncated to 1/10th of a second 0..9
        %(milli2) milliseconds, truncated to 1/100th of a second 00..99 0-padded to length 2
        %(milli3) milliseconds, padded to 3 (same as %i for convenience)
        %(milliF1) milliseconds + 1 digits of micros
        %(milliF2) milliseconds + 2 digits of micros
        %(milliF3) milliseconds + 3 digits of micros
        %(milliF4) milliseconds + 4 digits of micros

        %(micro) microseconds unpadded - alias for %J for convenience
        %(micro6) microseconds, padded to 6 (same as %j for convenience)

        %(nano) nanoseconds unpadded
        %(nano9) nanoseconds, padded to 9

        %(pico) picoseconds unpadded
        %(pico12) picoseconds, padded to 12

        %(fract) fraction part - as many as needed alias for %F for convenience

     Timestamp only:
        %(Day)         - day - unpadded
        %(Month)       - month - unpadded
        %(yearOrTime)  - year or time 5 digits    as in unix-ls:
                                                  year if it is not the current year;
                                                  time otherwise
        %(weekDay)      - day in week (1->monday, 2->tuesday, ... ,7->sunday)

        %(dayName)      - full day name
        %(DayName)      - full day name, first character uppercase
        %(DAYNAME)      - full day name, all uppercase

        %(monthName)    - full month name
        %(MonthName)    - full month name, first character uppercase
        %(MONTHNAME)    - full month name, all uppercase

        %(shortDayName) - short (abbreviated) day name
        %(ShortDayName) - short (abbreviated) day name, first character uppercase
        %(SHORTDAYNAME) - short (abbreviated) day name, all uppercase

        %(shortMonthName) - short (abbreviated) month name
        %(ShortMonthName) - short (abbreviated) month name, first character uppercase
        %(SHORTMONTHNAME) - short (abbreviated) month name, all uppercase

        %(nth)          - counting day-in-month (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')
        %(weekDayNth)   - counting day-in-week (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')
        %(weekNth)      - counting week-in-year (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')


     The ISO8601 printString are generated with:

       Year:
          YYYY (eg 1997)
                Date today printStringFormat:'%(year)'
                Timestamp now printStringFormat:'%(year)'

       Year and month:
          YYYY-MM (eg 1997-07)
                Date today printStringFormat:'%(year)-%(month)'
                Timestamp now printStringFormat:'%(year)-%(month)'

       Complete date:
          YYYY-MM-DD (eg 1997-07-16)
                Date today printStringFormat:'%(year)-%(month)-%(day)'
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)'

       Complete date plus hours and minutes:
          YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m%(TZD)'

       Complete date plus hours, minutes and seconds:
          YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m:%s%(TZD)'

       Complete date plus hours, minutes, seconds and a decimal fraction of a second
          YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m:%s.%(milli2)%(TZD)'

    "

    |time hours minutes seconds millis micros nanos picos 
     millisZ3 picosZ9 fract012 fract s t|

    aDictionary at:#'iso8601'         put:[ self printStringIso8601 ].
    aDictionary at:#'iso8601_compact' put:[ self printStringIso8601Compressed ].

    time := self asTime.
    hours := time hours.
    minutes := time minutes.
    seconds := time seconds.
    millis := self milliseconds.
    micros := self microseconds.
    nanos := self nanoseconds.
    picos := self picoseconds.

    aDictionary at:$H put:(s := hours printString).
    aDictionary at:$h put:(s leftPaddedTo:2 with:$0).

    aDictionary at:$M put:(s := minutes printString).
    aDictionary at:$m put:(s leftPaddedTo:2 with:$0).

    aDictionary at:$S put:(s := seconds printString).
    aDictionary at:$s put:(s leftPaddedTo:2 with:$0).

    aDictionary at:$I put:(s := millis printString).
    aDictionary at:#milli put:s.
    aDictionary at:$i put:(millisZ3 := s leftPaddedTo:3 with:$0).
    aDictionary at:#milli3 put:millisZ3.

    aDictionary at:#milli1 put:((millis // 100) printString).
    aDictionary at:#milli2 put:((millis // 10) printStringLeftPaddedTo:2 with:$0).
    aDictionary at:#milliF1 put:((micros asFixedPoint:1) / 1000).
    aDictionary at:#milliF2 put:((micros asFixedPoint:2) / 1000).
    aDictionary at:#milliF3 put:((micros asFixedPoint:3) / 1000).
    aDictionary at:#milliF4 put:((nanos asFixedPoint:4) / 1000000).

    aDictionary at:$J put:(s := micros printString).
    aDictionary at:#micro put:s.
    aDictionary at:$j put:(t := s leftPaddedTo:6 with:$0).
    aDictionary at:#micro6 put:t.
    aDictionary at:#microF1 put:((nanos asFixedPoint:1) / 1000).
    aDictionary at:#microF2 put:((nanos asFixedPoint:2) / 1000).
    aDictionary at:#microF3 put:((nanos asFixedPoint:3) / 1000).
    aDictionary at:#microF4 put:((picos asFixedPoint:4) / 1000000).

    aDictionary at:#nano put:(s := nanos printString).
    aDictionary at:#nano9 put:(s leftPaddedTo:9 with:$0).

    aDictionary at:#pico put:(s := picos printString).
    aDictionary at:#pico12 put:(s leftPaddedTo:12 with:$0).

    picosZ9 := self picoseconds printString leftPaddedTo:9 with:$0.
    fract012 := millisZ3,picosZ9.
    fract := fract012 copyTo:(fract012 findLast:[:ch | ch ~~ $0] ifNone:12).
    aDictionary at:$F put:fract.
    aDictionary at:#fract put:fract.

    aDictionary at:$t put:(seconds * minutes) printString.
    aDictionary at:$T put:(seconds * minutes * hours) printString.

    "
      |dict|
      dict := Dictionary new.
      Timestamp now addBasicPrintBindingsTo:dict language:#en.
      dict inspect
    "
    "
      Timestamp now printStringFormat:'%(milli)'        -- millis only  
      Timestamp now printStringFormat:'%(milli3)'       -- millis padded  
      Timestamp now printStringFormat:'%(micro6)'       -- micros padded  
      Timestamp now printStringFormat:'%(fract)'        -- fraction part - as needed
      Timestamp nowWithMicroseconds printStringFormat:'%(fract)'        -- fraction part - as needed

      (TimeDuration fromMicroseconds:30) printStringFormat:'%(milliF1)ms'
      (TimeDuration fromMicroseconds:30) printStringFormat:'%(milliF2)ms'
      (TimeDuration fromMicroseconds:30) printStringFormat:'%(milliF3)ms'
      (TimeDuration fromMicroseconds:30) printStringFormat:'%(milliF4)ms'
      (TimeDuration fromMicroseconds:3) printStringFormat:'%(milliF4)ms'
      (TimeDuration fromNanoseconds:300) printStringFormat:'%(milliF4)us'

      (TimeDuration fromNanoseconds:30) printStringFormat:'%(microF1)us'
      (TimeDuration fromNanoseconds:30) printStringFormat:'%(microF2)us'
      (TimeDuration fromNanoseconds:30) printStringFormat:'%(microF3)us'
      (TimeDuration fromNanoseconds:30) printStringFormat:'%(microF4)us'
      (TimeDuration fromNanoseconds:3) printStringFormat:'%(microF4)us'
      (TimeDuration fromPicoseconds:300) printStringFormat:'%(microF4)us'
    "

    "Modified (comment): / 14-01-2019 / 18:21:38 / Claus Gittinger"
!

addPrintBindingsTo:aDictionary
    <resource: #obsolete>

    self obsoleteMethodWarning:'use #addPrintBindingsTo:language:'.
    self addPrintBindingsTo:aDictionary language:nil
!

addPrintBindingsTo:aDictionary language:languageOrNil
    "private print support: add bindings for printing to aDictionary.
     languageOrNil can be #en, #fr, #de or nil for the current language.

     bindings:
        %h      hours, 00..23 (i.e. european)  0-padded to length 2
        %u      hours, 00..12 (i.e. us)        0-padded to length 2
        %m      minutes, 00..59                0-padded to length 2
        %s      seconds, 00..59                0-padded to length 2
        %i      milliseconds, 000..999         0-padded to length 3
        %a      am/pm

     Timestamp only:
        %(day)   day, 00..31                    0-padded to length 2
        %(month) month, 00..12                  0-padded to length 2
        %(year)  year, 4 digits                 0-padded to length 4

     special:
        %H      24-hours - unpadded
        %U      12-hours - unpadded
        %M      minutes - unpadded
        %S      seconds - unpadded
        %I      milliseconds, unpadded
        %A      AM/PM   - uppercase

        %t      seconds within hour  (unpadded)
        %T      seconds from midNight  (unpadded)

        %(TZD)  timeZone delta of the receiver from UTC in the format +/-hh:mm

        %(milli1) milliseconds, truncated to 1/10th of a second 0..9
        %(milli2) milliseconds, truncated to 1/100th of a second 00..99 0-padded to length 2
        %(milli3) milliseconds, same as %i for convenience

     Timestamp only:
        %(Day)         - day - unpadded
        %(Month)       - month - unpadded
        %(yearOrTime)  - year or time 5 digits    as in unix-ls:
                                                  year if it is not the current year;
                                                  time otherwise
        %(weekDay)      - day in week (1->monday, 2->tuesday, ... ,7->sunday)

        %(dayName)      - full day name
        %(DayName)      - full day name, first character uppercase
        %(DAYNAME)      - full day name, all uppercase

        %(monthName)    - full month name
        %(MonthName)    - full month name, first character uppercase
        %(MONTHNAME)    - full month name, all uppercase

        %(shortDayName) - short (abbreviated) day name
        %(ShortDayName) - short (abbreviated) day name, first character uppercase
        %(SHORTDAYNAME) - short (abbreviated) day name, all uppercase

        %(shortMonthName) - short (abbreviated) month name
        %(ShortMonthName) - short (abbreviated) month name, first character uppercase
        %(SHORTMONTHNAME) - short (abbreviated) month name, all uppercase

        %(nth)          - counting day-in-month (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')
        %(weekDayNth)   - counting day-in-week (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')
        %(weekNth)      - counting week-in-year (1->'st'; 2->'nd'; 3->'rd'; 4...->'th')

        %(yearRoman)    - year, in roman letters
        %(monthRoman)   - month, in roman letters


     The ISO8601 printString are generated with:

       Year:
          YYYY (eg 1997)
                Date today printStringFormat:'%(year)'
                Timestamp now printStringFormat:'%(year)'

       Year and month:
          YYYY-MM (eg 1997-07)
                Date today printStringFormat:'%(year)-%(month)'
                Timestamp now printStringFormat:'%(year)-%(month)'

       Complete date:
          YYYY-MM-DD (eg 1997-07-16)
                Date today printStringFormat:'%(year)-%(month)-%(day)'
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)'

       Complete date plus hours and minutes:
          YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m%(TZD)'

       Complete date plus hours, minutes and seconds:
          YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m:%s%(TZD)'

       Complete date plus hours, minutes, seconds and a decimal fraction of a second
          YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
                Timestamp now printStringFormat:'%(year)-%(month)-%(day)T%h:%m:%s.%(milli2)%(TZD)'

    "

    |usHours ampm s zone tzDelta|

    self addBasicPrintBindingsTo:aDictionary language:languageOrNil.
    zone := self timeZoneName.
    tzDelta := self timeZoneDeltaInMinutes.

    ampm := self meridianAbbreviation.
    usHours := self hour12 printString.
    aDictionary at:$U put:usHours.
    aDictionary at:$u put:(usHours leftPaddedTo:2 with:$0).


    aDictionary at:$a put:ampm.
    aDictionary at:$A put:ampm asUppercase.
    aDictionary at:$z put:zone.
    aDictionary at:$Z put:zone asUppercase.

    tzDelta == 0 ifTrue:[
        s := 'Z'.
    ] ifFalse:[
        s := tzDelta < 0 ifTrue:[ '-' ] ifFalse:[ '+' ].
        tzDelta := tzDelta abs.
        s := s  , ((tzDelta // 60) printStringLeftPaddedTo:2 with:$0),
            ':' , ((tzDelta \\ 60) printStringLeftPaddedTo:2 with:$0).
    ].
    aDictionary at:#TZD put:s


    "
      |dict|
      dict := Dictionary new.
      Timestamp now addPrintBindingsTo:dict language:#en.
      dict inspect
    "
!

printIso8601CompressedOn:aStream
    self subclassResponsibility

    "
     Timestamp now printStringIso8601       -> '2018-05-09T12:15:53.279'
     Time now printStringIso8601            -> 'T16:27:08'
     Date today printStringIso8601          -> '2018-05-09'

     Timestamp now printStringIso8601Compressed       -> '20180525T120822.699'
     Time now printStringIso8601Compressed            -> 'T120837'
     Date today printStringIso8601Compressed          -> '20180525'
    "

    "Created: / 25-05-2018 / 12:08:07 / Claus Gittinger"
!

printIso8601FormatOn:aStream
    self subclassResponsibility

    "
     Timestamp now printStringIso8601       -> '2018-05-09T12:15:53.279+02'
     Time now printStringIso8601            -> 'T16:27:08'
     Date today printStringIso8601          -> '2018-05-09'
    "
!

printOn:aStream format:aFormatString
    "print using a format string;
     See #addPrintBindingsTo:language: for allowed format strings"

    self printOn:aStream format:aFormatString language:nil.

    "
     Timestamp now printOn:Transcript format:'%h:%m:%s'   . Transcript cr.      
     Time now printOn:Transcript format:'%h:%m:%s'   . Transcript cr.      
     Time now printOn:Transcript format:'%H:%m:%s'   . Transcript cr.      
     Time now printOn:Transcript format:'%u:%m:%s %a'. Transcript cr.   
     Time now printOn:Transcript format:'%h:%m'      . Transcript cr. 
     Time now printOn:Transcript format:'%H:%m %A'   . Transcript cr.
     Time now printOn:Transcript format:'minutes:%M seconds:%S'. Transcript cr.
    "

    "Modified: 22.2.1996 / 16:58:30 / cg"
!

printOn:aStream format:aFormatString language:languageString
    "print using a format string;
     See #addPrintBindingsTo:language: for allowed format strings"

    |dict|

"/ is this a good idea?    
"/    aFormatString = '%h:%m:%s' ifTrue:[
"/        "/ this is so common...
"/        aStream nextPutAll:(self hours printStringLeftPaddedTo:2 with:$0).
"/        aStream nextPutAll:':'.
"/        aStream nextPutAll:(self minutes printStringLeftPaddedTo:2 with:$0).
"/        aStream nextPutAll:':'.
"/        aStream nextPutAll:(self seconds printStringLeftPaddedTo:2 with:$0).
"/        ^ self 
"/    ].
    
    dict := IdentityDictionary new.
    self addPrintBindingsTo:dict language:languageString.

    aFormatString expandPlaceholdersWith:dict on:aStream

    "
     Timestamp now printOn:Transcript format:'%h:%m:%s'   . Transcript cr.      
     Time now printOn:Transcript format:'%h:%m:%s'   . Transcript cr.      
     Time now printOn:Transcript format:'%H:%m:%s'   . Transcript cr.      
     Time now printOn:Transcript format:'%u:%m:%s %a'. Transcript cr.   
     Time now printOn:Transcript format:'%h:%m'      . Transcript cr. 
     Time now printOn:Transcript format:'%H:%m %A'   . Transcript cr.
     Time now printOn:Transcript format:'minutes:%M seconds:%S'. Transcript cr.
    "

    "Modified: / 22-02-1996 / 16:58:30 / cg"
    "Modified: / 14-01-2019 / 18:22:12 / Claus Gittinger"
!

printStringFormat:aFormatString
    "print using a format string.
     See #addPrintBindingsTo:language: for allowed format strings"

    ^ self printStringFormat:aFormatString language:nil.

    "
     Timestamp now printStringFormat:'%U:%m:%s %a'        
     Timestamp now printStringFormat:'%u:%m:%s %a'        
     Time now printStringFormat:'%U:%m:%s %a'   
     Time now printStringFormat:'%u:%m:%s %a'   

     Time now printStringFormat:'%h:%m:%s'      
     Time now printStringFormat:'%H:%m:%s'      
     Time now printStringFormat:'%H:%m:%s.%i'           
     Timestamp now printStringFormat:'%H:%m:%s.%i'   
     Timestamp now printStringFormat:'%H:%m:%s.%(milli1)'   
     Timestamp now printStringFormat:'%H:%m:%s.%(milli2)'     
     Timestamp now printStringFormat:'%(day)-%(month)-%(year) :%m:%s'       
     Timestamp now printStringFormat:'%(day)-%(monthName)-%(year) :%m:%s'       
     Time now printStringFormat:'%u:%m:%s %a'   
     Time now printStringFormat:'%h:%m'         
     Time now printStringFormat:'%h:%m'         
     Time now printStringFormat:'%H:%m %A'     
     Time now printStringFormat:'%m minutes after %U %a'     
     Time now printStringFormat:'%t seconds after %U %a'     
     Time now printStringFormat:'%T seconds from midNight'     
    "

    "Modified: 22.2.1996 / 16:58:30 / cg"
!

printStringFormat:aFormatString language:languageString
    "print using a format string.
     See #addPrintBindingsTo:language: for allowed format strings"

    |s|

    s := CharacterWriteStream on:(String new:20).
    self printOn:s format:aFormatString language:languageString.
    ^ s contents.

    "
     Timestamp now printStringFormat:'%U:%m:%s %a  
     Time now printStringFormat:'%U:%m:%s %a'   

     Time now printStringFormat:'%h:%m:%s'      
     Time now printStringFormat:'%H:%m:%s'      
     Time now printStringFormat:'%H:%m:%s.%i'           
     Timestamp now printStringFormat:'%H:%m:%s.%i'   
     Timestamp now printStringFormat:'%H:%m:%s.%(milli1)'   
     Timestamp now printStringFormat:'%H:%m:%s.%(milli2)'     
     Timestamp now printStringFormat:'%(day)-%(month)-%(year) :%m:%s'       
     Timestamp now printStringFormat:'%(day)-%(monthName)-%(year) :%m:%s' language:#en      
     Timestamp now printStringFormat:'%(day)-%(monthName)-%(year) :%m:%s' language:#de      
     Timestamp now printStringFormat:'%(day)-%(monthName)-%(year) :%m:%s' language:#fr      
     Time now printStringFormat:'%u:%m:%s %a'   
     Time now printStringFormat:'%h:%m'         
     Time now printStringFormat:'%h:%m'         
     Time now printStringFormat:'%H:%m %A'     
     Time now printStringFormat:'%m minutes after %U %a'     
     Time now printStringFormat:'%t seconds after %U %a'     
     Time now printStringFormat:'%T seconds from midNight'     
    "

    "Modified: 22.2.1996 / 16:58:30 / cg"
!

printStringIso8601
    "return the Iso8601 representation of the receiver with local timezon information.
     This format looks like:
        1999-01-01T24:00:00
     or, for zero hr:min:sec,
        1999-01-01
     Of course, a 24 hour clock is used."

    ^ String streamContents:[:s | self printIso8601FormatOn:s]

    "
     Timestamp now printStringIso8601
    "

    "Modified (comment): / 25-05-2018 / 11:58:37 / Claus Gittinger"
!

printStringIso8601Compressed
    "return the Iso8601 representation of the receiver with local timezon information.
     This format looks like:
        1999-01-01T24:00:00
     or, for zero hr:min:sec,
        1999-01-01
     Of course, a 24 hour clock is used."

    ^ String streamContents:[:s | self printIso8601CompressedOn:s]

    "
     Timestamp now printStringIso8601Compressed
    "

    "Created: / 25-05-2018 / 11:58:29 / Claus Gittinger"
!

printStringIso8601Format
    "would like to make it obsolete, but it is:"
    <resource: #EXPECCO_API>
    "<resource: #obsolete>"

    "return the Iso8601 representation of the receiver with local timezon information.
     This format looks like:
        1999-01-01T24:00:00
     or, for zero hr:min:sec,
        1999-01-01
     Of course, a 24 hour clock is used."

    "/ self obsoleteMethodWarning:'use printStringIso8601'.
    ^ self printStringIso8601

    "
     Timestamp now printStringIso8601Format
    "
! !

!AbstractTime methodsFor:'private'!

additionalPicoseconds
    "return the additional picoseconds within the current second (0..999999999).
     Here, assume that we have none"

    ^ 0.

    "
     Timestamp now picoseconds
    "
!

fromOSTime:osTime
    "set my time, from operatingSystems time parts"

    ^ self subclassResponsibility

    "Modified: 1.7.1996 / 15:09:44 / cg"
!

fromOSTimeWithMilliseconds:anUninterpretedOSTime
    "strictly private: set the milliseconds from an OS time (since the epoch)"

    self subclassResponsibility
!

getMicroseconds
    "get the milliseconds since some point of time in the past.    
     Since I am abstract (not knowing how the time is actually
     represented), this must be done by a concrete class.
     Also be aware that the returned value is concrete-class specific;
     Time returns the micros since midnight, Timestamp since the epoch.
     Use this only to compute relative time deltas.
     Here is a fallback, which only returns milliseocnd precision values"

    ^ self getMilliseconds * 1000
!

getMilliseconds
    "get the milliseconds since some point of time in the past.
     Since I am abstract (not knowing how the time is actually
     represented), this must be done by a concrete class.
     Also be aware that the returned value is concrete-class specific;
     Time returns the millis since midnight, Timestamp since the epoch.
     Use this only to compute relative time deltas."

    ^ self subclassResponsibility

    "Created: 1.7.1996 / 14:16:49 / cg"
!

getSeconds
    "get the (truncated) seconds since some point of time in the past.
     Since I am abstract (not knowing how the time is actually
     represented), this must be done by a concrete class."

    ^ self subclassResponsibility

    "Modified (comment): / 21-09-2017 / 18:50:30 / cg"
!

setMilliseconds:millis
    "set the milliseconds since some point of time in the past.
     Since I am abstract (not knowing how the time is actually
     represented), this must be done by a concrete class."

    ^ self subclassResponsibility

    "Created: 1.7.1996 / 14:17:00 / cg"
!

setMilliseconds:arg1 additionalPicoseconds:arg2
    "raise an error: must be redefined in concrete subclass(es)"

    ^ self subclassResponsibility
!

setSeconds:secs
    "set the seconds since some point of time in the past.
     Since I am abstract (not knowing how the time is actually
     represented), this must be done by a concrete class."

    ^ self subclassResponsibility
!

speciesNew
    ^ self species basicNew
! !

!AbstractTime class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !