Date.st
author Claus Gittinger <cg@exept.de>
Tue, 07 Jan 1997 12:02:05 +0100
changeset 2072 e84dbf5e5424
parent 1701 80d13adb2e77
child 2706 878723bc99fc
permissions -rw-r--r--
removed package-change info message

"
 COPYRIGHT (c) 1989 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.
"

Magnitude subclass:#Date
	instanceVariableNames:'dateEncoding'
	classVariableNames:'DayNames MonthNames DayAbbrevs MonthAbbrevs EnvironmentChange'
	poolDictionaries:''
	category:'Magnitude-General'
!

!Date  class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1989 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
"
    Instances of Date represent dates as year, month and day encoded in the 
    (private & hidden) instance dateEncoding. The value found there is 
    year*100*100 + month*100 + day (which makes magnitude-like comparison of 
    dates easy, but is not guaranteed for future versions).
    Do not depend on the internal representation.

    The old representation used days since 1st Jan. 1901 internally - 
    with the new implementation, it is possible to reasonably represent almost 
    any Date.  (which insurance companies will like, since they can now 
    represent even very old peoples birthday :-)
    Notice: no correction for pre-Julian dates is done.

    The printed representation of dates is controlled by resource definitions -
    thus national variants are already supported (see file 'resources/Date.rs').

    Compatibility notice:
        due to some historic reasons, there are some methods found twice
        with different names in this class. The old ST/X methods will vanish in
        one of the next releases, and kept for a while to support existing
        applications (the info on how these methods should be named came 
        somewhat late from the testers ..).

        Please do not use methods marked as obsolete in their comment.

    Most useful methods:

        Date today
        (Date today) addDays:
        (Date today) subtractDays:

    [author:]
        Claus Gittinger

    [see also:]
        Time AbstractTime
        Filename
        OperatingSystem
"
! !

!Date  class methodsFor:'instance creation'!

