Timestamp.st
changeset 16914 c550a2e2359d
parent 16901 8394ec484888
child 16915 2174b3784836
--- a/Timestamp.st	Wed Nov 05 17:49:21 2014 +0100
+++ b/Timestamp.st	Wed Nov 05 21:27:34 2014 +0100
@@ -19,7 +19,8 @@
 !
 
 Object subclass:#TimestampBuilderAbstract
-	instanceVariableNames:'year month day hour minute second millisecond isUtcTime'
+	instanceVariableNames:'year month day hour minute second millisecond isUtcTime
+		yearAlreadyRead'
 	classVariableNames:''
 	poolDictionaries:''
 	privateIn:Timestamp
@@ -360,8 +361,13 @@
 basicReadFrom:aStream
     "return a new Timestamp, reading a printed representation from aStream.
      The string is interpreted as 24 hour format, as printed.
+
      Notice, that this is not the storeString format and 
      is different from the format expected by readFrom:.
+     This is more or less a heuristic attempt to read any reasonable format.
+     If the input starts with an integer > 31, it is assumed to be a year and the rest
+     is read in iso8601 format.
+
      KLUDGE: 
         us and non-us format have different ordering of day and month;
         The format read here is (non-us) dd-mm-yyyy hh:mm:ss.iii
@@ -439,7 +445,8 @@
     ].
     ^ self year:year month:month day:day hour:hour minute:min second:sec millisecond:millis.
 
-    "
+    "some add hoc formats:
+
      Timestamp basicReadFrom:'20-2-1995 13:11:06' readStream   
      Timestamp basicReadFrom:'20-2-1995 13:11:06.' readStream   
      (Timestamp basicReadFrom:'10-9-1995 13:11:06' readStream) month   
@@ -456,6 +463,8 @@
      Timestamp basicReadFrom:'20-2-1995 24:01:00.100' readStream 
      Timestamp basicReadFrom:'20-2-1995 24:00:01.100' readStream 
      Timestamp basicReadFrom:'foo' readStream                    
+
+     any iso8601 format:.
      Timestamp basicReadFrom:(Timestamp now printString readStream)                  
      Timestamp basicReadFrom:'1995-10-20 24:00:00.000' readStream 
      Timestamp basicReadFrom:'1995-10-20 12:10:00.000' readStream 
@@ -476,124 +485,149 @@
      Missing minute, second and ms values are replaced with 0;
      i.e. 1999T12 is the same as 1999-01-01T12:00:00.000.
      Partial hours, minutes, seconds are allowed at the end,
-     decimal separtors are both $. and $, .
+     decimal separators are both $. and $, .
      Of course, a 24 hour clock is used.
      On error, raise an exception.
