!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: time.gs,v 1.13 2008-01-09 22:50:20 stever Exp $
!
! Superclass Hierarchy:
!   Time, Magnitude, Object.
!
!=========================================================================
expectvalue %String
run
^ Magnitude _newKernelSubclass: 'Time'
        instVarNames: #( 'milliseconds' )
        classVars: #()
        classInstVars: #()
        poolDictionaries: #[]
        inDictionary: Globals
        constraints: #[ #[ #milliseconds, SmallInteger] ]
        instancesInvariant: false
        isModifiable: false
        reservedOop: 789
%

removeallmethods Time
removeallclassmethods Time

category: 'For Documentation Installation only'
classmethod: Time
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'An instance of Time describes a time of day with one-millisecond resolution.
 The class Time also provides methods for examining the system clock and for
 measuring the performance of a block.

 The internal representation of a Time is based on local time.

 You can convert a Time to a String (using Formatting instance methods), and
 you can convert a String to a Time (using Instance Creation class methods).
 Such conversions require a specification to describe the format of the String.
 Some methods provide for the default format, HH:MM:SS, which uses a 24-hour
 clock.

 Explicit string-formatting specifications take the form of an Array, described
 in the following table.  A specification is incorrect if it is missing an
 element or if an element value is not one of the acceptable values listed in
 the table.

 String-formatting Specification Array for Time.

 Element   Acceptable Value     Explanation
 
 1st       A Character literal  Separates hours, minutes, and seconds.
           (such as $: or $.)

 2nd       true                 Include seconds.

 2nd       false                Do not include seconds.

 3rd       true                 Time is expressed in 12-hour format, with
                                am or pm (such as 1:30:55 pm).  The space is
                                required preceding the am or pm indicator.

 3rd       false                Time is expressed in 24-hour format
                                (such as 13:30:55).' .
doc documentClassWith: txt.

txt := (GsDocText new) details:
'The number of milliseconds since midnight, local time.'.
doc documentInstVar: #milliseconds with: txt.

self description: doc.
%

category: 'Private'
method: Time
_initialize: anInteger

"Private. Initialize and make the receiver invariant. Returns the receiver."

milliseconds := anInteger \\ 86400000.
self immediateInvariant
%

category: 'Instance Creation'
classmethod: Time
new

"Disallowed.  To create a new Time, use another instance creation method."

self shouldNotImplement: #new
%

category: 'Instance Creation'
classmethod: Time
migrateNew

"Override default migrateNew behavior with #_basicNew."

^ self _basicNew
%

category: 'Instance Creation'
classmethod: Time
new: anInteger

"Disallowed.  To create a new Time, use another instance creation method."

self shouldNotImplement: #new:
%

category: 'Instance Creation'
classmethod: Time
fromSeconds: anInteger

"Creates and returns an instance of the receiver from the specified value,
 which expresses local time."

^ super new _initialize: (anInteger * 1000).
%

category: 'Instance Creation'
classmethod: Time
fromMilliseconds: anInteger

"Creates and returns an instance of the receiver from the specified value,
 which expresses local time."

^ super new _initialize: anInteger.
%

category: 'Instance Creation'
classmethod: Time
fromSecondsGmt: anInteger

"Creates and returns an instance of the receiver from the specified value,
 which expresses Greenwich Mean Time."

^ super new _initialize: ((anInteger - self gmtOffsetSeconds) * 1000).
%

category: 'Instance Creation'
classmethod: Time
fromString: aString

"Creates and returns an instance of the receiver from the String aString.
 The String expresses local time in the default format (HH:MM:SS).
 Generates an error if the String does not conform to the format."

^ self fromString: aString usingFormat: #($: true false).
%

category: 'Instance Creation'
classmethod: Time
fromStringGmt: aString

"Creates and returns an instance of the receiver from the String aString.
 The String expresses Greenwich Mean Time in the default format (HH:MM:SS).
 Generates an error if the String does not conform to the format."

^ self fromStringGmt: aString usingFormat: #($: true false).
%

category: 'Instance Creation'
classmethod: Time
fromStream: aStream