fromDays:dayCount
    "return a new Date, given the day-number starting with 0 at 1.Jan 1901;
     (i.e. 'Date fromDays:0' returns 1st Jan. 1901).
     Date asDays is the reverse operation.
     Added for GNU/ST-80 compatibility"

    |yr rest d|

    "approx. year"
    yr := (dayCount // 366) + 1901.
    rest := dayCount - (self yearAsDays:yr) + 1. "+1 for ST-80 compatibility"
    d := self daysInYear:yr.
    (rest > d) ifTrue:[
        "adjust"
        yr := yr + 1.
        rest := rest - d.
    ].

    ^ self newDay:rest year:yr

    "
     Date fromDays:0     -> 1 jan 1901
     Date fromDays:365   -> 1 jan 1902
     Date fromDays:730   -> 1 jan 1903
     Date fromDays:1095  -> 1 jan 1903
     Date fromDays:1460  ->31 dec 1904 since 1904 was a leap year
    "

    "Modified: 1.7.1996 / 14:24:25 / cg"
!

newDay:day month:month year:year
    "return a new Date, given the day, month and year.
     For your convenience, month may be either an integer 
     or the months name as a string. 

     Year may be the actual year (such as 1890, 2001) or the number 
     of years since 1900 (which is rubbish ST-80 compatibility: 
     it will be totally useless in a few years ...).
     You better not use this short-year feature in your programs."

    |monthIndex ok yr|

    yr := year.
    yr < 100 ifTrue:[
        yr := yr + 1900.
    ].

    month isInteger ifTrue:[
        monthIndex := month
    ] ifFalse:[
        monthIndex := self indexOfMonth:month
    ].
    (monthIndex == 2 and:[day == 29]) ifTrue:[
        ok := self leapYear:yr
    ] ifFalse:[
        ok := day <= (self daysInMonth:month forYear:yr)
    ].
    ((day > 0) and:[ok]) ifTrue:[
        ^ self basicNew dateEncoding:(((yr * 100) + monthIndex) * 100) + day
    ].

    "this error is triggered if you try to create a date from an
     invalid year/month/day combination;
     Such as 29-feb-year, where year is no leap year
    "
    self error:'invalid date'

    "
     Date newDay:8  month:'may' year:1993
     Date newDay:8  month:5     year:1994
     Date newDay:29 month:'feb' year:1994
     Date newDay:29 month:'feb' year:1993
     Date newDay:28 month:'feb' year:5
     Date newDay:28 month:'feb' year:95
    "

    "Modified: 19.4.1996 / 15:28:15 / cg"
!

newDay:dayInYear year:year
    "return a new Date, given the year and the day-in-year (starting at 1).
     See also: Date today / Time now / AbsoluteTime now.
     ST-80 compatibility"

    |monthAndDay|

    (dayInYear between:1 and:365) ifFalse:[
        ((dayInYear == 366) and:[self leapYear:year]) ifFalse:[
            "
             this error is triggered, when you try to create a
             day from an invalid day-in-year; 
             for example, 366 in a non-leap year.
             I dont know, if ST-80 wraps to the next year(s) in this case.
            "
            ^ self error:'invalid date'
        ]
    ].
    monthAndDay := self monthAndDayFromDayInYear:dayInYear forYear:year.
    ^ self newDay:(monthAndDay at:2) month:(monthAndDay at:1) year:year

    "
     Date newDay:150 year:1994
     Date newDay:1 year:1994
     Date newDay:1 year:1901
     Date newDay:1 year:1902
     Date newDay:365 year:1992
     Date newDay:366 year:1992
     Date newDay:365 year:1994
     Date newDay:366 year:1994
    "

    "Modified: 1.7.1996 / 14:22:24 / cg"
!

readFrom:aStringOrStream onError:exceptionBlock
    "return a new Date, reading a printed representation from aStream.
     Notice, that this is not the storeString format and 
     is different from the format expected by readFrom:.
     BUG:
       This method assumes american format (i.e. month-day-year) instead
       of the german/french and other day-month-year.
       There ought to be a nationalized variant of this."

    ErrorSignal handle:[:ex |
        ^ exceptionBlock value
    ] do:[
        |str month day year|

        str := aStringOrStream readStream.

        [str peek isLetterOrDigit] whileFalse:[str next].
        (str peek isDigit) ifTrue:[
            day := Integer readFrom:str onError:[^ exceptionBlock value]
        ].
        [str peek isLetterOrDigit] whileFalse:[str next].
        (str peek isLetter) ifTrue:[
            month := str nextAlphaNumericWord.
            day isNil ifTrue:[
                [str peek isLetterOrDigit] whileFalse:[str next].
                day := Integer readFrom:str onError:[^ exceptionBlock value].
            ]
        ] ifFalse:[
            month := self nameOfMonth:day.
            day := Integer readFrom:str onError:[^ exceptionBlock value]
        ].
        [str peek isLetterOrDigit] whileFalse:[str next].
        year := Integer readFrom:str onError:[^ exceptionBlock value].
        ^ self newDay:day month:month year:year
    ].

    "
     Date readFromString:'31 December 1992'  
     Date readFromString:'December, 5, 1992'  
     Date readFromString:'December, 5 1992'  
     Date readFromString:'3-jan-95'           
     Date readFromString:'12/31/1992'        
     Date readFromString:'15.4.1992'         -> german; leads to an error
     Date readFromString:'10.4.1992'         -> german; leads to a wrong date
     Date readFromString:'10.4.1992' onError:['wrong date']
    "

    "Created: 16.11.1995 / 22:50:17 / cg"
    "Modified: 8.10.1996 / 19:25:39 / cg"
!

today
    "return a date, representing today.
     See also: Time now / AbsoluteTime now."

    ^ self fromOSTime:(OperatingSystem getOSTime)

    "
     Date today
    "

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

!Date  class methodsFor:'general queries'!

abbreviatedNameOfDay:dayIndex
    "given a day index (1..7), return the abbreviated name
     of the day"

    EnvironmentChange ifTrue:[
	self initNames
    ].
    ^ DayAbbrevs at:dayIndex

    "
     Date abbreviatedNameOfDay:4
    "
!

abbreviatedNameOfMonth:monthIndex
    "given a month index (1..12), return the abbreviated name
     of the month"

    EnvironmentChange ifTrue:[
	self initNames
    ].
    ^ MonthAbbrevs at:monthIndex

    "
     Date abbreviatedNameOfMonth:11
     Date abbreviatedNameOfMonth:12
    "
!

dateAndTimeNow
    "return an array containing the date and time of now"

    ^ Time dateAndTimeNow

    "
     Date dateAndTimeNow
    "
!

dayOfWeek:dayName
    "given the name of a day (either string or symbol),
     return the day-index (1 for monday; 7 for sunday).
     Return 0 for invalid day name"

    EnvironmentChange ifTrue:[
	self initNames
    ].
    ^ DayNames indexOf:dayName

    "
     Date dayOfWeek:'wednesday'
    "
!

daysInMonth:month forYear:yearInteger
    "given the name of a month and a year, return the number 
     of days this month has (modified GNU).
     return 0 if the month name was invalid.
     For your convenience, month maybe an integer or name-string."

    |monthIndex "{ Class: SmallInteger }"|

    month isInteger ifTrue:[
	monthIndex := month
    ] ifFalse:[
	monthIndex := self indexOfMonth:month
    ].

    ^ self daysInMonthIndex:monthIndex forYear:yearInteger

    "
     Date daysInMonth:2 forYear:1980
     Date daysInMonth:2 forYear:1981
     Date daysInMonth:'feb' forYear:1981
    "
!

daysInYear:yearInteger
    "return the number of days in a year"

    (self leapYear:yearInteger) ifTrue:[^ 366].
    ^ 365

    "
     Date daysInYear:1900  
     Date daysInYear:1901 
     Date daysInYear:1904 
     Date daysInYear:1980 
     Date daysInYear:1981
    "
!

daysUntilMonth:month forYear:yearInteger
    "given the name of a month and a year, return the number 
     of days from 1st january to last of prev month of that year.
     Return 0 if the month name/index is invalid or is january.
     For your convenience, month maybe an integer or name-string."

    |monthIndex "{ Class: SmallInteger }"
     sumDays    "{ Class: SmallInteger }" |

    month isInteger ifTrue:[
	monthIndex := month
    ] ifFalse:[
	monthIndex := self indexOfMonth:month
    ].
    (monthIndex between:1 and:12) ifFalse:[^ 0].

    sumDays := 0.
    1 to:monthIndex-1 do:[:m |
	sumDays := sumDays + (self daysInMonthIndex:m forYear:yearInteger)
    ].
    ^ sumDays

    "
     Date daysUntilMonth:'feb' forYear:1993
     Date daysUntilMonth:'jan' forYear:1993
    "
!

indexOfMonth:aMonthString
    "given the name of a month (either string or symbol),
     return the month-index (1 for jan; 12 for december).
     The given string may be a full or abbreviated name,
     case is ignored.
     Return 0 for invalid month name."

    |idx name|

    EnvironmentChange ifTrue:[
	self initNames
    ].
    name := aMonthString asLowercase.
    idx := MonthAbbrevs indexOf:name.
    idx ~~ 0 ifTrue:[^ idx].
    idx := MonthNames indexOf:name.
    idx ~~ 0 ifTrue:[^ idx].

    name at:1 put:(name at:1) asUppercase.
    idx := MonthAbbrevs indexOf:name.
    idx ~~ 0 ifTrue:[^ idx].
    idx := MonthNames indexOf:name.
    idx ~~ 0 ifTrue:[^ idx].

    ^ idx

    "
     Date indexOfMonth:'jan'
     Date indexOfMonth:'Jan'
     Date indexOfMonth:'December'
    "
!

isLeapYear:yearInteger
    "Return true, if a year is a leap year.
     Obsolete:
	 Please use the ST-80 compatible #leapYear for new programs, 
	 since this method will vanish."

    ^ self leapYear:yearInteger
!

leapYear:yearInteger
    "return true, if yearInteger is a leap year."

    |y "{ Class: SmallInteger }"|

    y := yearInteger.
    (y \\ 4 == 0) ifTrue:[
	(y \\ 100 ~~ 0) ifTrue:[^ true].
	(y \\ 400 == 0) ifTrue:[^ true]
    ].
    ^ false

    "
     Date leapYear:1992
     Date leapYear:1994
     Date leapYear:1900
     Date leapYear:2000
    "
!

monthAndDayFromDayInYear:aDayInYear forYear:yearInteger
    "given a day-in-year (1..365) return an Array containing the
     month index and the day-in-month. Return nil if the argument is invalid."

    |restDays daysInMonth|

    restDays := aDayInYear.
    restDays < 1 ifTrue:[^ nil].

    1 to:12 do:[:m |
	daysInMonth := self daysInMonthIndex:m forYear:yearInteger.
	restDays <= daysInMonth ifTrue:[
	    ^ Array with:m with:restDays
	].
	restDays := restDays - daysInMonth 
    ].
    restDays > daysInMonth ifTrue:[^ nil].
    ^ Array with:12 with:restDays

    "
     Date monthAndDayFromDayInYear:66 forYear:1980
     Date monthAndDayFromDayInYear:66 forYear:1981
    "
!

nameOfDay:dayIndex
    "given a day index (1..7), return the name of the day" 

    EnvironmentChange ifTrue:[
	self initNames
    ].
    ^ DayNames at:dayIndex

    "
     Date nameOfDay:4
    "
!

nameOfMonth:monthIndex
    "given a month index (1..12), return the name of the month"

    EnvironmentChange ifTrue:[
	self initNames
    ].
    ^ MonthNames at:monthIndex

    "
     Date nameOfMonth:11
     Date nameOfMonth:12
     Date nameOfMonth:4
    "
!

yearAsDays: yearInteger
    "Returns the number of days since Jan 1, 1901. (GNU)
     to the first Jan of the year, yearInteger.
     For 1901 this is zero, for 1902 its 365.
     Defined for years >= 1901"

    |y "{ Class: SmallInteger }"|

    y := yearInteger - 1900.
    y := y - 1.
    ^ (y * 365)
	+ (y // 4)
	- (y // 100)
	+ ((y + 300) // 400)

    "
     Date yearAsDays:1901 
     Date yearAsDays:1902   
     Date yearAsDays:1903   
     Date yearAsDays:1904    
     Date yearAsDays:1905     
     Date yearAsDays:1994   
     (Date yearAsDays:2001) - (Date yearAsDays:2000)   
    "
! !

!Date  class methodsFor:'handling language changes'!

initialize
    "check for case where Resource-classes are absent"
    ResourcePack isNil ifTrue:[
	self initNames
    ] ifFalse:[
	Smalltalk addDependent:self.
	EnvironmentChange := true
    ]
!

update:something with:aParameter from:changedObject
    ((something == #Language) or:[something == #LanguageTerritory]) ifTrue:[
        "just remember change for next access"
        EnvironmentChange := true
    ]

    "Created: 15.6.1996 / 15:19:25 / cg"
! !

!Date  class methodsFor:'obsolete instance creation'!

day:day month:month year:year
    "return a new Date, given the day, month and year.
     Obsolete:
	use newDay:month:year: for ST-80 compatibility"

    ^ self newDay:day month:month year:year
!

day:dayInYear year:year
    "return a new Date, given the year and the day-in-year (starting at 1).
     Obsolete:
	use newDay:year: for ST-80 compatibility"

    ^ self newDay:dayInYear year:year
! !

!Date  class methodsFor:'private'!

daysInMonthIndex: monthIndex forYear: yearInteger
    "return the number of days in month monthIndex of
     year yearInteger (modified GNU).
     Return 0 for invalid month index.
     This is the internal version of daysInMonth:forYear:"

    |days|

    (monthIndex between:1 and:12) ifFalse:[^ 0].

    days := #(31 28 31           "Jan Feb Mar"
	      30 31 30           "Apr May Jun"
	      31 31 30           "Jul Aug Sep"
	      31 30 31           "Oct Nov Dec"
	     ) at: monthIndex.

    (monthIndex == 2) ifTrue:[
	(self leapYear:yearInteger) ifTrue:[
	    ^ days + 1
	]
    ].
    ^ days

    "
     Date daysInMonthIndex:2 forYear:1994
     Date daysInMonthIndex:2 forYear:1980
     Date daysInMonthIndex:2 forYear:1981
    "
!

initNames
    "read the language specific names"

    |resources|

    DayNames := #('monday'
		  'tuesday'
		  'wednesday'
		  'thursday'
		  'friday'
		  'saturday'
		  'sunday').

    DayAbbrevs := #('mon' 
		    'tue' 
		    'wed'
		    'thu' 
		    'fri' 
		    'sat' 
		    'sun').

    MonthNames := #('january'
		    'february'
		    'march'
		    'april'
		    'may'
		    'june'
		    'july'
		    'august'
		    'september'
		    'october'
		    'november'
		    'december').

    MonthAbbrevs := #('jan'
		      'feb'
		      'mar'
		      'apr'
		      'may'
		      'jun'
		      'jul'
		      'aug'
		      'sep'
		      'oct'
		      'nov'
		      'dec').

    "check for case where Resource-classes are absent"
    ResourcePack notNil ifTrue:[
	resources := ResourcePack for:self.

	DayNames := resources array:DayNames.
	DayAbbrevs := resources array:DayAbbrevs.
	MonthNames := resources array:MonthNames.
	MonthAbbrevs := resources array:MonthAbbrevs.
    ].

    EnvironmentChange := false

    "Date initNames"