-     Please use this format for all external representations - its the standard."
-
-    |str day month year hour min sec millis fraction isUtcTime peekChar|
-
-    str := aStringOrStream readStream.
-
-    month := day := 1.
-    hour := millis := 0.
-    isUtcTime := false.
-
-    yearOrNil notNil ifTrue:[
-        year := yearOrNil
-    ] ifFalse:[
-        year := Integer readFrom:str onError:nil.
-        year isNil ifTrue:[ TimeConversionError raiseErrorString:' - bad year' ]
-    ].
-
-    str skipSeparators.
-    str peek == $- ifTrue:[
-        str next.
-        "/ month follows.
-        month := Integer readFrom:str.
-        (month between:1 and:12) ifFalse:[ TimeConversionError raiseErrorString:' - bad month' ].
-
-        str skipSeparators.
-        str peek == $- ifTrue:[
-            str next.
-            "/ day follows.
-            day := Integer readFrom:str.
-            (day between:1 and:31) ifFalse:[ TimeConversionError raiseErrorString:' - bad day' ].
-        ].
-    ].
-
-    str skipSeparators.
-    str atEnd ifFalse:[
-        "time follows"
-
-        str peek == $T ifTrue:[
-            "we treat the T as optional here"
-            str next.
-            str skipSeparators.
-        ].
-        hour := Integer readFrom:str onError:-1.
-        (hour between:0 and:24) ifFalse:[ TimeConversionError raiseErrorString:' - bad hour' ].
-        str skipSeparators.
-        str peekOrNil == $: ifTrue:[
-            str next.
-            "/ minutes follow.
-            min := Integer readFrom:str onError:-1.
-            (min between:0 and:59) ifFalse:[ TimeConversionError raiseErrorString:' - bad minute' ].
-            str skipSeparators.
-            str peekOrNil == $: ifTrue:[
-                str next.
-                "/ seconds follow.
-                sec := Integer readFrom:str onError:-1.
-                (sec between:0 and:59) ifFalse:[ TimeConversionError raiseErrorString:' - bad seconds' ].
-                str skipSeparators.
-            ].
-        ].
-
-        peekChar := str peekOrNil.
-        (peekChar == $. or:[peekChar == $,]) ifTrue:[
-            str next.
-            "/ decimals follow.
-            fraction := Number readMantissaFrom:str radix:10.
-            min isNil ifTrue:[
-                min := 60 * fraction.
-                fraction := min fractionPart.
-                min := min truncated.
-            ].
-            (sec isNil and:[fraction ~= 0])ifTrue:[
-                sec := 60 * fraction.
-                fraction := sec fractionPart.
-                sec := sec truncated.
-            ].
-            fraction ~= 0 ifTrue:[
-                millis := (1000 * fraction) rounded.  "/ mhmh - should it be truncated ?
-            ].
-        ].
-        
-        peekChar := str peekOrNil.
-        peekChar notNil ifTrue:[
-            peekChar == $Z ifTrue:[
-                str next.
-                isUtcTime := true.
-            ]
-"/ Todo
-"/            ifFalse:[peekChar == $+ ifTrue:[
+     Please use this format for all external representations - it's the standard."
+
+    "/ changed to use the new reader
+    ^ TimestampISO8601Builder 
+        read:aStringOrStream withClass:self 
+        yearAlreadyReadAs:yearOrNil
+
+"/    |str day month dayInWeek week year hour min sec tmpDay millis fraction isUtcTime peekChar ch|
+"/
+"/    str := aStringOrStream readStream.
+"/
+"/    month := day := 1.
+"/    hour := millis := 0.
+"/    isUtcTime := false.
+"/
+"/    yearOrNil notNil ifTrue:[
+"/        year := yearOrNil
+"/    ] ifFalse:[
+"/        year := Integer readFrom:str onError:nil.
+"/        year isNil ifTrue:[ TimeConversionError raiseErrorString:' - bad year' ]
+"/    ].
+"/
+"/    str skipSeparators.
+"/    (((ch := str peek) == $-) or:[ch == $W]) ifTrue:[
+"/        (ch == $-) ifTrue:[ str next ].
+"/        str peek == $W ifTrue:[
+"/            str next.
+"/
+"/            "/ week follows
+"/            week := Integer readFrom:str onError:-1.
+"/            (week between:1 and:53) ifFalse:[ TimeConversionError raiseErrorString:' - bad week' ].
+"/
+"/            str skipSeparators.
+"/            str peek == $- ifTrue:[
+"/                str next.
+"/                "/ day follows.
+"/                dayInWeek := Integer readFrom:str onError:-1.
+"/                (dayInWeek between:1 and:7) ifFalse:[ TimeConversionError raiseErrorString:' - bad day in week' ].
+"/            ] ifFalse:[
+"/                dayInWeek := 1.
+"/            ].
+"/            tmpDay := Date newDayInWeek:dayInWeek week:week year:year.
+"/            day := tmpDay day.
+"/            month := tmpDay month.
+"/            year := tmpDay year.
+"/        ] ifFalse:[
+"/            "/ month follows.
+"/            month := Integer readFrom:str onError:-1.
+"/            (month between:1 and:12) ifFalse:[ TimeConversionError raiseErrorString:' - bad month' ].
+"/
+"/            str skipSeparators.
+"/            str peek == $- ifTrue:[
+"/                str next.
+"/                "/ day follows.
+"/                day := Integer readFrom:str onError:-1.
+"/                (day between:1 and:31) ifFalse:[ TimeConversionError raiseErrorString:' - bad day' ].
+"/            ].
+"/        ].
+"/    ].
+"/
+"/    str skipSeparators.
+"/    str atEnd ifFalse:[
+"/        "time follows"
+"/
+"/        str peek == $T ifTrue:[
+"/            "we treat the T as optional here"
+"/            str next.
+"/            str skipSeparators.
+"/        ].
+"/        hour := Integer readFrom:str onError:-1.
+"/        (hour between:0 and:24) ifFalse:[ TimeConversionError raiseErrorString:' - bad hour' ].
+"/        str skipSeparators.
+"/        str peekOrNil == $: ifTrue:[
+"/            str next.
+"/            "/ minutes follow.
+"/            min := Integer readFrom:str onError:-1.
+"/            (min between:0 and:59) ifFalse:[ TimeConversionError raiseErrorString:' - bad minute' ].
+"/            str skipSeparators.
+"/            str peekOrNil == $: ifTrue:[
+"/                str next.
+"/                "/ seconds follow.
+"/                sec := Integer readFrom:str onError:-1.
+"/                (sec between:0 and:59) ifFalse:[ TimeConversionError raiseErrorString:' - bad seconds' ].
+"/                str skipSeparators.
+"/            ].
+"/        ].
+"/
+"/        peekChar := str peekOrNil.
+"/        (peekChar == $. or:[peekChar == $,]) ifTrue:[
+"/            str next.
+"/            "/ decimals follow.
+"/            fraction := Number readMantissaFrom:str radix:10.
+"/            min isNil ifTrue:[
+"/                min := 60 * fraction.
+"/                fraction := min fractionPart.
+"/                min := min truncated.
+"/            ].
+"/            (sec isNil and:[fraction ~= 0])ifTrue:[
+"/                sec := 60 * fraction.
+"/                fraction := sec fractionPart.
+"/                sec := sec truncated.
+"/            ].
+"/            fraction ~= 0 ifTrue:[
+"/                millis := (1000 * fraction) rounded.  "/ mhmh - should it be truncated ?
+"/            ].
+"/        ].
+"/        
+"/        peekChar := str peekOrNil.
+"/        peekChar notNil ifTrue:[
+"/            peekChar == $Z ifTrue:[
 "/                str next.
 "/                isUtcTime := true.
-"/            ] ifFalse:[peekChar == $- ifTrue:[
-"/                str next.
-"/                isUtcTime := true.
-"/            ]]]
-        ].
-    ].
-
-    min isNil ifTrue:[min := 0].
-    sec isNil ifTrue:[sec := 0].
-
-    "special check - only 24:00:00 is allowed;
-     every time after that must wrap"
-    hour == 24 ifTrue:[
-        (min ~~ 0 or:[sec ~~ 0 or:[millis ~~ 0]]) ifTrue:[ TimeConversionError raiseErrorString:' - bad hour' ].
-    ].
-
-    isUtcTime ifTrue:[
-        ^ self 
-            UTCYear:year month:month day:day 
-            hour:hour minute:min second:sec millisecond:millis.
-    ] ifFalse:[
-        ^ self 
-            year:year month:month day:day 
-            hour:hour minute:min second:sec millisecond:millis.
-    ]
+"/            ] ifFalse:[
+"/                ((peekChar == $+) or:[peekChar == $- ]) ifTrue:[
+"/                    str next.
+"/                    self halt.
+"/                    isUtcTime := true.
+"/                ]
+"/            ]
+"/        ].
+"/    ].
+"/
+"/    min isNil ifTrue:[min := 0].
+"/    sec isNil ifTrue:[sec := 0].
+"/
+"/    "special check - only 24:00:00 is allowed;
+"/     every time after that must wrap"
+"/    hour == 24 ifTrue:[
+"/        (min ~~ 0 or:[sec ~~ 0 or:[millis ~~ 0]]) ifTrue:[ TimeConversionError raiseErrorString:' - bad hour' ].
+"/    ].
+"/
+"/    isUtcTime ifTrue:[
+"/        ^ self 
+"/            UTCYear:year month:month day:day 
+"/            hour:hour minute:min second:sec millisecond:millis.
+"/    ] ifFalse:[
+"/        ^ self 
+"/            year:year month:month day:day 
+"/            hour:hour minute:min second:sec millisecond:millis.
+"/    ]
 
     "
      Timestamp readIso8601FormatFrom:'1995-02-20T13:11:06.123'    
