!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: datetim.gs,v 1.18 2008-01-09 22:50:10 stever Exp $
!
! Superclass Hierarchy:
!   DateTime, Magnitude, Object.
!
!=========================================================================

removeallmethods DateTime
removeallclassmethods DateTime

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

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

txt := (GsDocText new) details:
'An instance of DateTime describes a moment in time (with one second resolution)
 on a date after December 31, 1900. All instances of DateTime are invariant.

 The internal representation of a DateTime is based on Greenwich Mean Time.
 However, many methods express time in the local time zone.  ("Local" time is
 local to your Gem process.)  These methods automatically convert between
 time zones, but the internal representation remains in Greenwich Mean Time.
 Hence, you can interact with DateTime methods in a natural way, but DateTime
 objects can be safely compared to each other no matter what time zone is used
 to express them.

 You can convert a DateTime to a String (using Formatting instance methods), and
 you can convert a String to a DateTime (using Instance Creation class methods).
 Such conversions require a specification to describe the format of the String.
 Some methods provide for the default format, DD/MM/YYYY HH:MM:SS, which
 expresses the day and month (in that order) as digits and 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 DateTime.

 Element   Acceptable Value       Explanation

 1st,      Integers 1, 2,         Determines the position of the day (1),
 2nd, and  and 3, in any          month (2), and year (3).
 3rd       order

 4th       A Character literal    Separates year, month, and day.
           (such as a space, $ )

 5th       Integer                Determines the month format to be a number (1),
                                  three-letter abbreviation (2), or the entire
                                  name (3).

 6th       Integer                Determines the year format to be the entire
                                  number (1), or only the last two digits (2).

 7th       A Character literal    Separates hours, minutes, and seconds.
           (such as $: or $.)

 8th       true                   Include the time of day.

 8th       false                  Do not include the time of day.  Ignore elements
                                  7, 9, and 10.  Elements 9 and 10 are optional
                                  in the specification.

 9th       true                   Include seconds.

 9th       false                  Do not include seconds.

 10th      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.

 10th      false                  Time is expressed in 24-hour format
                                  (such as 13:30:55).

 11th      true                   Include the time zone information.

 12th      true                   Use the time zone information of the time zone
			          in which the DateTime was created rather than
                                  the local time zone.'.

doc documentClassWith: txt.

txt := (GsDocText new) details:
'A SmallInteger that represents the year. Years beyond one million are not
 allowed.' .
doc documentInstVar: #year with: txt.

txt := (GsDocText new) details:
'A SmallInteger between 1 and 366 inclusive that represents the day of the
 year.'.
doc documentInstVar: #dayOfYear with: txt.

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

txt := (GsDocText new) details:
'The time zone in which the DateTime was created.'.
doc documentInstVar: #timeZone with: txt.

txt := (GsDocText new) details:
'A SymbolDictionary.  Each key is a Symbol representing one of the native
 languages supported by GemStone, and each value is an Array of Strings, the
 names of the days of the week in the corresponding language.'.
doc documentClassVar: #WeekDayNames with: txt.

txt := (GsDocText new) details:
'A SymbolDictionary.  Each key is a Symbol representing one of the native
 languages supported by GemStone, and each value is an Array of Strings, the
 names of the months of the year in the corresponding language.'.
doc documentClassVar: #MonthNames with: txt.

txt := (GsDocText new) details:
'Methods in this category are obsolete and are provided only for compatibility
 with earlier releases of GemStone.  They will be removed in a future release.'.
doc documentCategory: #'Backward Compatibility' with: txt.

self description: doc.
%

category: 'Instance Creation'
classmethod: DateTime
new

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

self shouldNotImplement: #new
%

category: 'Instance Creation'
classmethod: DateTime
migrateNew

"Override default migrateNew behavior with #_basicNew."

^ self _basicNew
%

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

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

self shouldNotImplement: #new:
%

category: 'Instance Creation'
classmethod: DateTime
newWithYear: yearInt month: monthInt day: dayInt hours: hourInt
  minutes: minuteInt seconds: secondInt timeZone: aTimeZone

"Creates and returns an instance of the receiver from the specified values,
 as represented in the time zone specified."

| aDateTime |

aDateTime := (self newGmtWithYear: yearInt month: monthInt day: dayInt 
                   hours: hourInt minutes: minuteInt seconds: secondInt
                   timeZone: aTimeZone) 
             subtractSeconds: (aTimeZone secondsFromGmt).

(aDateTime isDstIn: aTimeZone)
  ifTrue:  [ ^ aDateTime subtractSeconds: (aTimeZone secondsForDst)]
  ifFalse: [ ^ aDateTime ].
%

category: 'Instance Creation'
classmethod: DateTime
newWithYear: yearInt month: monthInt day: dayInt hours: hourInt
  minutes: minuteInt seconds: secondInt

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

^ self newWithYear: yearInt month: monthInt day: dayInt hours: hourInt
       minutes: minuteInt seconds: secondInt timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithYear: yearInt month: monthInt day: dayInt hours: hourInt
  minutes: minuteInt seconds: secondInt timeZone: aTimeZone

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

<primitive: 312>

   "Check the class of the arguments"
yearInt _validateClass: SmallInteger.
monthInt _validateClass: SmallInteger.
dayInt _validateClass: SmallInteger.
hourInt _validateClass: SmallInteger.
minuteInt _validateClass: SmallInteger.
secondInt _validateClass: Integer.
aTimeZone _validateClass: TimeZone.