! !

!Date  class methodsFor:'private encoding'!

encodeYear:y month:m day:d
    "the internal encoding is stricktly private, 
     and should not be used outside."

    ^ (((y * 100) + m) * 100) + d
! !

!Date  class methodsFor:'private instance creation'!

fromOSTime:osTime
    "return a date, representing the date given by the operatingSystem time.
     This somewhat clumsy implementation hides the OS's date representation
     (i.e. makes this class independent of what the OS starts its time values with).
     Dont use this method, the osTime representation is totally unportable."

    ^ self basicNew fromOSTime:osTime

    "
     Date fromOSTime:#(0 0)      -> on UNIX: this should return 1st Jan 1970
				    thats where Unix time starts
				    On other systems, it may be something different.

     Date fromOSTime:#(86400 0)  -> on UNIX: the day after
    "
! !

!Date methodsFor:'accessing'!

abbreviatedDayName
    "return the short week-day of the receiver as a string.
     The returned string depends on the language setting.
     Expect things like 'mon', 'tue' ..."

    ^ self class abbreviatedNameOfDay:(self dayInWeek)

    "
     Date today abbreviatedDayName
     (Date day:15 month:4 year:1959) abbreviatedDayName
    "
!

abbreviatedMonthName
    "return the month of the receiver as a string.
     The returned string depends on the language setting.
     Expect things like 'jan', 'feb' ..."

    ^ self class abbreviatedNameOfMonth:(self month)

    "
     Date today abbreviatedMonthName
     (Date day:15 month:4 year:1959) abbreviatedMonthName
    "