@@ -608,6 +642,13 @@
      Timestamp readIso8601FormatFrom:'1995T13.333333'              
      Timestamp readIso8601FormatFrom:'1995'              
 
+     Timestamp readIso8601FormatFrom:'2014W40'   -> 29.sep.2014     
+     Timestamp readIso8601FormatFrom:'2014W44-4' -> 30.oct.2014     
+     Timestamp readIso8601FormatFrom:'2014W1'    -> 30.dec.2013      !!!!!! (this week starts in the previous year)     
+     Timestamp readIso8601FormatFrom:'2014W1-1'  -> same 30.dec.2013 !!!!!! (this week starts in the previous year)     
+     Timestamp readIso8601FormatFrom:'2014W1-2'  -> 31.dec.2013      !!!!!! (this week starts in the previous year)     
+     Timestamp readIso8601FormatFrom:'2014W1-3'  -> 1.jan.2014       !!!!!! (this week starts in the previous year)     
+
      Timestamp readIso8601FormatFrom:'1995-02-20 13:11:06'    
      Timestamp readIso8601FormatFrom:'1995-02-20 13:11:06Z'    
 
@@ -637,9 +678,9 @@
 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:
+     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
@@ -664,9 +705,11 @@
         %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;
+     an optional length after the % gives a field length;
         i.e. %2h%2m%2s parses 123557 as 12:35:37
