--- a/Timestamp.st Fri Jan 25 11:37:50 2013 +0100
+++ b/Timestamp.st Fri Jan 25 14:34:35 2013 +0100
@@ -618,6 +618,182 @@
!Timestamp class methodsFor:'reading'!
+readFrom:aStringOrStream format:formatString language:languageOrNil onError:exceptionalValue
+ "return a new Timestamp, reading a printed representation from aStream using a formatString.
+ The formatString is similar to the one used when printing.
+ On error, exceptionalValue is returned. If exceptionalValue is a one-arg block, an error message is
+ passed as argument.
+ format:
+ %h hours, 00..23 (i.e. european) 0-padded to length 2
+ %u hours, 00..12 (i.e. us) 0-padded to length 2
+ %m minutes, 00..59 0-padded to length 2
+ %s seconds, 00..59 0-padded to length 2
+ %i milliseconds, 000..999 0-padded to length 3
+ %a am/pm
+
+ %d - day
+ %D - day
+ %(day) - day
+
+ %m - month
+ %M - month
+ %(month) - month
+
+ %(monthName) - monthName
+
+ %(year) - year, full 4 digits
+ %Y - year, last 2 digits only,
+ 0..71 map to 2000..2071;
+ 72..99 map to 1972..1999;
+ %Y1900 - year, last 2 digits only, map to 1900..1999
+ %Y2000 - year, last 2 digits only, map to 2000..2099
+
+ an optional length after the % gives a field length;
+ i.e. %2h%2m%2s parses 123557 as 12:35:37
+"
+
+ |day month year
+ hour minute second millisecond
+ utcOffset inStream formatStream error fChar format itemHandler
+ len now s|
+
+ error := [:msg |
+ exceptionalValue isBlock ifTrue:[
+ ^ exceptionalValue valueWithOptionalArgument:'format error; space expcected'
+ ] ifFalse:[
+ ^ exceptionalValue value
+ ].
+ ].
+
+ itemHandler := [:format |
+ |input|
+ input := len isNil ifTrue:[ inStream ] ifFalse:[ inStream next: len ].
+
+ ( #('d' 'D' 'day' ) includes:format ) ifTrue:[
+ day := Integer readFrom:input onError:[ error value:'invalid day' ].
+
+ ] ifFalse:[ ( format = 'month' ) ifTrue:[
+ month := Integer readFrom:input onError:[ error value:'invalid month' ].
+
+ ] ifFalse:[ ( format = 'year' or:[ format = 'y' ]) ifTrue:[
+ year := Integer readFrom:input onError:[ error value:'invalid year' ].
+
+ ] ifFalse:[ ( format = 'Y' ) ifTrue:[
+ year := Integer readFrom:input onError:[ error value:'invalid year' ].
+ (year between:0 and: 99) ifFalse:[ error value:'invalid year' ].
+ (year between:0 and:71) ifTrue:[
+ year := year + 1900
+ ] ifFalse:[
+ year := year + 2000
+ ]
+
+ ] ifFalse:[ (format = 'monthName') ifTrue:[
+ s := input nextMatching:[:c | c isLetter] thenMatching:[:c | c isLetter].
+ month := Date indexOfMonth:s asLowercase language:languageOrNil
+
+ ] ifFalse:[ ( format = 'Y1900' ) ifTrue:[
+ year := Integer readFrom:input onError:[ error value:'invalid year' ].
+ (year between:0 and: 99) ifFalse:[ error value:'invalid year' ].
+ year := year + 1900
+
+ ] ifFalse:[ ( format = 'Y2000' ) ifTrue:[
+ year := Integer readFrom:input onError:[ error value:'invalid year' ].
+ (year between:0 and: 99) ifFalse:[ error value:'invalid year' ].
+ year := year + 2000
+
+ ] ifFalse:[ ( format = 'h' or:[ format = 'H' ]) ifTrue:[
+ hour := Integer readFrom:input onError:[ error value:'invalid hour' ].
+
+ ] ifFalse:[ ( format = 'u' or:[ format = 'U']) ifTrue:[
+ hour := Integer readFrom:input onError:[ error value:'invalid hour' ].
+
+ ] ifFalse:[ ( format = 'm' or:[ format = 'M' ]) ifTrue:[
+ minute := Integer readFrom:input onError:[ error value:'invalid minute' ].
+
+ ] ifFalse:[ ( format = 's' or:[ format = 'S' ]) ifTrue:[
+ second := Integer readFrom:input onError:[ error value:'invalid second' ].
+
+ ] ifFalse:[ ( format = 'i' or:[ format = 'I' ]) ifTrue:[
+ millisecond := Integer readFrom:input onError:[ error value:'invalid month' ].
+
+ ] ifFalse:[ ( format = 'tz' ) ifTrue:[
+ utcOffset := self utcOffsetFrom:input.
+ utcOffset isNil ifTrue:[ error value:'invalid timezone' ]
+
+ ] ifFalse:[ ( format = 'a' ) ifTrue:[
+ s := (input next:2) asLowercase.
+ s = 'am' ifTrue:[
+ (hour between:0 and:12) ifFalse:[ error value:'invalid hour' ]
+ ] ifFalse:[
+ s = 'pm' ifTrue:[
+ (hour between:1 and:12) ifFalse:[ error value:'invalid hour' ].
+ hour := hour + 12.
+ ] ifFalse:[
+ error value:'invalid am/pm'
+ ]
+ ]
+
+ ] ifFalse:[
+ error value:'unhandled format:',format
+ ]]]]]]]]]]]]]]
+ ].
+
+ hour := 0.
+ minute := 0.
+ second := 0.
+ millisecond := 0.
+
+ inStream := aStringOrStream readStream.
+ formatStream := formatString readStream.
+
+ [formatStream atEnd] whileFalse:[
+ fChar := formatStream next.
+ fChar = Character space ifTrue:[
+ inStream peek isSeparator ifFalse:[ error value: 'format error; space expcected' ].
+ inStream skipSeparators.
+ ] ifFalse:[
+ fChar == $% ifTrue:[
+ len := nil.
+ (formatStream peek isDigit) ifTrue:[
+ len := Integer readFrom:formatStream onError:[ error value: 'format error; invalid length' ]
+ ].
+ (formatStream peek == $() ifTrue:[
+ formatStream next.
+ format := formatStream upTo:$).
+ ] ifFalse:[
+ (formatStream peek == ${) ifTrue:[
+ formatStream next.
+ format := formatStream upTo:$}.
+ ] ifFalse:[
+ (formatStream peek isLetter) ifTrue:[
+ format := formatStream nextAlphaNumericWord.
+ ] ifFalse:[
+ error value:'unhandled format:',formatStream peek
+ ]
+ ]
+ ].
+ itemHandler value:format.
+ ] ifFalse:[
+ inStream peek = fChar ifFalse:[^ error value: 'format error; ',fChar,' expcected'].
+ inStream next.
+ ]
+ ].
+ ].
+
+ year isNil ifTrue:[
+ year := (now := Timestamp now) year
+ ].
+
+ ^ (self year:year month:month day:day hour:(hour ? 0) minute:(minute ? 0) second:(second ? 0) millisecond:millisecond) - utcOffset
+
+ "
+ Timestamp readFrom:'20-2-1995 13:11:06' format:'%day-%month-%year %h:%m:%s' language:nil onError:[self halt]
+ Timestamp readFrom:'20021995131106' format:'%2d%2month%4y%2h%2m%2s' language:nil onError:[self halt]
+ Timestamp readFrom:'March 7 2009 7:30pm EST' format:'%monthName %day %year %u:%m%a %tz' language:#en onError:[self halt]
+ Timestamp readFrom:'March 7 2009 7:30pm UTC' format:'%monthName %day %year %u:%m%a %tz' language:#en onError:[self halt]
+ "
+!
+
readFrom:aStringOrStream onError:exceptionBlock
"return a new Timestamp, reading a printed representation from aStream.
The string is interpreted as 24 hour format, as printed.
@@ -919,8 +1095,6 @@
!
readRFC1123FormatFrom:rfc1123String onError:exceptionBlock
- |parts indexModifier utcOffsetString utcOffset day year time monthName month|
-
"/ All HTTP/1.0 date/time stamps must be represented in Universal Time (UT),
"/ also known as Greenwich Mean Time (GMT), without exception.
"/ This is indicated in the first two formats by the inclusion of "GMT" as the three-letter abbreviation for time zone,
@@ -953,6 +1127,15 @@
"/ | "Sep" | "Oct" | "Nov" | "Dec"
"/
"/ Mon, 17 Aug 2009 11:11:15 GMT
+"/
+"/ however, occasionally, someone presents us with non-UTC strings which include a timezone;
+"/ thus, this also supports:
+"/ Mon, 17 Aug 2009 11:11:15 +xxxx
+"/ Mon, 17 Aug 2009 11:11:15 -xxxx
+"/ and:
+"/ Mon, 17 Aug 2009 11:11:15 PST
+
+ |parts indexModifier utcOffsetString utcOffset day year time monthName month|
rfc1123String isEmptyOrNil ifTrue:[^ exceptionBlock value].
@@ -967,18 +1150,8 @@
].
].
- utcOffset := 0.
utcOffsetString := (parts at:6 + indexModifier).
- ((utcOffsetString sameAs:'GMT') or:[utcOffsetString sameAs:'UTC']) ifFalse:[
- self assert:utcOffsetString size == 5.
-
- utcOffset := (utcOffsetString from:4 to:5) asString asNumber * 60.
- utcOffset := utcOffset + ((utcOffsetString from:2 to:3) asString asNumber * 60 * 60).
-
- (utcOffsetString at:1) asSymbol == #- ifTrue:[
- utcOffset := -1 * utcOffset.
- ].
- ].
+ utcOffset := (self utcOffsetFrom:utcOffsetString) ? 0.
day := Integer readFrom:(parts at:2 + indexModifier) onError:[^ exceptionBlock].
year := Integer readFrom:(parts at:4 + indexModifier) onError:[^ exceptionBlock].
@@ -991,9 +1164,307 @@
^ (self
fromDate:(Date newDay:day monthIndex:month year:year)
- andTime:time) + utcOffset
+ andTime:time) - utcOffset
+
+ "
+ self readRFC1123FormatFrom:'Mon, 17 Aug 2009 11:11:15 PST' onError:nil
+ self readRFC1123FormatFrom:'Mon, 17 Aug 2009 11:11:15 PDT' onError:nil
+ self readRFC1123FormatFrom:'Mon, 17 Aug 2009 11:11:15 UTC' onError:nil
+ self readRFC1123FormatFrom:'Mon, 17 Aug 2009 11:11:15 +0100' onError:nil
+ "
"Modified: / 05-10-2010 / 16:05:32 / cg"
+!
+
+utcOffsetFrom:aStringOrStream
+ "return the utcOffset (in seconds) for a given time-zone name.
+ Returns nil for invalid formats"
+
+ |table i offset stream tzName sign|
+
+ table :=
+ #(
+ 'GMT' 0
+ 'UTC' 0
+
+ "/ US
+ 'AST' -4 "/ atlantic
+ 'ADT' -3
+
+ 'AKST' -8 "/ alaska
+ 'AKDT' -9
+
+ 'CST' -6 "/ central
+ 'CDT' -5
+
+ 'EST' -5 "/ eastern
+ 'EDT' -4
+
+ 'EGST' 0 "/ east greenland
+ 'EGT' -1
+
+ 'HADT' -9 "/ hawaii
+ 'HAST' -10
+
+ 'MST' -7 "/ mountain
+ 'MDT' -6
+
+ 'NST' -3.5 "/ new foundland
+ 'NDT' -2.5
+
+ 'PST' -8 "/ pacific
+ 'PDT' -7
+
+ 'PMST' -3 "/ pierre & miquelon
+ 'PMDT' -2
+
+ 'WGST' -2 "/ west greenland
+ 'WGT' -3
+
+ "/ europe
+ 'CET' 1 "/ central european
+ 'CEST' 2
+
+ 'EET' 2 "/ east european
+ 'EEST' 3
+
+ 'WET' 0 "/ west european
+ 'WEST' 1
+
+ 'MSK' 4 "/ moscow european
+ 'MSD' 4
+
+ "/ pacific
+ 'NZST' 12 "/ new zealand
+ 'NZDT' 13
+
+ "/ south america
+ 'ART' -3 "/ argentina
+ 'BOT' -4 "/ bolivia
+ 'BRT' -3 "/ brasilia
+ 'BRST' -2
+ 'CLT' -4 "/ chile
+ 'CLST' -3
+ 'ECT' -5 "/ equador
+ 'PET' -5 "/ peru
+ 'PYT' -4 "/ paraguay
+ 'UYT' -3 "/ uruguay
+ 'VET' -4.5 "/ venezuela
+
+ "/ africa
+ 'CAT' 2 "/ central africa
+ 'EAT' 3 "/ east africa
+ 'SAST' 2 "/ south africa
+ 'WAT' 1 "/ west africa
+ 'WAST' 2
+ 'WT' 0 "/ west sahara
+ 'WST' 1
+
+ 'HKT' 8 "/ hongkong
+ 'IST' 5.5 "/ india
+ 'JST' 9 "/ japan
+ 'KST' 9 "/ korea
+ 'SGT' 8 "/ singapore
+
+ "/ military
+ 'A' 1
+ 'B' 2
+ 'C' 3
+ 'D' 4
+ 'E' 5
+ 'F' 6
+ 'G' 7
+ 'H' 8
+ 'I' 9
+ 'K' 10
+ 'L' 11
+ 'M' 12
+ 'N' -1
+ 'O' -2
+ 'P' -3
+ 'Q' -4
+ 'R' -5
+ 'S' -6
+ 'T' -7
+ 'U' -8
+ 'V' -9
+ 'W' -10
+ 'X' -11
+ 'Y' -12
+ ).
+
+ stream := aStringOrStream readStream.
+ stream skipSeparators.
+
+ stream peek isLetter ifTrue:[
+ tzName := stream upToMatching:[:ch | ch isLetter not].
+
+ i := table indexOf:tzName.
+ i ~~ 0 ifTrue:[
+ ^ (table at:i+1) * 60 * 60
+ ].
+ ] ifFalse:[
+ sign := 1.
+ stream peek == $- ifTrue:[
+ sign := -1.
+ stream next.
+ ] ifFalse:[
+ stream peek == $+ ifTrue:[
+ sign := 1.
+ stream next.
+ ] ifFalse:[
+ stream skipSeparators
+ ]
+ ].
+ offset := ((stream next:2) asNumber * 60 * 60).
+ offset := offset + ((stream next:2) asNumber * 60).
+ ^ offset * sign
+ ].
+
+ ^ nil
+
+ "
+ self utcOffsetFrom:'UTC'
+ self utcOffsetFrom:'PST'
+ self utcOffsetFrom:'EST'
+ self utcOffsetFrom:'+0130'
+ "
+!
+
+utcOffsetFromString:aString
+ "return the utcOffset (in seconds) for a given time-zone name.
+ Returns nil for invalid formats"
+
+ |table i offset|
+
+ table :=
+ #(
+ 'GMT' 0
+ 'UTC' 0
+
+ "/ US
+ 'AST' -4 "/ atlantic
+ 'ADT' -3
+
+ 'AKST' -8 "/ alaska
+ 'AKDT' -9
+
+ 'CST' -6 "/ central
+ 'CDT' -5
+
+ 'EST' -5 "/ eastern
+ 'EDT' -4
+
+ 'EGST' 0 "/ east greenland
+ 'EGT' -1
+
+ 'HADT' -9 "/ hawaii
+ 'HAST' -10
+
+ 'MST' -7 "/ mountain
+ 'MDT' -6
+
+ 'NST' -3.5 "/ new foundland
+ 'NDT' -2.5
+
+ 'PST' -8 "/ pacific
+ 'PDT' -7
+
+ 'PMST' -3 "/ pierre & miquelon
+ 'PMDT' -2
+
+ 'WGST' -2 "/ west greenland
+ 'WGT' -3
+
+ "/ europe
+ 'CET' 1 "/ central european
+ 'CEST' 2
+
+ 'EET' 2 "/ east european
+ 'EEST' 3
+
+ 'WET' 0 "/ west european
+ 'WEST' 1
+
+ 'MSK' 4 "/ moscow european
+ 'MSD' 4
+
+ "/ pacific
+ 'NZST' 12 "/ new zealand
+ 'NZDT' 13
+
+ "/ south america
+ 'ART' -3 "/ argentina
+ 'BOT' -4 "/ bolivia
+ 'BRT' -3 "/ brasilia
+ 'BRST' -2
+ 'CLT' -4 "/ chile
+ 'CLST' -3
+ 'ECT' -5 "/ equador
+ 'PET' -5 "/ peru
+ 'PYT' -4 "/ paraguay
+ 'UYT' -3 "/ uruguay
+ 'VET' -4.5 "/ venezuela
+
+ "/ africa
+ 'CAT' 2 "/ central africa
+ 'EAT' 3 "/ east africa
+ 'SAST' 2 "/ south africa
+ 'WAT' 1 "/ west africa
+ 'WAST' 2
+ 'WT' 0 "/ west sahara
+ 'WST' 1
+
+ 'HKT' 8 "/ hongkong
+ 'IST' 5.5 "/ india
+ 'JST' 9 "/ japan
+ 'KST' 9 "/ korea
+ 'SGT' 8 "/ singapore
+
+ "/ military
+ 'A' 1
+ 'B' 2
+ 'C' 3
+ 'D' 4
+ 'E' 5
+ 'F' 6
+ 'G' 7
+ 'H' 8
+ 'I' 9
+ 'K' 10
+ 'L' 11
+ 'M' 12
+ 'N' -1
+ 'O' -2
+ 'P' -3
+ 'Q' -4
+ 'R' -5
+ 'S' -6
+ 'T' -7
+ 'U' -8
+ 'V' -9
+ 'W' -10
+ 'X' -11
+ 'Y' -12
+ ).
+
+ i := table indexOf:aString.
+ i ~~ 0 ifTrue:[ ^ (table at:i+1) * 60 * 60 ].
+
+ aString size == 5 ifTrue:[
+ offset := (aString copyFrom:4 to:5) asNumber * 60.
+ offset := offset + ((offset copyFrom:2 to:3) asNumber * 60 * 60).
+ (aString at:1) == $- ifTrue:[
+ offset := offset negated.
+ ].
+ ^ offset
+ ].
+
+ ^ nil
+
+ "
+ self utcOffsetFrom:'UTC'
+ "
! !
!Timestamp methodsFor:'accessing'!
@@ -2699,11 +3170,12 @@
!Timestamp class methodsFor:'documentation'!
version
- ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.142 2012-10-24 22:42:11 cg Exp $'
+ ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.143 2013-01-25 13:34:35 cg Exp $'
!
version_CVS
- ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.142 2012-10-24 22:42:11 cg Exp $'
+ ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.143 2013-01-25 13:34:35 cg Exp $'
! !
+
Timestamp initialize!