!

asDays
    "return the number of days elapsed since 01-Jan-1901
     and the receiver's day; starts with 0 for 1-1-1901.
     Date>>fromDays: is the reverse operation.
     For ST-80 compatibility."

    |yr|

    yr := self year.
    ^ (self class yearAsDays:yr)
      + (self class daysUntilMonth:self month forYear:yr)
      + self day
      - 1

    "
     (Date day: 5 month: 8 year: 1962) asDays  -> should be 22496
     (Date day: 1 month: 1 year: 1901) asDays  -> 0
     Date today asDays
     Date fromDays:(Date today asDays + 7)
    "
!

asSeconds
    "return the seconds between 1.jan.1901 and the same time in the receivers 
     day. (i.e. midnight to midnight).
     This does not include any leapSeconds ... strictly speaking, this is incorrect.
     ST-80 compatibility."

    ^ 60*60*24 * self asDays

    "
     (Date day: 5 month: 8 year: 1962) asSeconds
     (Date day: 1 month: 1 year: 1901) asSeconds
     (Date today addDays:7) asSeconds - Date today asSeconds
    "

    "Modified: 1.7.1996 / 14:19:17 / cg"
!

day
    "return the day (1..31) of the receiver"

    ^ dateEncoding \\ 100

    "
     Date today day
    "