-"
+
+     Please consider using a standard format, such as iso8601.
+    "
 
     |day month year
      hour minute second millisecond
@@ -815,7 +858,10 @@
      The string is interpreted as 24 hour format, as printed.
      Notice, that this is not the storeString format and 
      is different from the format expected by readFrom:.
-     The format read here is dd-mm-yyyy hh:mm:ss.iii"
+     The format read here is either dd-mm-yyyy hh:mm:ss.iii
+     or iso8601 (if the first integer is >31).
+
+     Please consider using a standard format, such as iso8601."
 
     |stream newTime|
 
@@ -857,9 +903,12 @@
      This format is used for BER specification of the ASN.1 GeneralizedTime as defined in X.208 Sec. 33,
      so read this before changing anything here.
 
+     New applications should consider using a standard format, such as iso8601.
+
      Notice, that this is not the storeString format and 
      is different from the format expected by readFrom:.
     "
+
     ^ self 
         readGeneralizedFrom:aStringOrStream
         short:false
@@ -881,6 +930,8 @@
      This format is used for BER specification of the ASN.1 GeneralizedTime as defined in X.208 Sec. 33,
      so read this before changing anything here.
 
+     New applications should consider using a standard format, such as iso8601.
+
      Notice, that this is not the storeString format and 
      is different from the format expected by readFrom:.
     "
@@ -939,6 +990,8 @@
      UTCTime as defined in X.208 Sec. 33, so read this before changing anything here.
      The short form is no longer recommended.
 