"Creates and returns an instance of the receiver by reading a String from
 aStream.  The String expresses local time in the default format (HH:MM:SS).
 Generates an error if the String does not conform to the format."

^ self fromStream: aStream usingFormat: #($: true false).
%

category: 'Instance Creation'
classmethod: Time
fromStreamGmt: aStream

"Creates and returns an instance of the receiver by reading a String from
 aStream.  The String expresses Greenwich Mean Time in the default format
 (HH:MM:SS).  Generates an error if the String does not conform to the format."

^ self fromStreamGmt: aStream usingFormat: #($: true false).
%

category: 'Instance Creation'
classmethod: Time
fromString: aString usingFormat: anArray

"Creates and returns an instance of the receiver from the String aString.
 The String expresses local time in the format specified by anArray.
 The expression is terminated either by a space Character or by the end of the
 String.  Generates an error if the String does not conform to the format,
 or if anArray contains an incorrect formatting specification.

 See the class documentation of Time for a complete description of the
 String-formatting specification Array."

| s result |

s := ReadStream on: aString.
result := self fromStream: s usingFormat: anArray.
[ s atEnd ]
whileFalse:
  [ (s next isEquivalent:  $ )
    ifFalse:
      [ self _errIncorrectFormat: aString ]
  ].
^ result
%

category: 'Instance Creation'
classmethod: Time
fromStringGmt: aString usingFormat: anArray

"Creates and returns an instance of the receiver from the String aString.
 The String expresses Greenwich Mean Time in the format specified by anArray.
 The expression is terminated either by a space Character or by the end of the
 String.  Generates an error if the String does not conform to the format,
 or if anArray contains an incorrect formatting specification.

 See the class documentation of Time for a complete description of the
 String-formatting specification Array."

| s result |

s := ReadStream on: aString.
result := self fromStreamGmt: s usingFormat: anArray.
[ s atEnd ]
whileFalse:
  [ (s next isEquivalent:  $ )
    ifFalse:
      [ self _errIncorrectFormat: aString ]
  ].
^ result
%

category: 'Instance Creation'
classmethod: Time
fromStreamGmt: aStream usingFormat: anArray

"Creates and returns an instance of the receiver by reading a String from
 aStream.  The String expresses local time in the format specified by
 anArray.  The expression is terminated either by a space Character or by the
 end of the Stream.  Generates an error if the String does not conform to the
 format, or if anArray contains an incorrect formatting specification.

 See the class documentation of Time for a complete description of the
 String-formatting specification Array."

^ (self fromStream: aStream usingFormat: anArray ) 
        subtractSeconds: self gmtOffsetSeconds.
%

category: 'Instance Creation'
classmethod: Time
fromStream: aStream usingFormat: anArray

"Creates and returns an instance of the receiver by reading a String from
 aStream.  The String expresses Greenwich Mean Time in the format specified by
 anArray.  The expression is terminated either by a space Character or by the
 end of the Stream.  Generates an error if the String does not conform to the
 format, or if anArray contains an incorrect formatting specification.

 See the class documentation of Time for a complete description of the
 String-formatting specification Array."

| hourInt minInt secInt timeDelim ampm ampmPresent secondsPresent parseField 
  totalSeconds |

"This block returns a string up from the input stream up to the specified
 delimiter.  If also allows an end-of-file if that parameter is set true.
 It then skips over the delimiter if it is found.