!

dayCount
    "return the number of days since 1st. Jan. 1901;
     starting with 0 for this date.
     Date>>fromDays: is the reverse operation.
     Obsolete:
	 please use asDays for ST-80 compatibility"

    ^ self asDays.

    "
     (Date day:1 month:1 year:1901) dayCount
     Date fromDays:(Date day:1 month:1 year:1994) dayCount
     Date today dayCount
    "
!

dayInWeek
    "return the week-day of the receiver - 1 for monday, 7 for sunday"

    ^ (1 "know, that 1st Jan 1901 was a tuesday"
      + self asDays) \\ 7 + 1

    "
     Date today dayInWeek 
     (Date day:15 month:4 year:1959) dayInWeek 
     (Date day:1 month:1 year:1901) dayInWeek 
    "

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

dayName
    "return the week-day of the receiver as a string.
     The returned string depends on the language setting.
     Expect things like 'monday', 'tuesday' ...
     Obsolete:
	use #weekday for ST-80 compatibility"

    ^ self weekday
!

dayOfMonth
    "Answer the day of the month represented by me.
     Same as day; for ST-80 compatibility."

    ^ self day

    "
     Date today dayOfMonth
    "
!

daysInMonth
    "return the number of days in the month of the receiver"

    ^ Date daysInMonth:(self month) forYear:(self year)

    "
     Date today daysInMonth
    "