+     New applications should consider using a standard format, such as iso8601.
+
      Notice, that this is not the storeString format and 
      is different from the format expected by readFrom:.
     "
@@ -1056,13 +1109,31 @@
     "Modified: / 22-08-2006 / 12:30:11 / cg"
 !
 
-readISO8601From: stringOrStream
-
-    ^ TimestampISO8601Builder read:stringOrStream withClass:self
+readISO8601From:aStringOrStream
+    "Please use this format for all external representations - it's the standard."
+
+    "using the new reader"
+
+    ^ TimestampISO8601Builder read:aStringOrStream withClass:self
 
     "Created: / 16-06-2005 / 16:13:36 / masca"
 !
 
+readISO8601From:aStringOrStream onError:exceptionValue
+    "Please use this format for all external representations - it's the standard."
+
+    "using the new reader"
+
+    |retVal|
+
+    ConversionError handle:[:ex |
+        retVal := exceptionValue value
+    ] do:[
+        retVal := TimestampISO8601Builder read:aStringOrStream withClass:self
+    ].
+    ^ retVal
+!
+
 readIso8601FormatFrom:aStringOrStream
     "return a new Timestamp, reading an iso8601 UTC representation from aStream.
      Missing month/day values are replaced with 1; i.e. 1999T24:00
@@ -1071,10 +1142,14 @@
      i.e. 1999T12 is the same as 1999-01-01T12:00:00.000.
      Of course, a 24 hour clock is used.
      On error, raise an exception.
-     Please use this format for all external representations - its the standard."
-
-    ^ self
-        readIso8601FormatFrom:aStringOrStream yearAlreadyRead:nil
+
+     Please use this format for all external representations - it's the standard."
+
+    "changed to use the new reader"
+
+    ^ TimestampISO8601Builder read:aStringOrStream withClass:self 
+"/    ^ self
+"/        readIso8601FormatFrom:aStringOrStream yearAlreadyRead:nil
 
     "
      Timestamp readIso8601FormatFrom:'1995-02-20T13:11:06'    
@@ -1102,54 +1177,70 @@
      i.e. 1999T12 is the same as 1999-01-01T12:00:00.000.
      Of course, a 24 hour clock is used.
      On error, raise an exception.
-     Please use this format for all external representations - its the standard."
-
-    ^ self
-        readIso8601FormatFrom:aStringOrStream 
-        yearAlreadyRead:nil 
-        onError:exceptionValue
+
+     Please use this format for all external representations - it's the standard."
+
+    "/ changed to use the new reader
+    |retVal|
+
+    ConversionError handle:[:ex |
+        retVal := exceptionValue value
+    ] do:[
+        retVal := TimestampISO8601Builder read:aStringOrStream withClass:self
+    ].
+    ^ retVal
+
+"/    ^ self
+"/        readIso8601FormatFrom:aStringOrStream 
+"/        yearAlreadyRead:nil 
+"/        onError:exceptionValue
 !
 
 readRFC1123FormatFrom:rfc1123String onError:exceptionBlock