(yearInt >= 1901)
   ifFalse: [yearInt _error: #rtErrBadDateTimeArgs].
((hourInt >= 0) _and: [hourInt <= 23])
   ifFalse: [hourInt _error: #rtErrBadDateTimeArgs].
((minuteInt >= 0) _and: [minuteInt <= 59])
   ifFalse: [minuteInt _error: #rtErrBadDateTimeArgs].
((secondInt >= 0) _and: [secondInt <= 59])
   ifFalse: [secondInt _error: #rtErrBadDateTimeArgs].
((monthInt >=1) _and: [monthInt <= 12])
   ifFalse: [monthInt _error: #rtErrBadDateTimeArgs].
dayInt _error: #rtErrBadDateTimeArgs.
^ self _primitiveFailed: #newGmtWithYear:month:day:hours:minutes:seconds
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithYear: yearInt month: monthInt day: dayInt hours: hourInt
  minutes: minuteInt seconds: secondInt 

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

^ self newGmtWithYear: yearInt month: monthInt day: dayInt hours: hourInt
       minutes: minuteInt seconds: secondInt timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newWithYear: year dayOfYear: dayCount seconds: seconds

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

^ self newWithYear: year dayOfYear: dayCount seconds: seconds 
       timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newWithYear: year dayOfYear: dayCount seconds: seconds timeZone: aTimeZone

"Creates and returns an instance of the receiver from the specified values,
 in the time zone specified."

| aDateTime |

aDateTime := (self newGmtWithYear: year dayOfYear: dayCount 
              seconds: seconds timeZone: aTimeZone)
             subtractSeconds: (aTimeZone secondsFromGmt).

(aDateTime isDstIn: aTimeZone)
  ifTrue: [ ^ aDateTime subtractSeconds: (aTimeZone secondsForDst) ]
  ifFalse: [ ^ aDateTime ].
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithDate: aDate time: aTime timeZone: aTimeZone

"Creates and returns an instance of the receiver from the specified values,
 in the time zone specified."

^ self newGmtWithYear: (aDate year) dayOfYear: (aDate dayOfYear) 
  seconds: (aTime asSeconds) timeZone: aTimeZone.
%

category: 'Private'
classmethod: DateTime
newGmtWithDate: aDate seconds: secsGmt timeZone: aTimeZone

"Creates and returns an instance of the receiver from the specified values,
 in the time zone specified."

^ self newGmtWithYear: (aDate year) dayOfYear: (aDate dayOfYear) 
  seconds: secsGmt  timeZone: aTimeZone.
%

category: 'Private'
classmethod: DateTime
newGmtWithDate: aDate seconds: secsGmt

"Creates and returns an instance of the receiver from the specified values,
 in the current time zone."

^ self newGmtWithDate: aDate seconds: secsGmt timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newWithDate: aDate time: aTime 

"Creates and returns an instance of the receiver from the specified values,
 in the current time zone."

^ self newWithDate: aDate time: aTime timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newWithDate: aDate time: aTime timeZone: aTimeZone

"Creates and returns an instance of the receiver from the specified values,
 in the time zone specified."

| aDateTime |

aDateTime := (self newGmtWithYear: (aDate year) dayOfYear: (aDate dayOfYear)
             seconds: (aTime asSeconds) timeZone: aTimeZone)
             subtractSeconds: (aTimeZone secondsFromGmt).

(aDateTime isDstIn: aTimeZone)
  ifTrue: [ ^ aDateTime subtractSeconds: (aTimeZone secondsForDst) ]
  ifFalse: [ ^ aDateTime ].
%

category: 'Private'
classmethod: DateTime
newWithDate: aDate seconds: secs timeZone: aTimeZone

"Creates and returns an instance of the receiver from the specified values,
 in the time zone specified."

| aDateTime |

aDateTime := (self newGmtWithYear: (aDate year) dayOfYear: (aDate dayOfYear)
             seconds: secs timeZone: aTimeZone)
             subtractSeconds: (aTimeZone secondsFromGmt).

(aDateTime isDstIn: aTimeZone)
  ifTrue: [ ^ aDateTime subtractSeconds: (aTimeZone secondsForDst) ]
  ifFalse: [ ^ aDateTime ].
%

category: 'Private'
classmethod: DateTime
newWithDate: aDate seconds: secs 

"Creates and returns an instance of the receiver from the specified values,
 in the current time zone."

^ self newWithDate: aDate seconds: secs timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithYear: year dayOfYear: dayCount seconds: seconds 

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

^ self newGmtWithYear: year dayOfYear: dayCount seconds: seconds 
       timeZone: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithYear: year dayOfYear: dayCount seconds: seconds timeZone: aTimeZone

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

^ self newGmtWithYear: year dayOfYear: dayCount 
       milliseconds: (seconds * 1000) timeZone: aTimeZone.
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithYear: year dayOfYear: dayCount milliseconds: ms timeZone: aTimeZone

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

<primitive: 314>

aTimeZone _validateClass: TimeZone.
(year _isSmallInteger _and: [ (dayCount _isSmallInteger) ])
  ifFalse: [
    self _error: #rtErrDateTimeOutOfRange
    ].

(year >= 1901)
  ifFalse: [ ^ year _error: #rtErrBadDateTimeArgs ].
((dayCount <= 366) _and: [ dayCount > 0 ])
  ifFalse: [ ^ dayCount _error: #rtErrBadDateTimeArgs ].

(ms isKindOf: Integer)
  ifTrue: [ | extraDays newMs |
    extraDays := ms // 86400000.
    newMs := ms \\ 86400000.
    ^ (self newGmtWithYear: year dayOfYear: dayCount milliseconds: newMs
      timeZone: aTimeZone) addDays: extraDays.
    ]
  ifFalse: [ ^ ms _error:  #rtErrBadDateTimeArgs ].
        
^ self _primitiveFailed: #newGmtWithYear:dayOfYear:milliseconds:timeZone:
%

category: 'Instance Creation'
classmethod: DateTime
newGmtWithYear: year dayOfYear: dayCount milliseconds: ms 

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

^ self newGmtWithYear: year dayOfYear: dayCount milliseconds: ms 
  timeZone: (TimeZone current).
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: year dayOfYear: dayCount seconds: seconds

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

^ self _newGmtWithYear: year dayOfYear: dayCount seconds: seconds 
       timeZone: (TimeZone current).
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: year dayOfYear: dayCount seconds: seconds timeZone: aTimeZone

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

| extraDays newSeconds newDayOfYear |

(seconds abs >= 86400)
  ifTrue: [
    extraDays := seconds // 86400.
    newDayOfYear := dayCount + extraDays.
    newSeconds := seconds \\ 86400.
    ]
  ifFalse: [
    newDayOfYear := dayCount.
    newSeconds := seconds.
    ].

^ self newGmtWithYear: year dayOfYear: newDayOfYear seconds: newSeconds
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: year dayOfYear: dayCount milliseconds: ms timeZone: aTimeZone

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

| extraDays newMillisecs newDayOfYear |

(ms abs >= 86400000)
  ifTrue: [
    extraDays := ms // 86400000.
    newDayOfYear := dayCount + extraDays.
    newMillisecs := ms \\ 86400000.
    ]
  ifFalse: [
    newDayOfYear := dayCount.
    newMillisecs := ms.
    ].

^ self newGmtWithYear: year dayOfYear: newDayOfYear milliseconds: newMillisecs
       timeZone: aTimeZone.
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: year dayOfYear: dayCount milliseconds: ms

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

^ self _newGmtWithYear: year dayOfYear: dayCount milliseconds: ms 
       timeZone: (TimeZone current).
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: yearInt month: monthInt day: dayInt 
milliseconds: millisecInt timeZone: aTimeZone

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

<primitive: 238>

aTimeZone _validateClass: TimeZone.
( yearInt _isSmallInteger _and: 
[ (monthInt _isSmallInteger) _and: 
[ (dayInt _isSmallInteger) _and: [millisecInt _isSmallInteger]]] )
  ifFalse: [
    self _error: #rtErrDateTimeOutOfRange
    ].

(yearInt >= 1901)
  ifFalse: [ yearInt _error: #rtErrBadDateTimeArgs ].
((monthInt >=  1) _and: [ monthInt <= 12 ])
  ifFalse: [ ^ monthInt _error: #rtErrBadDateTimeArgs ].
((dayInt >=  1) _and: [ dayInt <= 31 ])
  ifFalse: [ ^ dayInt _error: #rtErrBadDateTimeArgs ].
((millisecInt > 0) _and: [ millisecInt <= 86399999 ])
  ifFalse: [ ^ millisecInt _error:  #rtErrBadDateTimeArgs ].

^ self _primitiveFailed: #_newGmtWithYear:month:day:
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: yearInt month: monthInt day: dayInt 
seconds: secondInt timeZone: aTimeZone

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

^ self _newGmtWithYear: yearInt month: monthInt day: dayInt
       milliseconds: (secondInt * 1000) timeZone: aTimeZone.
%

category: 'Private'
classmethod: DateTime
_newGmtWithYear: yearInt month: monthInt day: dayInt 
seconds: secondInt 

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

^ self _newGmtWithYear: yearInt month: monthInt day: dayInt
       seconds: secondInt timeZone: (TimeZone current).
%

category: 'Private'
classmethod: DateTime
_newJulianDay: julianDay second: sec

"Private. Creates and returns an instance of the receiver from the specified 
 values, which express Greenwich Mean Time. Generates an error if any of the 
 values are out of range."

^ self _newJulianDay: julianDay second: sec timeZone: (TimeZone current).
%

category: 'Private'
classmethod: DateTime
_newJulianDay: julianDay second: sec timeZone: aTimeZone

"Private. Creates and returns an instance of the receiver from the specified 
 values, which express Greenwich Mean Time. Generates an error if any of the 
 values are out of range."

^ self _newJulianDay: julianDay millisecond: (sec * 1000) 
    timeZone: aTimeZone.
%

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

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

^ self fromString: aString usingFormat: #(1 2 3 $/ 1 1 $: true true false)
%

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

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

^ self fromStringGmt: aString usingFormat: #(1 2 3 $/ 1 1 $: true true false)
%

category: 'Instance Creation'
classmethod: DateTime
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
 (DD/MM/YYYY HH:MM:SS).  Generates an error if the String does not conform to
 the format."

^ self fromStream: aStream usingFormat: #(1 2 3 $/ 1 1 $: true true false)
%

category: 'Instance Creation'
classmethod: DateTime
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
 (DD/MM/YYYY HH:MM:SS).  Generates an error if the String does not conform to
 the format."

^ self fromStreamGmt: aStream usingFormat: #(1 2 3 $/ 1 1 $: true true false)
%

category: 'Instance Creation'
classmethod: DateTime
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 DateTime for a complete description of the
 String-formatting specification Array.

 If the month format (5th element) indicates either an abbreviation (2) or an
 entire name (3), then this method tries to determine the month by decoding a
 character substring.  That substring may include any number of Characters, but
 must exactly match a legal month name (or the initial portion of that month
 name).  If the substring matches more than one month, the first month matched
 is used (the search begins with January).

 If the specification indicates that seconds should not be included (9th
 element is false), and aString includes seconds, this method generates an
 error."

| aDateTime localTimeZone |

localTimeZone := TimeZone current.
aDateTime := (self fromStringGmt: aString usingFormat: anArray)
             subtractSeconds: (localTimeZone secondsFromGmt).
aDateTime isDst
  ifTrue:  [ ^ aDateTime subtractSeconds: (localTimeZone secondsForDst)]
  ifFalse: [ ^ aDateTime ].
%

category: 'Instance Creation'
classmethod: DateTime
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 DateTime for a complete description of the
 String-formatting specification Array.

 If the month format (5th element) indicates either an abbreviation (2) or an
 entire name (3), then this method tries to determine the month by decoding a
 character substring.  That substring may include any number of Characters, but
 must exactly match a legal month name (or the initial portion of that month
 name).  If the substring matches more than one month, the first month matched
 is used (the search begins with January).

 If the specification indicates that seconds should not be included (9th
 element is false), and aString includes seconds, this method generates an
 error."

| 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: DateTime
fromStream: 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 DateTime for a complete description of the
 String-formatting specification Array.

 If the month format (5th element) indicates either an abbreviation (2) or an
 entire name (3), then this method tries to determine the month by decoding a
 character substring.  That substring may include any number of Characters, but
 must exactly match a legal month name (or the initial portion of that month
 name).  If the substring matches more than one month, the first month matched
 is used (the search begins with January).

 If the specification indicates that seconds should not be included (9th
 element is false), and aString includes seconds, this method generates an
 error."

| aDateTime localTimeZone |

localTimeZone := TimeZone current.
aDateTime := (self fromStreamGmt: aStream usingFormat: anArray)
             subtractSeconds: (localTimeZone secondsFromGmt).
aDateTime isDst
  ifTrue:  [ ^ aDateTime subtractSeconds: (localTimeZone secondsForDst)]
  ifFalse: [ ^ aDateTime ].
%

category: 'Instance Creation'
classmethod: DateTime
fromStreamGmt: 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 DateTime for a complete description of the
 String-formatting specification Array.

 If the month format (5th element) indicates either an abbreviation (2) or an
 entire name (3), then this method tries to determine the month by decoding a
 character substring.  That substring may include any number of Characters, but
 must exactly match a legal month name (or the initial portion of that month
 name).  If the substring matches more than one month, the first month matched
 is used (the search begins with January).

 If the specification indicates that seconds should not be included (9th
 element is false), and aString includes seconds, this method generates an
 error."

| dayInt monthInt yearInt hourInt minInt secInt timeDelim dateDelim ampm
  ampmPresent secondsPresent timePresent result parseDay
  blkNo delim parseMonth parseYear parseField |

"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
             ].

parseDay:= "parse day"
  [ :delim | | nextField |
    nextField := parseField value: delim value: (delim isEquivalent: $ ).
    dayInt := Integer fromCompleteString: nextField
  ].
parseMonth:= "parse month"
  [ :delim | | nextField |
    nextField := parseField value: delim value: (delim isEquivalent: $ ).
    (nextField =  '' )
    ifTrue:
      [ self _error: #rtErrBadFormat args: #[aStream]].
    (anArray at: 5) == 1
    ifTrue:
      [ monthInt := Integer fromCompleteString: nextField ]
    ifFalse:
      [ monthInt := Date _getMonthFrom: nextField ].
    (monthInt < 1) | (monthInt > 12)
    ifTrue:
      [ self _error: #rtErrBadFormat args: #[aStream] ]
  ].
parseYear := "parse year"
  [ :delim | | nextField |
    nextField := parseField value: delim value: (delim isEquivalent: $ ).
    yearInt := Integer fromCompleteString: nextField.
    (anArray at: 6) == 2
    ifTrue:
      [ (yearInt > 99)
        ifFalse: [yearInt := yearInt + ((self now yearGmt) // 100 * 100) ]
      ]
  ].

self _checkReadStream: aStream forClass: CharacterCollection.

self _checkFormat: anArray.

dateDelim := anArray at: 4.
timeDelim := anArray at: 7.
timePresent := anArray at: 8.

"parse the date, with day, month, year in the specified format order"
delim:= Array new add: dateDelim; add: dateDelim; add: $ ; yourself .
1 to: 3 do: [:i | blkNo:= anArray at: i.
            (blkNo == 1) ifTrue: [parseDay value: (delim at: i)].
            (blkNo == 2) ifTrue: [parseMonth value: (delim at: i)].
            (blkNo == 3) ifTrue: [parseYear value: (delim at: i)]
      ].

timePresent ifTrue:[ "read the time" 
    secondsPresent := anArray at: 9.
    ampmPresent := anArray at: 10.
    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 add: (aStream next); add: aStream next; yourself).
        (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].
          ].
      ]
  ]
ifFalse:[ "ignore the time"
    hourInt := 0.
    minInt := 0.
    secInt := 0
  ].

result := self newGmtWithYear: yearInt
                month: monthInt
                day: dayInt
                hours: hourInt
                minutes: minInt
                seconds: secInt.

"This is an easy way to test that all of the values specified were in
 in range.  If any of them were not, the result will be different
 than what we specified."

(result asPartsGmt = #[yearInt, monthInt, dayInt, hourInt, minInt, secInt])
ifFalse:
  [ self _error: #rtErrBadFormat args: #[aStream] ].

^ result
%

category: 'Instance Creation'
classmethod: DateTime
now

"Creates and returns an instance of the receiver representing the current
 time."

^ self now: (TimeZone current).
%

category: 'Instance Creation'
classmethod: DateTime
now: aTimeZone

"Creates and returns an instance of the receiver representing the current
 time in the time zone specified."

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

category: 'Backward Compatibility'
classmethod: DateTime
today

"Creates and returns an instance of the receiver representing the 00:00 hrs, 
 today, local time."

^ self today: (TimeZone current).
%

category: 'Backward Compatibility'
classmethod: DateTime
today: aTimeZone

"Creates and returns an instance of the receiver representing the 00:00 hrs, 
 today, local time."

^ self newWithDate: (Date today) seconds: 0 timeZone: aTimeZone.
%

category: 'Private '
classmethod: DateTime
_checkFormat: anArray

"Private. Verifies that anArray is a valid string-formatting specification for
 the receiver.  Generates an error if it is not."

"anArray is a format Array as documented in DateTime>>asStringUsingFormat:"

| v doTime |

anArray _validateClass: Array.
(anArray size < 8 _or:[ anArray size > 12])
  ifTrue:[ self _error: #rtErrBadFormatSpec args: #[anArray] ].

"Check for a combination of the digits 1, 2, and 3"
((anArray at: 1) + (anArray at: 2) + (anArray at: 3) == 6 _and:
        [(anArray at: 1) * (anArray at: 2) * (anArray at: 3) == 6])
  ifFalse:[ self _error: #rtErrBadFormatSpec args: #[anArray] ].

(anArray at: 4) _validateClasses: #[AbstractCharacter , CharacterCollection].

((v := anArray at: 5) == 1 _or: [v == 2 _or: [v == 3]])
  ifFalse:[ self _error: #rtErrBadFormatSpec args: #[anArray] ].

((anArray at: 6) == 1 _or: [(anArray at: 6) == 2])
  ifFalse:[ self _error: #rtErrBadFormatSpec args: #[anArray] ].

(doTime := anArray at: 8) _validateClass: Boolean.
doTime ifTrue:[
  anArray size >= 10 
    ifFalse:[ self _error: #rtErrBadFormatSpec args: #[anArray] ].
  (anArray at: 7) _validateClasses: #[AbstractCharacter , CharacterCollection].
  (anArray at: 9) _validateClass: Boolean.
  (anArray at: 10) _validateClass: Boolean.
  ].

(anArray size > 10)
  ifTrue: [ | doTimeZone |
    (doTimeZone := anArray at: 11) _validateClass: Boolean.
    doTimeZone ifTrue: [ 
      (anArray size > 11) ifTrue: [ (anArray at: 12) _validateClass: Boolean ]
      ].
    ].
%

category: 'Accessing'
method: DateTime
leap

"Returns true if the receiver describes a leap year, expressed in local time,
 and false if it does not."

^ self asDate leap.
%

category: 'Accessing'
method: DateTime
leapGmt

"Returns true if the receiver describes a leap year, expressed in Greenwich Mean
 Time, and false if it does not."

| yr |

"a year is a leap year if: (it is evenly divisible by 4 and it is not a
 century year) or (it is a century year and evenly divisible by 400)"

yr := (self _yearMonthDayGmt) at: 1.
((yr \\ 100) == 0) ifTrue: [^ ((yr \\ 400) == 0)].
^ ((yr \\ 4) == 0)
%

category: 'Accessing'
method: DateTime
dayGmt

"Returns a SmallInteger that gives the day in the month described by the 
 receiver, expressed in Greenwich Mean Time."

^  (self _yearMonthDayGmt) at: 3
%

category: 'Backward Compatibility'
method: DateTime
daysInMonth

"Returns a SmallInteger that gives the number of days in the month
 described by the receiver, expressed in local time."

^ self asDate daysInMonth.
%

category: 'Backward Compatibility'
method: DateTime
daysInMonthGmt

"Returns a SmallInteger that gives the number of days in the month described by
 the receiver, expressed in Greenwich Mean Time."

^ self _daysInMonth: self monthOfYearGmt
%

category: 'Accessing'
method: DateTime
month

"Returns a SmallInteger that gives the numeric index of the month of the year
 described by the receiver, expressed in local time.  The index is a number
 between 1 and 12 inclusive, where 1 signifies January."

^ self asDate month
%

category: 'Accessing'
method: DateTime
monthGmt

"Returns a SmallInteger that gives the numeric index of the month of the year
 described by the receiver, expressed in Greenwich Mean Time. The index is a 
 number between 1 and 12 inclusive, where 1 signifies January."

^  (self _yearMonthDayGmt) at: 2
%

category: 'Backward Compatibility'
method: DateTime
monthOfYear

"Returns a SmallInteger that gives the numeric index of the month of the year
 described by the receiver, expressed in local time.  The index is a number
 between 1 and 12 inclusive, where 1 signifies January."

^ self month
%

category: 'Backward Compatibility'
method: DateTime
monthOfYearGmt

"Returns a SmallInteger that gives the numeric index of the month of the year
 described by the receiver, expressed in Greenwich Mean Time.  The index is a
 number between 1 and 12 inclusive, where 1 signifies January."

^ self monthGmt
%

category: 'Accessing'
method: DateTime
monthName

"Returns a String that gives the name of the month of the year described by the
 receiver, expressed in local time, in the user's native language."

^ self asDate monthName.
%

category: 'Accessing'
method: DateTime
monthNameGmt

"Returns a String that gives the name of the month of the year described by the
 receiver, expressed in Greenwich Mean Time, in the user's native language."

^ MonthNames value at: self monthGmt
%

category: 'Accessing'
method: DateTime
yearGmt

"Returns a SmallInteger that gives the year described by the receiver, expressed
 in Greenwich Mean Time."

^ year
%

category: 'Accessing'
method: DateTime
year

"Returns a SmallInteger that gives the year described by the receiver,
 expressed in local time."

^ self asDate year.
%

category: 'Accessing'
method: DateTime
day

"Returns a SmallInteger that gives the day of the month described by the
 receiver, expressed in local time."

^ self asDate day.
%

category: 'Backward Compatibility'
method: DateTime
dayOfMonth

"Returns a SmallInteger that gives the day of the month described by the
 receiver, expressed in local time."

^ self asDate dayOfMonth.
%

category: 'Backward Compatibility'
method: DateTime
dayOfMonthGmt

"Returns a SmallInteger that gives the day of the month described by the
 receiver, expressed in Greenwich Mean Time."

^  (self _yearMonthDayGmt) at: 3
%

category: 'Accessing'
method: DateTime
dayOfWeek

"Returns a SmallInteger that gives the numeric index of the day of the week
 described by the receiver, expressed in local time.  The index is a
 number between 1 and 7 inclusive, where 1 signifies Sunday."

^ self asDate dayOfWeek.
%
category: 'Accessing'
method: DateTime
dayOfWeekGmt

"Returns a SmallInteger that gives the numeric index of the day of the week
 described by the receiver, expressed in Greenwich Mean Time.  The index is a
 number between 1 and 7 inclusive, where 1 signifies Sunday."

    ^(DateTime fromString: (self asStringGmt)) dayOfWeek
%
category: 'Accessing'
method: DateTime
dayOfYear

"Returns a SmallInteger that gives the day of the year described by the
 receiver, expressed in local time."

^ self asDate dayOfYear.
%

category: 'Accessing'
method: DateTime
dayOfYearGmt

"Returns a SmallInteger that gives the day of the year described by the
 receiver, expressed in Greenwich Mean Time."

^ dayOfYear
%

category: 'Private'
method: DateTime
_secondsLocal

"Returns the number of seconds since midnight in the receiver, as expressed in
 local time."

^ ((milliseconds // 1000) + self _localOffset) \\ 86400.
%

category: 'Accessing'
method: DateTime
hour

"Returns the hour in the 24-hour clock represented by the receiver in local
 time."

^ self _secondsLocal // 3600
%

category: 'Accessing'
method: DateTime
hourGmt

"Returns the hour in the 24-hour clock represented by the receiver, as
 expressed in Greenwich Mean Time"

^ milliseconds // 3600000
%

category: 'Backward Compatibility'
method: DateTime
hours

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

^ self hour
%

category: 'Backward Compatibility'
method: DateTime
hoursGmt

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

^ self hourGmt
%

category: 'Backward Compatibility'
method: DateTime
julianSecond

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

^ self timeSinceMidnight.
%

category: 'Accessing'
method: DateTime
minute

"Returns a SmallInteger (between zero and 59 inclusive) that gives the minute
 of the time represented by the receiver in local time."

^ (self _secondsLocal \\ 3600) // 60
%

category: 'Accessing'
method: DateTime
minuteGmt

"Returns a SmallInteger (between zero and 59 inclusive) that gives the minute
 of the time represented by the receiver in Greenwich Mean Time."

^ milliseconds \\ 3600000 // 60000
%

category: 'Backward Compatibility'
method: DateTime
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."

^ self minute
%

category: 'Backward Compatibility'
method: DateTime
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."

^ self minuteGmt
%

category: 'Accessing'
method: DateTime
second

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

^ self _secondsLocal \\ 60.
%

category: 'Accessing'
method: DateTime
secondGmt

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

^ milliseconds // 1000 \\ 60.
%

category: 'Backward Compatibility'
method: DateTime
seconds

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

^ self second.
%

category: 'Backward Compatibility'
method: DateTime
secondsGmt

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

^ self secondGmt.
%

category: 'Private'
method: DateTime
_yearMonthDayGmt

"Private. Returns a three-element Array of SmallIntegers containing the year, 
 index of the month, and the day of the month represented by the receiver, 
 expressed in Greenwich Mean Time."

<primitive: 239 >
^ self _primitiveFailed: #_yearMonthDayGmt
%

category: 'Private'
method: DateTime
_yearMonthDay

"Private. Returns a three-element Array of SmallIntegers containing the year, 
 index of the month, and the day of the month represented by the receiver, 
 expressed in local time."

^ (self addSeconds: (self _localOffset)) _yearMonthDayGmt.
%

category: 'Formatting'
method: DateTime
asString

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

^self asStringUsingFormat: #(1 2 3 $/ 1 1 $: true true false)
%

category: 'Formatting'
method: DateTime
asStringMs
"Returns a String that expresses the receiver in local time
 in the default format (DD/MM/YYYY HH:MM:SS.mmm)."

| result |
result := self asString .
result add: $. ;
  addAll: (milliseconds \\ 1000) _digitsAsString3  .
^ result
%


category: 'Formatting'
method: DateTime
asStringGmt

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

| t result |

t := self _yearMonthDayGmt.
result := (t at: 3) _digitsAsString .
result addAll: '/';
  addAll: (t at: 2) _digitsAsString;
  addAll: '/';
  addAll: (t at: 1) _digitsAsString;
  addAll: ' ';
  addAll: (milliseconds // 3600000) _digitsAsString;
  addAll: ':';
  addAll: (milliseconds // 1000 \\ 3600 // 60) _digitsAsString;
  addAll: ':';
  addAll: (milliseconds // 1000 \\ 60) _digitsAsString .
^ result
%

category: 'Formatting'
method: DateTime
asStringGmtMs
"Returns a String that expresses the receiver in Greenwich Mean Time
 in the default format (DD/MM/YYYY HH:MM:SS.mmm)."

| result |
result := self asStringGmt .
result add: $. ;
  addAll: (milliseconds \\ 1000) _digitsAsString3  .
^ result
%

category: 'Formatting'
method: DateTime
asStringUsingFormat: anArray

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

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

| localDt aString |

((anArray size >= 11) _and: [ anArray at: 11 ])
  ifTrue: [
    | aTimeZone |
    ((anArray size >= 12) _and: [ anArray at: 12 ])
      ifTrue: [ aTimeZone := self timeZone ]
      ifFalse: [ aTimeZone := TimeZone current ].
    localDt := self addSeconds: (self _localOffset: aTimeZone).
    aString := localDt asStringStdUsingFormat: anArray.
    aString add: Character space.
    (self isDstIn: aTimeZone)
      ifTrue: [ aString addAll: aTimeZone dstPrintString ]
      ifFalse: [ aString addAll: aTimeZone standardPrintString ].
    ]
  ifFalse: [
    localDt := self addSeconds: self _localOffset.
    aString := localDt asStringStdUsingFormat: anArray.
    ].

^ aString.
%

category: 'Formatting'
method: DateTime
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 DateTime for a complete description of the
 String-formatting specification Array."

| aString |

((anArray size >= 11) _and: [ anArray at: 11 ])
  ifTrue: [
    aString := self asStringStdUsingFormat: anArray.
    aString add: Character space.
    aString addAll: 'Greenwich Mean Time'.
    ]
  ifFalse: [
    aString := self asStringStdUsingFormat: anArray.
    ].

^ aString.
%


category: 'Private'
method: DateTime
asStringStdUsingFormat: anArray

"Returns a String that expresses the receiver according to its 
 contents without regard to the time zone parameter."

|t dateSeparator timeSeparator monthName aString
 hour hourInt min sec day yearNumber |

t := self _yearMonthDayGmt.
self class _checkFormat: anArray.
dateSeparator := (anArray at: 4) asString.

timeSeparator := (anArray at: 7) asString.

((anArray at: 5) == 2) "get the month name according to the format"
   ifTrue: [monthName := self _monthAbbrev: (t at: 2)]
   ifFalse: [((anArray at: 5) == 3) "month as number is default"
      ifTrue: [monthName := self _monthName: (t at: 2)]
      ifFalse: [monthName := (t at: 2) _digitsAsString]].

((anArray at: 6) == 2)
   ifTrue: [yearNumber := ((t at: 1) \\ 100) _digitsAsString]
   ifFalse: [yearNumber := (t at: 1) asString].  "YYYY is default"

day := (t at:3) _digitsAsString.
((anArray at: 1) == 2) "month first"
   ifTrue: [aString := monthName , dateSeparator]
   ifFalse: [((anArray at: 1) == 3) "yearNumber first"
      ifTrue: [aString := yearNumber , dateSeparator]
      ifFalse: [aString := day , dateSeparator]].  "day first is default"

((anArray at: 2) == 1) "day second"
   ifTrue: [aString addAll: day; addAll: dateSeparator] "yearNumber second"
   ifFalse: [((anArray at: 2) == 3) "month second is default"
      ifTrue: [aString addAll: yearNumber; addAll: dateSeparator]
      ifFalse: [aString addAll: monthName; addAll: dateSeparator]].

((anArray at: 3) == 1) "day third"
   ifTrue: [aString addAll: day]
   ifFalse: [((anArray at: 3) == 2) "month third"
      ifTrue: [aString addAll: monthName]
      ifFalse: [aString addAll: yearNumber]].  "yearNumber third is default"

hourInt := (milliseconds // 3600000) .
hour := hourInt _digitsAsString.
min := (milliseconds // 1000 \\ 3600 // 60) _digitsAsString.
sec := (milliseconds // 1000 \\ 60) _digitsAsString.

(anArray at: 8) ifTrue: [ "print the time"
  aString add: $ .
  (anArray at: 10) ifTrue: [ "12-hour format"
    (hourInt > 12) ifTrue: [
      aString addAll: (hourInt - 12) _digitsAsString;
              addAll: timeSeparator;
              addAll: min.

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

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

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

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

^ aString
%

category: 'Backward Compatibility'
method: DateTime
_monthName: anIndex

"Private.  Returns a String that gives the name, in the user's native language,
 of the month of the year whose numeric index is anIndex.  The index is a number
 between 1 and 12 inclusive, where 1 signifies January."

^ (MonthNames value) at: anIndex.
%

category: 'Converting'
method: DateTime
asPartsGmt

"Returns an Array of six SmallIntegers (year month day hours minutes seconds)
 that expresses the receiver in Greenwich Mean Time."

| result |

result := self _yearMonthDayGmt.  "year/month/day"
result addLast: (milliseconds // 3600000).  "hours"
result addLast: (milliseconds \\ 3600000) // 60000.  "minutes"
result addLast: (milliseconds // 1000  \\ 60).  "seconds"
^ result 
%

category: 'Converting'
method: DateTime
timeClass

^ Time
%

category: 'Converting'
method: DateTime
asTime

"Returns a Time which represents the time since midnight represented by the
 receiver, as expressed in local time."

^ self timeClass fromSeconds: (self timeSinceMidnight).
%

category: 'Converting'
method: DateTime
asParts

"Returns an Array of six SmallIntegers (year month day hours minutes seconds)
 that expresses the receiver in local time."

^ (self addSeconds: (self _localOffset)) asPartsGmt  .
%

category: 'Converting'
method: DateTime
asSecondsGmt

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

^ ((self asDaysGmt) * 86400) + (milliseconds // 1000) 
%
category: 'Converting'
method: DateTime
asMillisecondsGmt

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

^ ((self asDaysGmt) * 86400000) + milliseconds 
%

category: 'Converting'
method: DateTime
asSeconds

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

^ self asSecondsGmt + self _localOffset.
%

category: 'Backward Compatibility'
method: DateTime
timeSinceMidnightGmt

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

^ milliseconds // 1000.
%

category: 'Backward Compatibility'
method: DateTime
timeSinceMidnight

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

| secs |

secs := self timeSinceMidnightGmt + TimeZone current secondsFromGmt.
self isDst 
  ifTrue: [ secs := secs + TimeZone current secondsForDst ].

secs := secs + 86400 \\ 86400.

^ secs.
%

category: 'Comparing'
method: DateTime
< aDateTime

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

| argYear argDayOfYear |

argYear := aDateTime yearGmt.
(year < argYear) ifTrue: [ ^true ].
(year > argYear) ifTrue: [ ^false ]. 

"The years are the same"

argDayOfYear := aDateTime dayOfYearGmt .
(dayOfYear < argDayOfYear) ifTrue: [ ^true ].
(dayOfYear > argDayOfYear) ifTrue: [ ^false ]. 

"The days are the same"

^ milliseconds < aDateTime millisecondsGmt 
%

category: 'Comparing'
method: DateTime
= aDateTime

"Returns true if the receiver represents the same moment in time as that of the
 argument, and false if it does not."

self == aDateTime ifTrue:[ ^ true ].

(aDateTime isKindOf: self _class) ifFalse: 
    [^false].
^(year == aDateTime yearGmt) _and: 
     [(dayOfYear == aDateTime dayOfYearGmt) _and: 
          [milliseconds == aDateTime millisecondsGmt ]]
%

category: 'Comparing'
method: DateTime
hash

"Returns an Integer hash code for the receiver."

^ (((year hash) bitShift: -1) bitXor: (dayOfYear hash)) bitXor: (self secondsGmt hash).
%

category: 'Comparing'
method: DateTime
> aDateTime

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

| argYear argDayOfYear |

argYear := aDateTime yearGmt.
(year > argYear) ifTrue: [ ^true ].
(year < argYear) ifTrue: [ ^false ]. 

"The years are the same"

argDayOfYear := aDateTime dayOfYearGmt.
(dayOfYear > argDayOfYear) ifTrue: [ ^true ].
(dayOfYear < argDayOfYear) ifTrue: [ ^false ]. 

"The days are the same"

^ milliseconds > aDateTime millisecondsGmt 
%

! edited to fix 37016
category: 'Obsolete'
method: DateTime
timeCompare: aDateTime
    "Provided for compatibility. 

    Answer 0 is the reciever's time component is the same as that of
    aDateTime. Otherwise, -1 is the receiver's is less than, or 1 if
    the receiver's is greater than."

    | otherMs |
    otherMs := aDateTime millisecondsGmt.
    milliseconds == otherMs ifTrue: [^0].
    milliseconds < otherMs ifTrue: [^-1].
    ^1
%
category: 'Arithmetic'
method: DateTime
addYears: anInteger

"Returns a DateTime that describes a moment in time anInteger years
 later than that of the receiver."

| newDate |

newDate := self asDate addYears: anInteger.
^ self _class newWithDate: newDate seconds: self timeSinceMidnight
%

category: 'Arithmetic'
method: DateTime
subtractYears: anInteger

"Returns a DateTime that describes a moment in time anInteger years
 earlier than that of the receiver."

^ self addYears: (anInteger negated).
%

category: 'Arithmetic'
method: DateTime
addMonths: anInteger

"Returns a DateTime that describes a moment in time anInteger months
 later than that of the receiver.

 This method attempts to keep the day of the month the same.  If the
 new month has fewer days than the receiver's original month, then it
 truncates to the last day of the new month."

| newDate |

newDate := self asDate addMonths: anInteger.
^ self _class newWithDate: newDate seconds: self timeSinceMidnight
%

category: 'Arithmetic'
method: DateTime
subtractMonths: anInteger

"Returns a DateTime that describes a moment in time anInteger months
 earlier than that of the receiver.

 This method attempts to keep the day of the month the same.  If the
 new month has fewer days than the receiver's original month, then it
 truncates to the last day of the new month."

^ self addMonths: (anInteger negated). 
%

category: 'Arithmetic'
method: DateTime
addWeeks: anInteger

"Returns a DateTime that describes a moment in time anInteger weeks
 later than that of the receiver."

^ self addDays: (anInteger * 7).
%

category: 'Arithmetic'
method: DateTime
subtractWeeks: anInteger

"Returns a DateTime that describes a moment in time anInteger weeks
 earlier than that of the receiver."

^ self subtractDays: (anInteger * 7).
%

category: 'Arithmetic'
method: DateTime
addDays: anInteger

"Returns a DateTime that describes a moment in time anInteger days
 later than that of the receiver."

| newDate  |

newDate := self asDate addDays: anInteger.
^ self class newWithDate: newDate seconds: self timeSinceMidnight
%

category: 'Arithmetic'
method: DateTime
subtractDays: anInteger

"Returns a DateTime that describes a moment in time anInteger days
 earlier than that of the receiver."

^ self addDays: (anInteger negated).
%

category: 'Arithmetic'
method: DateTime
addHours: aNumber

"Returns a DateTime that describes a moment in time aNumber hours
 later than that of the receiver."

^ (self class) _newGmtWithYear: year dayOfYear: dayOfYear 
                  milliseconds: (milliseconds + (aNumber * 3600000)) asInteger
                  timeZone: timeZone.
%

category: 'Arithmetic'
method: DateTime
subtractHours: aNumber

"Returns a DateTime that describes a moment in time aNumber hours
 earlier than that of the receiver."

^ self addHours: (aNumber negated).
%

category: 'Arithmetic'
method: DateTime
addMinutes: aNumber

"Returns a DateTime that describes a moment in time aNumber minutes
 later than that of the receiver."

^ (self class) _newGmtWithYear: year dayOfYear: dayOfYear 
           milliseconds: (milliseconds + (aNumber * 60000)) asInteger 
           timeZone: timeZone.
%

category: 'Arithmetic'
method: DateTime
subtractMinutes: aNumber

"Returns a DateTime that describes a moment in time aNumber minutes
 earlier than that of the receiver."

^ self addMinutes: (aNumber negated).
%

category: 'Arithmetic'
method: DateTime
addSeconds: aNumber

"Returns a DateTime that describes a moment in time aNumber seconds
 later than that of the receiver."

^ (self class) _newGmtWithYear: year dayOfYear: dayOfYear 
		milliseconds: (milliseconds + (aNumber * 1000)) asInteger 
                timeZone: timeZone.
%

category: 'Arithmetic'
method: DateTime
addMilliseconds: aNumber

"Returns a DateTime that describes a moment in time aNumber milliseconds
 later than that of the receiver."

^ (self class) _newGmtWithYear: year dayOfYear: dayOfYear 
		milliseconds: (milliseconds + aNumber ) asInteger 
                timeZone: timeZone.
%

category: 'Arithmetic'
method: DateTime
subtractSeconds: aNumber

"Returns a DateTime that describes a moment in time aNumber seconds
 earlier than that of the receiver."

^ self addSeconds: (aNumber negated).
%

category: 'Arithmetic'
method: DateTime
subtractDateGmt: aDateTime

"Returns a positive Integer that counts the number of times that midnight
 Greenwich Mean Time occurs between the times described by the receiver and
 aDateTime."

^ (self asDays - aDateTime asDays) abs
%

category: 'Arithmetic'
method: DateTime
subtractDate: aDateTime

"Returns a positive Integer that counts the number of times midnight local time
 occurs between the times described by the receiver and aDateTime."

^ (self asDate) subtractDate: (aDateTime asDate)
%

category: 'Arithmetic'
method: DateTime
subtractTime: aDateTime

"Returns an Array of three positive Integers that count the hours, minutes, and
 seconds, respectively, between the times of day described by the receiver and
 aDateTime.

 The computation ignores the dates of both the receiver and aDateTime, and
 assumes that the receiver is the later time.  Hence, if the time of day in the
 receiver is less than the time of day in aDateTime, then the receiver's time of
 day is assumed to occur on the day following that of aDateTime."

| parts h m s |

parts := self asParts.
h := parts at: 4.
m := parts at: 5.
s := parts at: 6.

parts := aDateTime asParts.
h < (parts at: 4) ifTrue:[ h := h + 24 ].
h := h - (parts at: 4).
m := m - (parts at: 5).
s := s - (parts at: 6).

s < 0 ifTrue: [
  s := s + 60.
  m := m - 1
].
s > 60 ifTrue: [
  s := s - 60.
  m := m + 1
].
m < 0 ifTrue: [
  m := m + 60.
  h := h - 1
].
m > 60 ifTrue: [
  m := m - 60.
  h := h + 1
].
^#[h, m, s]
%

category: 'Formatting'
method: DateTime
US12HrFormat

"Returns a String that expresses the receiver in local time.  The date is in
 United States format (month first) and the time of day is based on the 12-hour
 clock (MM/DD/YY HH:MM:SS pm)."

^self asStringUsingFormat: #(2 1 3 $/ 1 2 $: true false true)
%

category: 'Formatting'
method: DateTime
US24HrFormat

"Returns a String that expresses the receiver in local time.  The date is in
 United States format (month first) and the time of day is based on the 24-hour
 clock (MM/DD/YY HH:MM:SS)."

^self asStringUsingFormat: #(2 1 3 $/ 1 2 $: true false false)
%

category: 'Formatting'
method: DateTime
USDateFormat

"Returns a String that expresses the date of the receiver, expressed in local
 time. The date is in United States format, month first (MM/DD/YY)."

^self asStringUsingFormat: #(2 1 3 $/ 1 2 $: false)
%

category: 'Storing and Loading'
classmethod: DateTime
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 year dayCount ms zone marker |
marker := passiveObj objectPositionMarker.
passiveObj version >= 510
  ifTrue: [
     passiveObj readNamedIV.
     year := passiveObj ivValue.
     passiveObj readNamedIV.
     dayCount := passiveObj ivValue.
     passiveObj readNamedIV.
     ms := passiveObj ivValue.
     passiveObj readNamedIV.
     zone := passiveObj ivValue.
     passiveObj skipNamedInstVars.

     inst := self _newGmtWithYear: year dayOfYear: dayCount milliseconds: ms 
               timeZone: zone .
    ]
  ifFalse: [
    inst := self _newGmtWithYear: passiveObj readObject
               month: passiveObj readObject
               day: passiveObj readObject
               seconds: passiveObj readObject
               timeZone: TimeZone current.
    passiveObj version >= 500
      ifFalse: [ "convert 4.1 local time to GMT"
        inst := inst addSeconds: Time gmtOffsetSeconds
        ].
    ].
passiveObj hasRead: inst marker: marker .
^inst.
%

! writeTo: inherited from Object

category: 'Converting'
method: DateTime
asDateTime

"Returns the receiver."

^ self.
%

category: 'Converting'
method: DateTime
timeZone: aTimeZone

"Returns a new DateTime with the specified TimeZone. The new DateTime 
 represents the same point in time as the receiver. A point in time 
 is usually different when viewed as local time in different time zones."

^ self class newGmtWithYear: (self yearGmt) dayOfYear: (self dayOfYearGmt) 
       milliseconds: (self millisecondsGmt) timeZone: aTimeZone.
%

! delete clusterDepthFirst with final fix for 36462

category: 'Repository Conversion'
method: DateTime
convertToDateTime

"Returns the receiver."

^ self.
%

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

"Disallowed. Changes to a DateTime a not allowed."

^ self shouldNotImplement: #at:put:
%

category: 'Accessing'
method: DateTime
julianDay

"Returns the Julian Day of the receiver, expressed in local time. The julian
 day is a SmallInteger that gives the number of days since January 1, 4713 B.C.,
 as defined in Communications of the ACM, algorithm #199."

^ self asDate julianDay.
%

category: 'Private'
method: DateTime
_daysInMonth: month

"Returns a SmallInteger that gives the number of days in the month
 specified by the Integer month."

((month == 1) _or: [(month == 3) _or: [(month == 5) _or: [(month == 7) _or:
   [(month == 8) _or: [(month == 10) _or: [(month == 12)]]]]]])
   ifTrue: [^ 31].
((month == 4) _or: [(month == 6) _or: [(month == 9) _or: [(month == 11)]]])
   ifTrue: [^ 30].
(self leap)
   ifTrue: [^ 29].
^ 28
%

category: 'Accessing'
method: DateTime
daysInYear

"Returns a SmallInteger that gives the number of days in the year
 described by the receiver."

(self leap) ifTrue: [^ 366].
^ 365
%

category: 'Accessing'
method: DateTime
size: anInteger

"Disallowed.  You may not change the size of a DateTime."

self shouldNotImplement: #size:
%

category: 'Accessing'
method: DateTime
weekDayName

"Returns a String that gives the name of the day of the week described by the
 receiver, expressed in local time."

^ self asDate weekDayName
%

category: 'Accessing'
method: DateTime
weekDayNameGmt

"Returns a String that gives the name of the day of the week described by the
 receiver, expressed in Greenwich Mean Time."

^  WeekDayNames value at: (self dayOfWeekGmt).
%

category: 'Formatting'
method: DateTime
printOn: aStream

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

aStream nextPutAll: self asString .
%

category: 'Formatting'
method: DateTime
_monthAbbrev: anIndex

"Private.  Returns a three-letter String that gives the abbreviation, in the
 user's native language, of the name of the month whose numeric index is
 anIndex.  The index is a number between 1 and 12 inclusive, where 1 signifies
 January."

|theMonth itsAbbrev|

theMonth := self _class nameOfMonth: anIndex.  "get its full name"
itsAbbrev := String new.
1 to: 3 do: "take the first three letters"
   [:aChar | itsAbbrev := itsAbbrev , (theMonth at: aChar)].
^ itsAbbrev
%

category: 'General Inquiries'
classmethod: DateTime
nameOfMonth: anIndex

"Returns a String that gives the name, in the user's native language, of the
 month of the year whose numeric index is anIndex.  The index is a number
 between 1 and 12 inclusive, where 1 signifies January."

^ (MonthNames value) at: anIndex.
%

category: 'Converting'
method: DateTime
asDaysGmt

"Returns an Integer that represents the receiver in units of days since
 January 1, 1901, Greenwich Mean Time."

| numYears numDays |

numYears := year - 1901.
numDays := (numYears * 365) + (numYears // 4) +
           ((numYears + 300) // 400) - (numYears // 100) + dayOfYear - 1.
^ numDays.
%

category: 'Converting'
method: DateTime
asDays

"Returns an Integer that represents the receiver in units of days since
 January 1, 1901, local time."

^ self asDate asDays.
%

category: 'Converting'
method: DateTime
asDate

"Returns a Date that represents the receiver, as expressed in local time."

^ self asDateIn: TimeZone current.
%

category: 'Converting'
method: DateTime
dateClass

"Returns the class of the object to be created when the receiver is being
 converted to a date."

^ Date
%

category: 'Converting'
method: DateTime
asDateIn: aTimeZone

"Returns a Date that represents the receiver, as expressed in the specified
 time zone."

| aDateTime |

aDateTime := self addSeconds: (self _localOffset: aTimeZone).
^ self dateClass newDay: (aDateTime dayOfYearGmt) year: (aDateTime yearGmt).
%

category: 'Accessing'
method: DateTime
timeZone

^ timeZone
%

category: 'Private'
method: DateTime
isDst

^ self isDstIn: (TimeZone current)
%

category: 'Private'
method: DateTime
isDstIn: aTimeZone

| startOfDst endOfDst aYear|

(aTimeZone secondsForDst == 0)
  ifTrue: [ ^ false "No Dst"].

aYear := self yearGmt.
(aTimeZone yearStartDst > aYear)
  ifTrue: [ ^ false "Dst did not begin until later on"].

(startOfDst := aTimeZone startOfDstFor: aYear) == nil ifTrue: [^false].
(endOfDst   := aTimeZone endOfDstFor: aYear) == nil ifTrue: [^false].

(startOfDst < endOfDst) 
  ifTrue: [ "Northern Hemisphere"
    ^ ((self >= startOfDst) _and: [ self < endOfDst ])
    ]
  ifFalse: [ "Southern Hemisphere"
    ^ ((self >= startOfDst) _or: [ self < endOfDst ])
    ]
%

category: 'Private'
method: DateTime
_localOffset

^ self _localOffset: TimeZone current.
%

category: 'Private'
method: DateTime
_localOffset: aTimeZone

| offset |

offset := aTimeZone secondsFromGmt.
(self isDstIn: aTimeZone)
  ifTrue: [ offset := offset + aTimeZone secondsForDst ].
^ offset.
%

category: 'Accessing'
category: 'Accessing'
method: DateTime
millisecondsGmt

"Returns the value of the milliseconds instance variable."

^ milliseconds.
%

category: 'Backward Compatibility'
method: DateTime
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 timeSinceMidnightGmt.
%

category: 'Backward Compatibility'
method: DateTime
timeAsSeconds

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

^ self timeSinceMidnight.
%

category: 'Private'
classmethod: DateTime
_newJulianDay: julianDay millisecond: ms

"Returns a new instance of the receiver based on the specified values. The 
 values julianDay and milliseconds should represent Greenwich Mean Time."

^ self _newJulianDay: julianDay millisecond: ms timeZone: (TimeZone current).
%

category: 'Private'
classmethod: DateTime
_newJulianDay: julianDay millisecond: ms timeZone: aTimeZone

"Returns a new instance of the receiver based on the specified values. The 
 values julianDay and milliseconds should represent Greenwich Mean Time."

<primitive: 236>
julianDay _validateClass: SmallInteger.
ms _validateClass: SmallInteger.
aTimeZone _validateClass: TimeZone.
#[julianDay, ms, aTimeZone] _error: #rtErrBadDateTimeArgs.
^ self _primitiveFailed: #_newJulianDay:milliseconds:timeZone
%

category: 'Private'
classmethod: DateTime
_newJulianDay: julianDay localTime: milliseconds

"Returns a new instance of the receiver based on the specified values. The
 values julianDay and milliseconds represent the local time."

| aTimeZone aDateTime |

aTimeZone := TimeZone current.
aDateTime := (self _newJulianDay: julianDay millisecond: milliseconds 
              timeZone: aTimeZone)
             subtractSeconds: (aTimeZone secondsFromGmt).

(aDateTime isDstIn: aTimeZone)
  ifTrue:  [ ^ aDateTime subtractSeconds: (aTimeZone secondsForDst)]
  ifFalse: [ ^ aDateTime ].
%

category: 'New Indexing Comparison'
method: DateTime
_classSortOrdinal

^ 60
%