!

daysInYear
    "return the number of days in the year of the receiver"

    ^ Date daysInYear:(self year)

    "
     Date today daysInYear
    "
!

daysLeftInYear
    "return the number of days left in the year of the receiver"

    ^ Date daysInYear:(self year) - self day

    "
     Date today daysLeftInYear
    "
!

leap
    "return true, if the receivers year is a leap year"

    ^ Date leapYear:(self year)

    "
     Date today leap
     (Date day:1 month:1 year:1992) leap
    "
!

month
    "return the month (1..12) of the receiver"

    ^ (dateEncoding // 100) \\ 100

    "
     Date today month
    "
!

monthIndex
    "return the index of the month (e.g. Feb.=2).
     Same as month; for ST-80 compatibility."

    ^ self month  
!

monthName
    "return the month of the receiver as a string.
     The returned string depends on the language setting.
     Expect things like 'january', 'february' ..."

    ^ self class nameOfMonth:(self month)

    "
     Date today monthName
     (Date day:15 month:4 year:1959) monthName
    "
!

weekday
    "return the week-day of the receiver as a string.
     The returned string depends on the language setting.
     Expect things like 'monday', 'tuesday' ...
     For ST-80 compatibility"

    ^ self class nameOfDay:(self dayInWeek)

    "
     Date today weekday
     (Date day:15 month:4 year:1959) weekday
    "
!

year
    "return the year (1..12) of the receiver"

    ^ dateEncoding // (100*100)

    "
     Date today year
    "
! !

!Date methodsFor:'arithmetic'!

addDays:days
    "return a new date representing 'days' after the receiver.
     The argument should be some kind of integer.
     For ST-80 compatibility."

    ^ self class fromDays:(self asDays + days)

    "
     Date today addDays:7
    "
!

daysUntil:aDate
    "return the number of days between the receiver and the argument,
     aDate, whuch should be some kind of date"

    ^ aDate asDays - self asDays

    "
     (Date day:24 month:12 year:1994) daysUntil:(Date day:1 month:1 year:1995)
     (Date day:1 month:2 year:1992) daysUntil:(Date day:1 month:3 year:1992)
     (Date day:1 month:2 year:1994) daysUntil:(Date day:1 month:3 year:1994)
    
     |delta|
     delta := Date today
		daysUntil:(Date day:25 month:12 year:Date today year).
     Transcript show:'still ';
		show:delta ;
		showCR:' days till xmas'
    "
!

minusDays:days
    "return a new date representing 'days' before the receiver.
     The argument should be some kind of integer.
     Obsolete:
	 Please dont use this method since it will vanish.
	 Use #subtractDays: instead for ST-80 compatibility."

    ^ self subtractDays:days
!

plusDays:days
    "return a new date representing 'days' after the receiver.
     The argument should be some kind of integer.
     Obsolete:
	 Please dont use this method since it will vanish.
	 Use #addDays: instead for ST-80 compatibility."

    ^ self addDays:days
!

subtractDate:aDate
    "return the number of days between the receiver and aDate"

    ^ self asDays - aDate asDays

    "
    (Date day:1 month:1 year:1995) subtractDate:(Date day:24 month:12 year:1994)
    (Date day:1 month:3 year:1992) subtractDate:(Date day:1 month:2 year:1992)
    (Date day:1 month:3 year:1994) subtractDate:(Date day:1 month:2 year:1994)
    "
!

subtractDays:days
    "return a new date representing 'days' before the receiver.
     The argument should be some kind of integer.
     For ST-80 compatibility"

    ^ self class fromDays:(self asDays - days)

    "
     Date today subtractDays:7
    "
! !

!Date methodsFor:'comparing'!

< aDate
    "return true, if the date represented by the receiver
     is before the argument, aDate"

    (aDate isMemberOf:Date) ifTrue:[
	^ dateEncoding < aDate dateEncoding
    ].

    "the argument must understand year, month and day to be
     comparable, whatever it is"

    ^ dateEncoding < (Date encodeYear:aDate year
				month:aDate month
				  day:aDate day)

    "Date today < (Date day:24 month:12 year:2000)"
    "Date today < (Date day:24 month:12 year:1900)"
!

= aDate
    "return true, if the date represented by the receiver
     is the same as the one represented by argument, aDate"

    (aDate isMemberOf:Date) ifFalse:[^ false].

    ^ dateEncoding = aDate dateEncoding

    "the argument must understand year, month and day to be
     comparable, whatever it is"

"
    ^ dateEncoding = (Date encodeYear:aDate year
				month:aDate month
				  day:aDate day)
"
    "Date today = ((Date today plusDays:7) minusDays:7)"
!

> aDate
    "return true, if the date represented by the receiver
     is after the argument, aDate"

    (aDate isMemberOf:Date) ifTrue:[
	^ dateEncoding > aDate dateEncoding
    ].

    "the argument must understand year, month and day to be
     comparable, whatever it is"

    ^ dateEncoding > (Date encodeYear:aDate year
				month:aDate month
				  day:aDate day)

    "Date today > (Date day:24 month:12 year:2000)"
    "Date today > (Date day:24 month:12 year:1900)"
!

hash
    "return an integer useful for hashing on dates"

    ^ dateEncoding
! !

!Date methodsFor:'converting'!

asAbsoluteTime
    "return an absoluteTime instance, representing midnight of
     last night"

    ^ AbsoluteTime day:(self day) month:(self month) year:(self year)
                   hour:0 minutes:0 seconds:0 milliseconds:0

    "
     Date today asAbsoluteTime
    "

    "Modified: 1.7.1996 / 14:53:32 / cg"
! !

!Date methodsFor:'printing & storing'!

printFormat:aFormatArray
    "return a string containing a printed representation of the receiver.
     The formatArray argument consists of 6 or 7 integers which control
     the resulting string. The entries are:
     1   day position (1, 2 or 3)
     2   month position (1..3)
     3   year position (1..3)
     4   asciiValue of separator character or separator character
	 or collection of 2 separator characters
     5   month format (1: numeric; 2: abbreviated name; 3: fullname
	 4: abbreviated uppercase 5: full uppercase)
     6   year format (1 include century; 2 exclude century)
     7 (optional) true/false of true, print numbers in 2-digit format
		  False, if ommited

     Day and monthnames are in the currently active language.

     This method supports more options than the ST-80 version; month formats 
     4 and 5, non-numeric separators and the optional 7th parameter are not 
     supported by ST-80. Be aware of this for compatibility reasons.

     Notice that not all formats can be scanned (correctly) by #readFrom:"

    |mf upperCase day month year components sep1 sep2 leadingZeros|

    aFormatArray size > 6 ifTrue:[
	leadingZeros := aFormatArray at:7
    ] ifFalse:[
	leadingZeros := false.
    ].

    components := Array new:3.
    day := self day.
    leadingZeros ifTrue:[
	day := day printStringLeftPaddedTo:2 with:$0 
    ] ifFalse:[
	day := day printString
    ].
    components at:(aFormatArray at:1) put:day.

    upperCase := false.
    mf := aFormatArray at:5.
    (mf between:4 and:5) ifTrue:[mf := mf - 2. upperCase := true].

    mf == 1 ifTrue:[
	month := self month.
	leadingZeros ifTrue:[
	    month := month printStringLeftPaddedTo:2 with:$0 
	] ifFalse:[
	    month := month printString
	]
    ] ifFalse:[
	mf == 2 ifTrue:[
	    month := self abbreviatedMonthName
	] ifFalse:[
	    month := self monthName
	]
    ].
    upperCase ifTrue:[month := month asUppercase].
    components at:(aFormatArray at:2) put:month.

    year := self year.
    (aFormatArray at:6) == 2 ifTrue:[
	year := year \\ 100
    ].
    components at:(aFormatArray at:3) put:year printString.

    sep1 := (aFormatArray at:4).
    sep1 isCollection ifTrue:[
	sep2 := (sep1 at:2).
	sep1 := (sep1 at:1).
    ] ifFalse:[
	sep1 := sep2 := sep1 asCharacter
    ].

    ^ (components at:1)
      , sep1 asString
      , (components at:2)
      , sep2 asString
      , (components at:3)

    "
     Date today printFormat:#(1 2 3 $- 1 2)   
     Date today printFormat:#(1 2 3 $. 1 2 true)  
     Date today printFormat:#(2 1 3 32 3 1)  
     Date today printFormat:#(2 1 3 #(' ' ', ') 3 1)  
     Date today printFormat:#(1 2 3 $- 2 1)    
     Date today printFormat:#(1 2 3 $- 4 1)    
    "
!

printFormat:aFormatArray on:aStream
    "append a printed representation of the receiver to aStream.
     The argument, aFormatString controls the format, as described
     in the #printFormat: method.
     Notice that not all formats can be scanned (correctly) by #readFrom:"

    aStream nextPutAll:(self printFormat:aFormatArray)


    "european formats:

     Date today printFormat:#(1 2 3 $- 1 2) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $- 2 2) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $- 2 1) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $- 3 2) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $. 1 2) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $. 1 2 true) on:Transcript. Transcript cr.

     US formats:

     Date today printFormat:#(2 1 3 32 3 1) on:Transcript. Transcript cr.
     Date today printFormat:#(2 1 3 32 2 2) on:Transcript. Transcript cr.
     Date today printFormat:#(2 1 3 $/ 1 2) on:Transcript. Transcript cr.
     Date today printFormat:#(2 1 3 $- 2 1) on:Transcript. Transcript cr.
     Date today printFormat:#(2 1 3 #(' ' ', ') 2 1) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $- 2 1) on:Transcript. Transcript cr.
     Date today printFormat:#(1 2 3 $- 4 1) on:Transcript. Transcript cr.
    "
!

printOn:aStream
    "append a printed representation of the receiver to aStream"

    self printFormat:#(1 2 3 $- 2 1) on:aStream

    "
     Date today printOn:Transcript
     Date today printNL
    "

    "Modified: 27.8.1995 / 01:01:49 / claus"
!

storeOn:aStream
    "append a representation to aStream, from which the receiver
     can be reconstructed"

    aStream nextPutAll:'('; nextPutAll:'Date day:'.
    self day printOn:aStream.
    aStream nextPutAll:' month:'.
    self month printOn:aStream.
    aStream nextPutAll:' year:'.
    self year printOn:aStream.
    aStream nextPutAll:')'

    "
     Date today storeOn:Transcript
     Date today storeString
    "
! !

!Date methodsFor:'private accessing'!

dateEncoding
    "the internal encoding is stricktly private, 
     and should not be used outside."

    ^ dateEncoding
!

dateEncoding:anInteger
    "the internal encoding is stricktly private, 
     and should not be used outside."

    dateEncoding := anInteger
!

fromOSTime:osTime
    "set my dateEncoding from an OS time.
     This somewhat clumsy implementation hides the OS's date representation
     (i.e. makes this class independent of what the OS starts its time values with).
     Dont use this method, the osTime representation is totally unportable."

    OperatingSystem computeDatePartsOf:osTime 
                                   for:[:year :month :day |
        dateEncoding := (((year * 100) + month) * 100) + day
    ]

    "Modified: 1.7.1996 / 15:23:12 / cg"
! !

!Date  class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libbasic/Date.st,v 1.37 1996-10-08 18:33:03 cg Exp $'
! !
Date initialize!