-"/    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, 
-"/    and should be assumed when reading the asctime format.
-"/
-"/    HTTP-date      = rfc1123-date | rfc850-date | asctime-date
-"/
-"/    rfc1123-date   = wkday "," SP date1 SP time SP "GMT"
-"/    rfc850-date    = weekday "," SP date2 SP time SP "GMT"
-"/    asctime-date   = wkday SP date3 SP time SP 4DIGIT
-"/
-"/    date1          = 2DIGIT SP month SP 4DIGIT
-"/                     ; day month year (e.g., 02 Jun 1982)
-"/    date2          = 2DIGIT "-" month "-" 2DIGIT
-"/                     ; day-month-year (e.g., 02-Jun-82)
-"/    date3          = month SP ( 2DIGIT | ( SP 1DIGIT ))
-"/                     ; month day (e.g., Jun  2)
-"/
-"/    time           = 2DIGIT ":" 2DIGIT ":" 2DIGIT
-"/                     ; 00:00:00 - 23:59:59
-"/
-"/    wkday          = "Mon" | "Tue" | "Wed"
-"/                   | "Thu" | "Fri" | "Sat" | "Sun"
-"/
-"/    weekday        = "Monday" | "Tuesday" | "Wednesday"
-"/                   | "Thursday" | "Friday" | "Saturday" | "Sunday"
-"/
-"/    month          = "Jan" | "Feb" | "Mar" | "Apr"
-"/                   | "May" | "Jun" | "Jul" | "Aug"
-"/                   | "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
+    "please use this only for http-requests.
+     All other programs should use iso8601, which is the standard for times and dates.
+
+     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, 
+     and should be assumed when reading the asctime format.
+
+     HTTP-date      = rfc1123-date | rfc850-date | asctime-date
+
+     rfc1123-date   = wkday ',' SP date1 SP time SP 'GMT'
+     rfc850-date    = weekday ',' SP date2 SP time SP 'GMT'
+     asctime-date   = wkday SP date3 SP time SP 4DIGIT
+
+     date1          = 2DIGIT SP month SP 4DIGIT
+                      ; day month year (e.g., 02 Jun 1982)
+     date2          = 2DIGIT '-' month '-' 2DIGIT
+                      ; day-month-year (e.g., 02-Jun-82)
+     date3          = month SP ( 2DIGIT | ( SP 1DIGIT ))
+                      ; month day (e.g., Jun  2)
+
+     time           = 2DIGIT ':' 2DIGIT ':' 2DIGIT
+                      ; 00:00:00 - 23:59:59
+
+     wkday          = 'Mon' | 'Tue' | 'Wed'
+                    | 'Thu' | 'Fri' | 'Sat' | 'Sun'
+
+     weekday        = 'Monday' | 'Tuesday' | 'Wednesday'
+                    | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday'
+
+     month          = 'Jan' | 'Feb' | 'Mar' | 'Apr'
+                    | 'May' | 'Jun' | 'Jul' | 'Aug'
+                    | '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|
 
@@ -2406,12 +2497,12 @@
     minutes isZero ifFalse: [
         minute := minute + minutes.
         minute >= 60 ifTrue: [
-            hours := hours + minute // 60.
-            minute := minute \\ 60.
+            hours := hours + 1.
+            minute := minute - 60.
         ].
         minute < 0 ifTrue: [
-            hours := hours + minute // 60.
-            minute := (minute \\ 60) negated
+            hours := hours - 1.
+            minute := minute + 60.
         ]
     ].
 
@@ -2466,14 +2557,16 @@
     adjust the year. Both week and day are 1-based, the first week in a year is the one
     with thursday (or the one containing 4.1.)."
 
+    |tmpDate|
+
     "Check numbers. Year may be checked if it contains 53 weeks or 52 weeks only."
     (dayInteger between: 1 and: 7) ifFalse: [self malformed: 'Bad weekday number: ' , dayInteger printString].
     (weekInteger between: 1 and: 53) ifFalse: [self malformed: 'Bad week number: ' , weekInteger printString].
 
-    self shouldImplement
-
-    "Created: / 15-06-2005 / 11:29:42 / masca"
-    "Modified: / 15-06-2005 / 16:42:33 / masca"
+    tmpDate := Date newDayInWeek:dayInteger week:weekInteger year:year.
+    day := tmpDate day.
+    month := tmpDate month.
+    year := tmpDate year.
 !
 
 isAllowedDay: anInteger
@@ -2517,6 +2610,13 @@
 
     "Created: / 15-06-2005 / 15:39:24 / masca"
     "Modified: / 30-06-2005 / 16:48:25 / masca"
+!
+
+yearAlreadyReadAs:yearArg
+    "support for readers which may have already preread the year"
+
+    year := yearArg.
+    yearAlreadyRead := true.
 ! !
 
 !Timestamp::TimestampISO8601Builder class methodsFor:'documentation'!