"
parseField := [ :delim :allowEof | | str |
                str := aStream contents class new.
                [ ((aStream peek isEquivalent: delim) not) & (aStream atEnd not) ]
                whileTrue:
                  [ str add: aStream next ].
                (aStream atEnd)
                ifTrue:
                  [ allowEof
                    ifFalse:
                      [ self _error: #rtErrBadFormat args: #[aStream] ]
                  ]
                ifFalse:
                  [ aStream next "skip over delimiter" ].
                str
             ].

self _checkReadStream: aStream forClass: CharacterCollection.

timeDelim := anArray at: 1.
secondsPresent := anArray at: 2.
ampmPresent := anArray at: 3.
hourInt := Integer fromCompleteString: (parseField value: timeDelim value: false).
minInt := Integer fromCompleteString: 
                  (parseField value: (secondsPresent ifTrue: [timeDelim] ifFalse: [$ ])
			      value: (secondsPresent not & ampmPresent not)).
secondsPresent 
  ifTrue: [
    secInt := Integer fromCompleteString: (parseField value: $  value: ampmPresent not)]
  ifFalse:
    [ secInt := 0 ].

ampmPresent 
  ifTrue: [
    hourInt < 0 ifTrue: [ self _error: #rtErrBadFormat args: #[aStream]].
    hourInt > 12 ifTrue: [ self _error: #rtErrBadFormat args: #[aStream]].
    ampm := String new.
    ampm add: (aStream next); add: aStream next.
    (ampm isEquivalent: 'PM') 
      ifTrue: [
        hourInt := hourInt + 12.
        hourInt == 24 ifTrue: [ hourInt := 12].
        ]
      ifFalse: [
        (ampm isEquivalent: 'AM') ifFalse: [
        self _error: #rtErrBadFormat args: #[aStream] ].
        hourInt == 12 ifTrue: [ hourInt := 0].
        ].
    ].

totalSeconds := (hourInt * 3600) + (minInt * 60) + secInt.
^ self fromSeconds: totalSeconds .
%

category: 'Instance Creation'
classmethod: Time
now

"Creates and returns an instance of the receiver from the system clock on the
 machine that is running the Gem process, which is assumed to represent the
 current time of day."

<primitive: 317>
^ self _primitiveFailed: #now
%

category: 'Private'
classmethod: Time
dateTimeClass

"Returns the DateTime class used internally in this class."

^ DateTime.
%

category: 'Adjusting'
classmethod: Time
gmtOffsetSeconds

"Returns a SmallInteger that gives the offset in seconds of the local time zone,
 its difference with respect to Greenwich Mean Time.

 A positive number corresponds to west of Greenwich, a negative number to east
 of Greenwich.  For example, the offset for the Pacific Standard Time zone is
 28800."

^ self dateTimeClass now _localOffset negated.
%

! deleted gmtOffsetSeconds: , primitive had no effect  ; part of fix 36453


category: 'Measuring'
classMethod: Time
millisecondClockValue

"Returns a SmallInteger representing the current time in milliseconds.
 The result is a SmallInteger equivalent to

    (System _timeGmtFloat * 1000) asInteger 

 The result is computed locally in the session process, using the offset
 from the Gem's time that was cached in the session at login.

 Gs64 v2.2, changed to no longer rollover to zero after 524287999 "

<primitive: 651>
^ self _primitiveFailed: #millisecondClockValue
%

category: 'Measuring'
classMethod: Time
millisecondsElapsedTime: aBlock

"Returns the elapsed time in milliseconds aBlock takes to return its value.
 The argument aBlock must be a zero-argument block."

^ ((self secondsElapsedTime: aBlock) * 1000) asInteger
%

category: 'Measuring'
classMethod: Time
secondsElapsedTime: aBlock

"Returns the elapsed time in seconds aBlock takes to return its value.
 The argument aBlock must be a zero-argument block.
 The result is a Float with microsecond resolution "

| startTime endTime systm |

systm := System .
startTime :=  systm _timeGmtFloat.
aBlock value.
endTime := systm _timeGmtFloat.

^ endTime - startTime
%

category: 'Accessing'
method: Time
at: anIndex put: aValue

"Disallowed.  You may not change the value of a Time."

self shouldNotImplement: #at:put:
%

category: 'Accessing'
method: Time
hoursGmt

"Returns a SmallInteger (between zero and 23 inclusive) that gives the number of
 hours represented by the receiver since midnight, Greenwich Mean Time."

^ (milliseconds // 1000 + self class gmtOffsetSeconds \\ 86400) // 3600
%

category: 'Accessing'
method: Time
hours

"Returns a SmallInteger (between zero and 23 inclusive) that gives the number of
 hours represented by the receiver since midnight, local time."

^ milliseconds // 3600000
%

category: 'Accessing'
method: Time
minutesGmt

"Returns a SmallInteger (between zero and 59 inclusive) that gives the number of
 minutes represented by the receiver since the previous hour, Greenwich Mean 
 Time."

^ ((milliseconds // 1000 + self class gmtOffsetSeconds) \\ 3600) // 60
%

category: 'Accessing'
method: Time
minutes

"Returns a SmallInteger (between zero and 59 inclusive) that gives the number of
 minutes represented by the receiver since the previous hour, local time."

^ (milliseconds \\ 3600000) // 60000
%

category: 'Accessing'
method: Time
secondsGmt

"Returns a SmallInteger (between zero and 59 inclusive) that gives the number of
 seconds represented by the receiver since the previous hour,  Greenwich Mean 
 Time."

^ (milliseconds // 1000 + self class gmtOffsetSeconds) \\ 60
%

category: 'Accessing'
method: Time
seconds

"Returns a SmallInteger (between zero and 59 inclusive) that gives the number of
 seconds represented by the receiver since the previous hour, local time."

^ milliseconds // 1000 \\ 60
%

category: 'Formatting'
method: Time
printOn: aStream

"Puts a displayable representation of the receiver, expressed in
 local time, on aStream."

aStream nextPutAll: self asString .
%

category: 'Formatting'
method: Time
asStringGmt

"Returns a String that expresses the receiver in Greenwich Mean Time
 in the default format (HH:MM:SS)."

^ (self addSeconds: self class gmtOffsetSeconds) asString
%

category: 'Formatting'
method: Time
asString

"Returns a String that expresses the receiver in local time
 in the default format (HH:MM:SS)."

| result |

"Check for seconds being nil, for graceful printing during error handling."
milliseconds == nil ifTrue:[ ^ '(nil):(nil):(nil)' ].

result := (milliseconds // 3600000) _digitsAsString.
result addAll: $:; addAll: ((milliseconds \\ 3600000 // 60000) _digitsAsString).
result addAll: $:; addAll: ((milliseconds // 1000 \\ 60) _digitsAsString).

^ result.
%

category: 'Formatting'
method: Time
asStringGmtUsingFormat: anArray

"Returns a String that expresses the receiver in Greenwich Mean Time
 in the format defined by anArray.  Generates an error if anArray
 contains an incorrect formatting specification.

 See the class documentation of Time for a complete description of the
 String-formatting specification Array."

^ (self addSeconds: self class gmtOffsetSeconds) asStringUsingFormat: anArray.
%

! fix bug 13181
category: 'Formatting'
method: Time
asStringUsingFormat: anArray

"Returns a String that expresses the receiver in Greenwich Mean Time
 in the format defined by anArray.  Generates an error if anArray
 contains an incorrect formatting specification.

 See the class documentation of Time for a complete description of the
 String-formatting specification Array."

| timeSeparator  hourInt hour min sec aString |
timeSeparator := anArray at: 1.
hourInt := milliseconds // 3600000.
hour := hourInt  _digitsAsString.
min := (milliseconds \\ 3600000 // 60000) _digitsAsString.
sec := (milliseconds // 1000 \\ 60) _digitsAsString.

aString := String new.
(anArray at: 3) 
  ifTrue: [ "12-hour format"
    (hourInt > 12) 
      ifTrue: [
        aString addAll: (hourInt - 12) _digitsAsString;
        addAll: timeSeparator;
        addAll: min.

        (anArray at: 2) 
          ifTrue: [ aString addAll: timeSeparator; addAll: sec ].
        ]
      ifFalse: [
        aString addAll: (hourInt == 0 ifTrue: ['12'] ifFalse: [hour]);
        addAll: timeSeparator;
        addAll: min.

        (anArray at: 2) 
          ifTrue: [ aString addAll: timeSeparator; addAll: sec].
        ].

    aString addAll: (hourInt >= 12 ifTrue: [' PM'] ifFalse: [' AM']).
    ]
  ifFalse: [
    aString addAll: hour;
            addAll: timeSeparator;
            addAll: min.

    (anArray at: 2) 
      ifTrue: [ aString addAll: timeSeparator; addAll: sec].
    ].

^ aString
%

category: 'Arithmetic'
method: Time
addSeconds: anInteger

"Returns a Time that describes a time of day anInteger seconds
 later than that of the receiver."

^ self class fromMilliseconds: (milliseconds + (anInteger * 1000)).
%

category: 'Arithmetic'
method: Time
subtractSeconds: anInteger

"Returns a Time that describes a time of day anInteger seconds
 earlier than that of the receiver."

^ self addSeconds: (anInteger negated).
%

category: 'Arithmetic'
method: Time
addTime: aTime

"Returns a Time that describes a time of day that is aTime
 later than that of the receiver. aTime represents the duration since 
 midnight local time."

^ self class fromMilliseconds: (milliseconds + aTime asMilliseconds).
%

category: 'Arithmetic'
method: Time
subtractTime: aTime

"Returns a Time that describes a time of day that is aTime
 earlier than that of the receiver. aTime represents the duration 
 since midnight local time."

^ self class fromMilliseconds: (milliseconds - aTime asMilliseconds).
%

category: 'Comparing'
method: Time
< aTime

"Returns true if the receiver represents a time of day before that of the
 argument, and false if it doesn't.  Generates an error if the argument is not
 a Time."

^ milliseconds < aTime asMilliseconds.
%

category: 'Comparing'
method: Time
= aTime

"Returns true if the receiver represents the same time of day as that of the
 argument, and false if it doesn't."

self == aTime ifTrue:[ ^ true ].
(aTime isKindOf: self class) ifFalse: [ ^false ].
^ (milliseconds == aTime asMilliseconds ).
%

category: 'Comparing'
method: Time
hash

"Returns an Integer hash code for the receiver."

^ self asSeconds hash.
%

category: 'Comparing'
method: Time
> aTime

"Returns true if the receiver represents a time of day after that of the
 argument, and false if it doesn't.  Generates an error if the argument is not
 a Time."

^ milliseconds > aTime asMilliseconds
%

category: 'Backward Compatibility'
method: Time
timeAsSeconds

"Returns a SmallInteger (between zero and 86399 inclusive) that gives 
 the number of seconds represented by the receiver since midnight, local time."

^ milliseconds // 1000.
%

category: 'Backward Compatibility'
method: Time
timeAsSecondsGmt

"Returns a SmallInteger (between zero and 86399 inclusive) that gives 
 the number of seconds represented by the receiver since midnight, Greenwich
 Mean Time."

^ self asSecondsGmt.
%

category: 'Converting'
method: Time
asSeconds

"Returns an Integer that represents the receiver in units of seconds since
 midnight, local time."

^ milliseconds // 1000.
%

category: 'Converting'
method: Time
asMilliseconds

"Returns an Integer that represents the receiver in units of milliseconds since
 midnight, local time."

^ milliseconds.
%

category: 'Converting'
method: Time
asSecondsGmt

"Returns an Integer that represents the receiver in units of seconds since
 midnight, Greenwich Mean Time."

^ ((milliseconds // 1000) + self class gmtOffsetSeconds) \\ 86400
%

! fixed 36155
category: 'Converting'
method: Time
asMillisecondsGmt

"Returns an Integer that represents the receiver in units of milliseconds since
 midnight, Greenwich Mean Time."

^ (milliseconds + ((self class gmtOffsetSeconds) * 1000)) \\ 86400000 .
%

category: 'Storing and Loading'
classmethod: Time
loadFrom: passiveObj

"Creates and returns an active instance of the receiver from the passive form
 of the object, which expresses itself in Greenwich Mean Time."

| inst |

passiveObj version >= 510
  ifTrue: [ inst := self fromMilliseconds: passiveObj readObject ]
  ifFalse: [ inst := self fromSecondsGmt: passiveObj readObject ].
passiveObj hasRead: inst.
^inst.
%

category: 'Storing and Loading'
method: Time
writeTo: passiveObj

"Writes the passive form of the receiver into passiveObj, expressed in
 Greenwich Mean Time."

passiveObj writeClass: self class.
milliseconds writeTo: passiveObj.
passiveObj space
%

category: 'New Indexing Comparison'
method: Time
_classSortOrdinal

^ 40
%