@@ -2563,6 +2663,8 @@
         Timestamp readISO8601From: (TimestampISO8601Builder print: Timestamp now)
         UtcTimestamp readISO8601From: (TimestampISO8601Builder print: UtcTimestamp now)
         Timestamp readISO8601From: (TimestampISO8601Builder print: UtcTimestamp now)
+
+    Timestamp readISO8601From:'fooBar' onError:[ Timestamp now ]. 
 "
 !
 
@@ -2570,14 +2672,6 @@
     "Created: / 16-06-2005 / 16:28:38 / masca"
 ! !
 
-!Timestamp::TimestampISO8601Builder class methodsFor:'parsing'!
-
-read: stringOrStream withClass:timestampClass
-    ^ self new read:stringOrStream withClass:timestampClass
-
-    "Created: / 15-06-2005 / 17:52:03 / masca"
-! !
-
 !Timestamp::TimestampISO8601Builder class methodsFor:'printing'!
 
 print: aTimestamp
@@ -2645,6 +2739,24 @@
     "Created: / 15-06-2005 / 17:54:17 / masca"
 ! !
 
+!Timestamp::TimestampISO8601Builder class methodsFor:'public parsing'!
+
+read: stringOrStream withClass:timestampClass
+    ^ self new read:stringOrStream withClass:timestampClass
+
+    "Created: / 15-06-2005 / 17:52:03 / masca"
+!
+
+read: stringOrStream withClass:timestampClass yearAlreadyReadAs:yearArg
+    "support for readers which may have already preread the year"
+
+    ^ self new 
+        yearAlreadyReadAs:yearArg;
+        read:stringOrStream withClass:timestampClass
+
+    "Created: / 15-06-2005 / 17:52:03 / masca"
+! !
+
 !Timestamp::TimestampISO8601Builder class methodsFor:'testing'!
 
 test
@@ -2832,7 +2944,7 @@
     "Modified: / 15-06-2005 / 15:54:29 / masca"
 ! !
 
-!Timestamp::TimestampISO8601Builder methodsFor:'processing'!
+!Timestamp::TimestampISO8601Builder methodsFor:'public processing'!
 
 read:stringOrStream withClass:timestampClass
     | peek |
@@ -2841,6 +2953,7 @@
 
     month := day := 1.
     hour := minute := second := millisecond := 0.
+    isUtcTime := false.
 
     "Read the year. This will read and swallow up to four year digits."
     self readYear.
@@ -2897,6 +3010,28 @@
 
 !Timestamp::TimestampISO8601Builder methodsFor:'reading'!
 
+readFraction
+    "Read an arbitrary number of digits representing a fraction."
+
+    | anyDigit digit factor fraction |
+
+    factor := (1 / 10).
+    fraction := 0.
+    anyDigit := false.
+
+    [
+        digit := self nextDigit.
+        digit >= 0
+    ] whileTrue: [
+        anyDigit := true.
+        fraction := digit * factor + fraction.
+        factor := (factor / 10)
+    ].
+
+    anyDigit ifFalse: [self malformed: 'Missing digits after fraction separator'].
+    ^ fraction
+!
+
 readMilliseconds
     "Read an arbitrary number of digits representing milliseconds. As the timestamp can
     hold only integer amounts of milliseconds, don't mind the rest of the digits."
@@ -2959,25 +3094,70 @@
 readTime
     "Date read, don't mind it. Read only the time value."
 
-    | peek |
+    | peek f |
 
     hour := self nextDigits: 2.
     (hour between: 0 and: 24) ifFalse: [self malformed: 'Bad hour: ' , hour printString].
 
     peek := stream peekOrNil.
-    peek = $:
-        ifTrue: [stream next]
-        ifFalse: [(peek notNil and: [peek isDigit]) ifFalse: [^self]].
-
-    minute := self nextDigits: 2.
+    peek isNil ifTrue: [^self].
+    (peek == $:) ifTrue: [
+        "/ read minutes
+        stream next.
+        minute := self nextDigits: 2.
+    ] ifFalse: [
+        peek isDigit ifTrue: [
+            "/ read minutes
+            minute := self nextDigits: 2.
+        ] ifFalse:[
+            (peek == $. or:[peek == $,]) ifTrue:[
+                stream next.
+                minute := self readFraction * 60.
+            ] ifFalse:[
+                ^ self.
+            ].
+        ]
+    ].
+
+    minute isInteger ifFalse:[
+        f := minute.
+        minute := f truncated.
+        second := (f - minute) * 60.
+        second isInteger ifFalse:[
+            f := second.
+            second := f truncated.
+            millisecond := (f - second) * 1000.
+            millisecond := millisecond rounded.
+        ].
+    ].
     (minute between: 0 and: 59) ifFalse: [self malformed: 'Bad minute: ' , minute printString].
 
     peek := stream peekOrNil.
-    peek = $:
-        ifTrue: [stream next]
-        ifFalse: [(peek notNil and: [peek isDigit]) ifFalse: [^self]].
-
-    second := self nextDigits: 2.
+    peek isNil ifTrue: [^self].
+    (peek == $:) ifTrue: [
+        "/ read seconds
+        stream next.
+        second := self nextDigits: 2.
+    ] ifFalse: [
+        peek isDigit ifTrue: [
+            "/ read seconds
+            second := self nextDigits: 2.
+        ] ifFalse:[
+            (peek == $. or:[peek == $,]) ifTrue:[
+                stream next.
+                second := self readFraction * 60.
+            ] ifFalse:[
+                ^ self.
+            ].
+        ]
+    ].
+
+    second isInteger ifFalse:[
+        f := second.
+        second := f truncated.
+        millisecond := (f - second) * 1000.
+        millisecond := millisecond rounded.
+    ].
     (second between: 0 and: 59) ifFalse: [
         "Seconds are usually in this range, do a special check for leap seconds."
         second <= 61
@@ -2990,7 +3170,7 @@
     ].
 
     "Hour, minute and second read. Read appendices."
-    stream peekOrNil = $.
+    ((peek := stream peekOrNil) == $. or:[peek == $,])
         ifTrue: [
             "Read dot. Skip it and read milliseconds."
             stream next.
@@ -3083,7 +3263,8 @@
 
 readWeekNumber
 
-    | week day digit |
+    | week dayInWeek digit |
+
     "Read week number. It is always two digits long."
     week := self nextDigits: 2.
 
@@ -3093,14 +3274,16 @@
             stream next.
             digit := self nextDigit.
             digit < 0 ifTrue: [self malformed: 'Bad weekday number'].
+            digit > 7 ifTrue: [self malformed: 'Bad weekday number'].
             self dateFromWeek: week andWeekday: digit.
             ^self].
 
     "Read day number that follows the week. If the number is not given, consider it monday."
-    day := self nextDigit.
-    day <= 0 ifTrue: [day := 1].
-
-    self dateFromWeek: week andWeekday: day
+    dayInWeek := self nextDigit.
+    dayInWeek <= 0 ifTrue: [dayInWeek := 1].
+    dayInWeek > 7 ifTrue: [self malformed: 'Bad weekday number'].
+
+    self dateFromWeek: week andWeekday: dayInWeek
 
     "Created: / 14-06-2005 / 12:06:47 / masca"
     "Modified: / 15-06-2005 / 15:53:34 / masca"
@@ -3152,11 +3335,11 @@
 !Timestamp class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.157 2014-10-18 11:17:48 cg Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.158 2014-11-05 20:27:34 cg Exp $'
 !
 
 version_CVS
-    ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.157 2014-10-18 11:17:48 cg Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/Timestamp.st,v 1.158 2014-11-05 20:27:34 cg Exp $'
 ! !