#FEATURE by cg
authorClaus Gittinger <cg@exept.de>
Thu, 10 May 2018 20:26:50 +0200
changeset 22873 cc5d5daffdfa
parent 22872 eca075dc2a34
child 22874 fbe961c2662c
#FEATURE by cg class: Timestamp added: #UTCyear:month:day:hour:minute:second:millisecond:additionalPicoseconds: #exactMicroseconds #exactMilliseconds #exactMinutes #exactNanoseconds #exactSeconds #setMicrosecond: #setMillisecond: #setMilliseconds:additionalPicoseconds: #setNanosecond: #year:month:day:hour:minute:second:millisecond:additionalPicoseconds: comment/format in: #getMilliseconds #inspectorExtraAttributes #microseconds #millisecond #milliseconds #nanoseconds #seconds category of: #osTime: class: Timestamp class added: #UTCYear:month:day:hour:minute:second:millisecond:additionalPicoseconds: #fromDate:hour:minute:second: #fromDate:hour:minute:second:microsecond: #fromDate:hour:minute:second:millisecond: #fromDate:hour:minute:second:nanosecond: #year:month:day:hour:minute:second:microsecond: #year:month:day:hour:minute:second:millisecond:additionalPicoseconds: comment/format in: #documentation #newDay:month:year: changed: #basicReadFrom: #readFrom:format:language:onError: class: Timestamp::TimestampBuilderAbstract class definition changed: #timestampWithClass: class: Timestamp::TimestampBuilderAbstract class added: #documentation #examples class: Timestamp::TimestampISO8601Builder comment/format in: #read:withClass: #readFraction #readTime #readTimezone #readTimezoneOffset #readWeekNumber changed: #readMilliseconds
Timestamp.st
--- a/Timestamp.st	Thu May 10 20:26:02 2018 +0200
+++ b/Timestamp.st	Thu May 10 20:26:50 2018 +0200
@@ -24,7 +24,7 @@
 
 Object subclass:#TimestampBuilderAbstract
 	instanceVariableNames:'year month day hour minute second millisecond isUtcTime
-		hasTimezone yearAlreadyRead utcOffset'
+		hasTimezone yearAlreadyRead utcOffset picos'
 	classVariableNames:''
 	poolDictionaries:''
 	privateIn:Timestamp
@@ -114,6 +114,8 @@
         Notice: 
             the picos are to be added to the millis, to get picos within the second.
             this is ugly, but makes all the rest backward compatible.
+            Also, most timestamps only require/have millisecond resolution,
+            so the pico instvar is nil/0 and does not require an aditional largeInteger operation.
 
         The typical OS-time resolution is in the milli- or microsecond range.
         External logging hardware may generate timestamps in the micro- or nanosecond range.
@@ -163,6 +165,29 @@
     "Modified: / 13.7.1999 / 12:42:30 / stefan"
 !
 
+UTCYear:y month:m day:d hour:h minute:min second:s millisecond:millis additionalPicoseconds:picos
+    "return an instance of the receiver, given individual components,
+     interpreted in the UTC timezone."
+
+    ^ self basicNew
+        UTCyear:y month:m day:d hour:h minute:min second:s millisecond:millis
+        additionalPicoseconds:picos
+
+    "
+     Timestamp UTCYear:1970 month:1 day:1 hour:0 minute:0 second:0 millisecond:0
+     Timestamp UTCYear:1991 month:1 day:2 hour:12 minute:30 second:0 millisecond:0
+     Timestamp UTCYear:1991 month:1 day:2 hour:12 minute:30 second:0 millisecond:100
+     Timestamp UTCYear:1999 month:7 day:1 hour:1 minute:0 second:0 millisecond:0
+     Timestamp UTCYear:2000 month:1 day:1 hour:1 minute:0 second:0 millisecond:0
+
+     UtcTimestamp UTCYear:2000 month:1 day:1 hour:1 minute:0 second:0 millisecond:0
+    "
+
+    "Modified: / 1.7.1996 / 15:22:07 / cg"
+    "Created: / 13.7.1999 / 12:34:37 / stefan"
+    "Modified: / 13.7.1999 / 12:42:30 / stefan"
+!
+
 decodeFromLiteralArray:anArray
     "decode a Timestamp literalArray.
 
@@ -255,6 +280,78 @@
     "Modified: / 13.7.1999 / 12:30:47 / stefan"
 !
 
+fromDate:aDate hour:hour minute:minute second:second
+    "return an instance of the receiver, initialized from a time and a date object.
+     See also `Timestamp now' and other protocol inherited from my superclass."
+
+    ^ self fromDate:aDate hour:hour minute:minute second:second millisecond:0
+
+    "
+     Timestamp fromDate:(Date today) andTime:(Time now)
+     Timestamp fromDate:(Date today) hour:10 minute:5 second:30
+    "
+!
+
+fromDate:aDate hour:hour minute:minute second:second microsecond:micros
+    "return an instance of the receiver, initialized from a time and a date object.
+     See also `Timestamp now' and other protocol inherited from my superclass."
+
+    ^ (self
+        year:aDate year
+        month:aDate month
+        day:aDate day
+        hour:hour
+        minute:minute
+        second:second
+        millisecond:0
+      ) setMicrosecond:micros
+
+    "
+     Timestamp fromDate:(Date today) hour:10 minute:5 second:30 microsecond:123456
+     Timestamp fromDate:(Date today) hour:10 minute:5 second:30 microsecond:140
+    "
+!
+
+fromDate:aDate hour:hour minute:minute second:second millisecond:millis
+    "return an instance of the receiver, initialized from a time and a date object.
+     See also `Timestamp now' and other protocol inherited from my superclass."
+
+    ^ self
+        year:aDate year
+        month:aDate month
+        day:aDate day
+        hour:hour
+        minute:minute
+        second:second
+        millisecond:millis
+
+    "
+     Timestamp fromDate:(Date today) andTime:(Time now)
+     Timestamp fromDate:(Date today) andTime:(Time nowWithMilliseconds)
+     Timestamp fromDate:(Date today) hour:10 minute:5 second:30 millisecond:140
+    "
+!
+
+fromDate:aDate hour:hour minute:minute second:second nanosecond:nanos
+    "return an instance of the receiver, initialized from a time and a date object.
+     See also `Timestamp now' and other protocol inherited from my superclass."
+
+    ^ (self
+        year:aDate year
+        month:aDate month
+        day:aDate day
+        hour:hour
+        minute:minute
+        second:second
+        millisecond:0
+      ) setNanosecond:nanos
+
+    "
+     Timestamp fromDate:(Date today) hour:10 minute:5 second:30 microsecond:123456
+     Timestamp fromDate:(Date today) hour:10 minute:5 second:30 microsecond:140
+    "
+!
+
 newDay:dayInYear year:year
     "return a new Timestamp, given the year and the day-in-year (starting at 1).
      Date protocol compatibility"
@@ -371,6 +468,18 @@
     "Modified: / 13.7.1999 / 12:27:47 / stefan"
 !
 
+year:y month:m day:d hour:h minute:min second:s microsecond:micros
+    "return an instance of the receiver, given individual components.
+     See also `Timestamp now' and other protocol inherited
+     from my superclass."
+
+    ^ (self year:y month:m day:d hour:h minute:min second:s) setMicrosecond:micros
+
+    "
+     Timestamp year:1991 month:1 day:2 hour:12 minute:30 second:0 microsecond:100
+    "
+!
+
 year:y month:m day:d hour:h minute:min second:s millisecond:millis
     "return an instance of the receiver, given individual components.
      See also `Timestamp now' and other protocol inherited
@@ -389,6 +498,28 @@
     "Modified: / 1.7.1996 / 15:22:07 / cg"
     "Created: / 13.7.1999 / 12:28:44 / stefan"
     "Modified: / 13.7.1999 / 12:37:57 / stefan"
+!
+
+year:y month:m day:d hour:h minute:min second:s millisecond:millis additionalPicoseconds:picos
+    "return an instance of the receiver, given individual components.
+     See also `Timestamp now' and other protocol inherited
+     from my superclass."
+
+    ^ self basicNew
+            year:y month:m day:d hour:h minute:min second:s 
+            millisecond:millis additionalPicoseconds:picos 
+
+    "
+     Timestamp year:1970 month:1 day:1 hour:0 minute:0 second:0
+     Timestamp year:1991 month:1 day:2 hour:12 minute:30 second:0
+     Timestamp year:1991 month:1 day:2 hour:12 minute:30 second:0 millisecond:100
+     Timestamp year:2000 month:7 day:1 hour:1 minute:0 second:0
+     UtcTimestamp year:2000 month:7 day:1 hour:1 minute:0 second:0
+    "
+
+    "Modified: / 1.7.1996 / 15:22:07 / cg"
+    "Created: / 13.7.1999 / 12:28:44 / stefan"
+    "Modified: / 13.7.1999 / 12:37:57 / stefan"
 ! !
 
 
@@ -489,17 +620,19 @@
 
 newDay:day month:month year:year
     <resource: #obsolete>
+
     "return a new Timestamp, given the year, month and day (starting at 1).
-     Date protocol compatibility"
+     Date protocol compatibility.
+     Obsolete: use year:month:day:."
 
     ^ self
-	year:year
-	month:month
-	day:day
-	hour:0
-	minute:0
-	second:0
-	millisecond:0
+        year:year
+        month:month
+        day:day
+        hour:0
+        minute:0
+        second:0
+        millisecond:0
 
     "
      Timestamp newDay:1 month:1 year:1996
@@ -523,7 +656,8 @@
         or (us-format, for Travis) mm/dd/yyyy hh:mm:ss.iii.
      On error, raise an exception"
 
-    |monthOrYear firstNumber secondNumber day month year hour min sec millis usFormat possibeMonthName ch utcOffsetOrNil count|
+    |monthOrYear firstNumber secondNumber day month year hour min sec 
+     millis usFormat possibeMonthName ch utcOffsetOrNil count mantissa fraction picos ts|
 
     count := 0.
     monthOrYear := aStream throughAnyForWhich:[:ch | count := count+1. ch isDigit and:[count <= 4]].
@@ -560,6 +694,8 @@
     [(ch := aStream peekOrNil) notNil and:[ch isDigit not]] whileTrue:[aStream next].
     year := Integer readFrom:aStream onError:[ TimeConversionError raiseErrorString:' - bad year' ].
 
+    picos := 0.
+
     aStream atEnd ifTrue:[
         hour := min := sec := millis := 0.
     ] ifFalse:[
@@ -578,10 +714,19 @@
             sec := Integer readFrom:aStream onError:-1.
             (sec between:0 and:59) ifFalse:[ TimeConversionError raiseErrorString:' - bad second' ].
 
-            aStream peek == $. ifTrue:[
+            (aStream peek == $. or:[aStream peek == $,]) ifTrue:[
                 aStream next.
-                millis := Integer readFrom:aStream onError:0.
-                millis >= 1000 ifTrue:[ TimeConversionError raiseErrorString:' - bad millisecond' ].
+                mantissa := Number readMantissaAndScaleFrom:aStream radix:10.
+                fraction := (mantissa at:2) / (10 raisedTo:(mantissa at:3)).
+                (mantissa at:3) > 3 ifTrue:[
+                    picos := fraction * (1000 * 1000 * 1000 * 1000).
+                    millis := picos // (1000 * 1000 * 1000).
+                    picos := picos \\ (1000 * 1000 * 1000).
+                ] ifFalse:[
+                    millis := fraction * 1000.
+                ].
+                "/ millis := Integer readFrom:aStream onError:0.
+                "/ millis >= 1000 ifTrue:[ TimeConversionError raiseErrorString:' - bad millisecond' ].
             ] ifFalse:[
                 millis := 0.
             ].
@@ -607,7 +752,9 @@
                 addSeconds:utcOffsetOrNil.
     ].
     "/ a local timestamp
-    ^ self year:year month:month day:day hour:hour minute:min second:sec millisecond:millis.
+    ts := self year:year month:month day:day hour:hour minute:min second:sec millisecond:millis.
+    picos ~~ 0 ifTrue:[ ts additionalPicoseconds:picos ].
+    ^ ts
 
     "some ad hoc formats:
 
@@ -874,6 +1021,7 @@
         %Y2000          - year, last 2 digits only, map to 2000..2099
         %Y1950          - year, last 2 digits only, map to 1950..2049
         %Y1980          - year, last 2 digits only, map to 1980..2079
+        %Y1970          - year, last 2 digits only, map to 1970..2069
 
      an optional length after the % gives a field length;
         i.e. %2h%2m%2s parses '123557' as 12:35:37
@@ -948,6 +1096,15 @@
                 year := year + 1900
             ]  
 
+        ] ifFalse:[ ( format sameAs:  'Y1970' ) ifTrue:[
+            year := Integer readFrom:input onError:[ error value:'invalid year' ].
+            (year between:0 and: 99) ifFalse:[ error value:'invalid year' ].
+            (year between:0 and: 69) ifTrue:[ 
+                year := year + 2000
+            ] ifFalse:[    
+                year := year + 1900
+            ]  
+
         ] ifFalse:[ ( format sameAs:  'Y2000' ) ifTrue:[
             year := Integer readFrom:input onError:[ error value:'invalid year' ].
             (year between:0 and: 99) ifFalse:[ error value:'invalid year' ].
@@ -991,7 +1148,7 @@
 
         ] ifFalse:[
             error value:'unhandled format:',format
-        ]]]]]]]]]]]]]]]]]]
+        ]]]]]]]]]]]]]]]]]]]
     ].
 
     hour := 0.
@@ -1884,6 +2041,145 @@
     "Created: / 20-01-2011 / 12:28:46 / cg"
 !
 
+exactMicroseconds
+    "return the exact microseconds within the stamp's second (0 .. 999.999...) as a fixedPoint number.
+     notice: 
+        that is NOT the total number of microseconds,
+        but the fractional part (within the second) only.
+     A fixedPoint number holds the exact value, but prints itself rounded!!"
+
+    |millis microsFromMillis|
+
+    millis := (osTime \\ 1000).
+    microsFromMillis := millis * 1000.
+    additionalPicoseconds notNil ifTrue:[
+        ^ microsFromMillis + (FixedPoint numerator:additionalPicoseconds denominator:(1000*1000) scale:3)
+    ].
+    ^ microsFromMillis.
+
+    "
+     |ts|
+
+     ts := Timestamp nowWithMicroseconds.
+     Transcript showCR:ts.
+     Transcript showCR:ts microseconds.
+     Transcript showCR:ts exactMicroseconds.
+     Transcript showCR:ts nanoseconds.
+     Transcript showCR:ts picoseconds.
+    "
+!
+
+exactMilliseconds
+    "return the exact milliseconds within the stamp's second (0 .. 999.999...) as a fixedPoint number.
+     notice: 
+        that is NOT the total number of microseconds,
+        but the fractional part (within the second) only.
+     A fixedPoint number holds the exact value, but prints itself rounded!!"
+
+    |millis|
+
+    millis := (osTime \\ 1000).
+    additionalPicoseconds notNil ifTrue:[
+        ^ millis + (FixedPoint numerator:additionalPicoseconds denominator:(1000*1000*1000) scale:3)
+    ].
+    ^ millis.
+
+    "
+     |ts|
+
+     ts := Timestamp nowWithMicroseconds.
+     Transcript showCR:ts.
+     Transcript showCR:ts milliseconds.
+     Transcript showCR:ts exactMilliseconds.
+     Transcript showCR:ts microseconds.
+     Transcript showCR:ts nanoseconds.
+     Transcript showCR:ts picoseconds.
+    "
+!
+
+exactMinutes
+    "return the exact minutes within the stamp's hour (00 .. 59.999...) as a fixedPoint number.
+     Notice: 
+        that is NOT the total number of minutes,
+        but the fractional part (within the hour) only.
+     A fixedPoint number holds the exact value, but prints itself rounded!!"
+
+    |minutes additionalSeconds|
+
+    minutes := FixedPoint numerator:(osTime \\ (60*60*1000)) / 60 denominator:1000 scale:3.
+    additionalPicoseconds notNil ifTrue:[
+        additionalSeconds := (FixedPoint numerator:additionalPicoseconds denominator:(1000*1000*1000*1000) scale:3).
+        minutes := minutes + (additionalSeconds / 60).
+    ].
+    ^ minutes.
+
+    "
+     |ts|
+
+     ts := Timestamp nowWithMicroseconds.
+     Transcript showCR:ts.
+     Transcript showCR:ts minutes.
+     Transcript showCR:ts exactMinutes.
+    "
+!
+
+exactNanoseconds
+    "return the exact nanoseconds within the stamp's second (0 .. 999.999...).
+     notice: 
+        that is NOT the total number of nanoseconds,
+        but the fractional part (within the second) only.
+     A fixedPoint number holds the exact value, but prints itself rounded!!"
+
+    |millis nanosFromMillis|
+
+    millis := (osTime \\ 1000).
+    nanosFromMillis := millis * 1000 * 1000.
+    additionalPicoseconds notNil ifTrue:[
+        ^ nanosFromMillis + (FixedPoint numerator:additionalPicoseconds denominator:(1000) scale:3)
+    ].
+    ^ nanosFromMillis.
+
+    "
+     |ts|
+
+     ts := Timestamp now + 100.3 nanoseconds.
+     Transcript showCR:ts.
+     Transcript showCR:ts milliseconds.
+     Transcript showCR:ts exactMilliseconds.
+     Transcript showCR:ts microseconds.
+     Transcript showCR:ts exactMicroseconds.
+     Transcript showCR:ts nanoseconds.
+     Transcript showCR:ts exactNanoseconds.
+     Transcript showCR:ts picoseconds.
+    "
+!
+
+exactSeconds
+    "return the exact seconds within the stamp's minute (00 .. 59.999...) as a fixedPoint number.
+     Notice: 
+        that is NOT the total number of seconds,
+        but the fractional part (within the minute) only.
+     A fixedPoint number holds the exact value, but prints itself rounded!!"
+
+    |seconds additionalSeconds|
+
+    seconds := FixedPoint numerator:(osTime \\ (60*1000)) denominator:1000 scale:3.
+    additionalPicoseconds notNil ifTrue:[
+        additionalSeconds := (FixedPoint numerator:additionalPicoseconds denominator:(1000*1000*1000*1000) scale:3).
+        seconds := seconds + additionalSeconds
+    ].
+    ^ seconds.
+
+    "
+     |ts|
+
+     ts := Timestamp fromDate:(Date today) hour:10 minute:30 second:20 millisecond:300.
+     Transcript showCR:ts.
+     Transcript showCR:ts seconds.
+     Transcript showCR:ts exactSeconds.
+    "
+!
+
 hours
     "return the hours (0..23)"
 
@@ -1900,23 +2196,29 @@
 !
 
 microseconds
-    "return the microseconds within the stamp's second (0..999999).
+    "return the truncated microseconds within the stamp's second (0..999999).
      notice: that is NOT the total number of microseconds,
      but the fractional part (within the second) only. 
      Use this only for printing."
 
-    |microsFromMillis|
-
-    microsFromMillis := (osTime \\ 1000) * 1000.
+    |millis microsFromMillis|
+
+    millis := (osTime \\ 1000).
+    microsFromMillis := millis * 1000.
     additionalPicoseconds notNil ifTrue:[
         ^ microsFromMillis + (additionalPicoseconds // (1000*1000))
     ].
     ^ microsFromMillis.
 
     "
-     Timestamp now microseconds
+     -- (definitely millisecond resolution here)
+     Timestamp now                          
+     Timestamp now microseconds             
+
+     -- (but some OS's only deliver millisecond resolution also here)
      Timestamp nowWithMicroseconds microseconds
 
+
      |t1 t2|
      t1 := Timestamp nowWithMicroseconds microseconds.
      t2 := Timestamp nowWithMicroseconds microseconds.
@@ -1933,7 +2235,7 @@
 !
 
 millisecond
-    "return the millisecond within the stamp's second (0..999).
+    "return the truncated millisecond within the stamp's second (0..999).
      ST-80 Timestamp compatibility (I'd prefer the name #milliseconds)."
 
     ^ self milliseconds
@@ -1943,7 +2245,7 @@
 !
 
 milliseconds
-    "return the milliseconds within the stamp's second (0..999)"
+    "return the truncated milliseconds within the stamp's second (0..999)"
 
     ^ osTime \\ 1000.
 
@@ -1998,7 +2300,7 @@
 !
 
 nanoseconds
-    "return the nanoseconds within the stamp's second (0..999999999).
+    "return the truncated nanoseconds within the stamp's second (0..999999999).
      notice: that is NOT the total number of nanoseconds,
      but the fractional part (within the second) only. 
      Use this only for printing."
@@ -2037,12 +2339,6 @@
     ^ osTime
 !
 
-osTime:aTime
-    "set the internal representation of the time"
-
-    osTime := aTime.
-!
-
 picoseconds
     "return the picoseconds within the stamp's second (0..999999999999).
      notice: that is NOT the total number of picoseconds,
@@ -2061,10 +2357,10 @@
 !
 
 seconds
-    "return the seconds (0..59)"
+    "return the truncated seconds (0..59)"
 
     (osTime between:MinOSTime and:MaxOSTime) ifFalse:[
-	^ self asTime seconds.
+        ^ self asTime seconds.
     ].
     ^ self timeInfo seconds
 
@@ -2627,6 +2923,14 @@
     self setOSTimeFromUTCYear:y month:m day:d hour:h minute:min second:s millisecond:millis
 !
 
+UTCyear:y month:m day:d hour:h minute:min second:s millisecond:millis additionalPicoseconds:picos
+    "private: ask the operating system to compute the internal osTime (based on the epoch),
+     given y,m,d and h,m,s in my time."
+
+    self setOSTimeFromUTCYear:y month:m day:d hour:h minute:min second:s millisecond:millis.
+    additionalPicoseconds := picos.
+!
+
 setOSTimeFromUTCYear:y month:m day:d hour:h minute:min second:s millisecond:millis
     "private: ask the operating system to compute the internal osTime (based on the epoch),
      given y,m,d and h,m,s in local time"
@@ -2688,6 +2992,19 @@
      self basicNew
          year:2016 month:4 day:16 hour:17 minute:21 second:13 millisecond:726
     "
+!
+
+year:y month:m day:d hour:h minute:min second:s millisecond:millis additionalPicoseconds:picos
+    "private: ask the operating system to compute the internal osTime (based on the epoch),
+     given y,m,d and h,m,s in my time."
+
+    self setOSTimeFromYear:y month:m day:d hour:h minute:min second:s millisecond:millis.
+    additionalPicoseconds := picos
+
+    "
+     self basicNew
+         year:2016 month:4 day:16 hour:17 minute:21 second:13 millisecond:726
+    "
 ! !
 
 
@@ -3231,7 +3548,7 @@
 !
 
 getMilliseconds
-    "strictly private: return the milliseconds (since the epoch) in utc"
+    "strictly private: return the truncated milliseconds (since the epoch) in utc"
 
     ^ osTime
 
@@ -3246,6 +3563,38 @@
     "Modified (comment): / 21-09-2017 / 18:50:23 / cg"
 !
 
+osTime:aTime
+    "set the internal representation of the time"
+
+    osTime := aTime.
+!
+
+setMicrosecond:aNumber
+    "change the sub-second fractional part only (leaves everything above seconds unchanged)"
+
+    self 
+        setMilliseconds:(self getMilliseconds // 1000) * 1000   "/ strip off any sub-second part 
+        additionalPicoseconds:(aNumber * 1000 * 1000) rounded.  "/ set picos 
+
+    "
+     Timestamp now setMicrosecond:15    - 15 microseconds after the current second's start 
+     Timestamp now setMicrosecond:0.1   - 100 nanoseconds after the current second's start 
+    "
+!
+
+setMillisecond:aNumber
+    "change the sub-second fractional part only (leaves everything above seconds unchanged)"
+
+    self 
+        setMilliseconds:(self getMilliseconds // 1000) * 1000           "/ strip off any sub-second part 
+        additionalPicoseconds:(aNumber * 1000 * 1000 * 1000) rounded.   "/ set picos 
+
+    "
+     Timestamp now setMillisecond:15    - 15 milliseconds after the current second's start
+     Timestamp now setMillisecond:0.05  - 50 microseconds after the current second's start
+    "
+!
+
 setMilliseconds:millis
     "strictly private: set the milliseconds (since the epoch)"
 
@@ -3255,6 +3604,42 @@
     "Created: 1.7.1996 / 14:34:24 / cg"
 !
 
+setMilliseconds:millis additionalPicoseconds:picos
+    "strictly private: set the milliseconds (since the epoch) and additional picos"
+
+    |rest newMillis newPicos|
+
+    millis isInteger ifTrue:[
+        newMillis := millis.
+        newPicos := 0.
+    ] ifFalse:[
+        newMillis := millis truncated.
+        rest := millis - newMillis.
+        newPicos := (rest * 1000 * 1000 * 1000) rounded asInteger.
+    ].
+
+    picos ~~ 0 ifTrue:[
+        newPicos := newPicos + picos.
+        newMillis := newMillis + (newPicos // (1000*1000*1000)).
+        newPicos := newPicos \\ (1000*1000*1000).
+    ].
+    osTime := newMillis.
+    additionalPicoseconds := newPicos.
+!
+
+setNanosecond:aNumber
+    "change the sub-second fractional part only (leaves everything above seconds unchanged)"
+
+    self 
+        setMilliseconds:(self getMilliseconds // 1000) * 1000   "/ strip off any sub-second part
+        additionalPicoseconds:(aNumber * 1000) rounded.         "/ set picos
+
+    "
+     Timestamp now setNanosecond:15     - 15 nanoseconds after the current second's start
+     Timestamp now setNanosecond:0.1    - 10 picoseconds after the current second's start
+    "
+!
+
 setSeconds:secs
     "strictly private: set the seconds (since whatever)"
 
@@ -3330,6 +3715,35 @@
     ^ aVisitor visitTimestamp:self with:aParameter
 ! !
 
+!Timestamp::TimestampBuilderAbstract class methodsFor:'documentation'!
+
+documentation
+"
+    documentation to be added.
+
+    [author:]
+        cg
+
+    [instance variables:]
+
+    [class variables:]
+
+    [see also:]
+
+"
+!
+
+examples
+"
+
+  more examples to be added:
+                                                                [exBegin]
+    ... add code fragment for 
+    ... executable example here ...
+                                                                [exEnd]
+"
+! !
+
 !Timestamp::TimestampBuilderAbstract methodsFor:'error reporting'!
 
 malformed:aString
@@ -3461,32 +3875,34 @@
      Attention: an explicit utcOffset in the input string has already been added into the hh:mm values."
 
     (timestampClass == UtcTimestamp) ifTrue:[
-	^ UtcTimestamp
-	    UTCYear: year month: month day: day
-	    hour: hour minute: minute second: second millisecond: millisecond
+        ^ UtcTimestamp
+            UTCYear:year month:month day:day
+            hour:hour minute:minute second:second millisecond:millisecond additionalPicoseconds:picos
     ].
     (timestampClass == TZTimestamp) ifTrue:[
-	"/ Attention: an explicit utcOffset in the input string has already been added into the hh:mm values."
-	^ ((TZTimestamp
-	    UTCYear: year month: month day: day
-	    hour: hour minute: minute second: second millisecond: millisecond) utcOffset:utcOffset)
+        "/ Attention: an explicit utcOffset in the input string has already been added into the hh:mm values."
+        ^ ((TZTimestamp
+            UTCYear:year month:month day:day
+            hour:hour minute:minute second:second millisecond:millisecond additionalPicoseconds:picos
+           ) utcOffset:utcOffset)
     ].
 
     (isUtcTime or:[hasTimezone and:[utcOffset == 0]]) ifTrue:[
-	^ ((timestampClass == Timestamp) ifTrue:UtcTimestamp ifFalse:timestampClass)
-	    UTCYear: year month: month day: day
-	    hour: hour minute: minute second: second millisecond: millisecond
+        ^ ((timestampClass == Timestamp) ifTrue:UtcTimestamp ifFalse:timestampClass)
+            UTCYear:year month:month day:day
+            hour:hour minute:minute second:second millisecond:millisecond additionalPicoseconds:picos
     ].
     hasTimezone ifTrue:[
-	"/ Attention: an explicit utcOffset in the input string has already been added into the hh:mm values."
-	^ (((timestampClass == Timestamp) ifTrue:TZTimestamp ifFalse:timestampClass)
-	    UTCYear: year month: month day: day
-	    hour: hour minute: minute second: second millisecond: millisecond) utcOffset:utcOffset
+        "/ Attention: an explicit utcOffset in the input string has already been added into the hh:mm values."
+        ^ (((timestampClass == Timestamp) ifTrue:TZTimestamp ifFalse:timestampClass)
+            UTCYear:year month:month day:day
+            hour:hour minute:minute second:second millisecond:millisecond additionalPicoseconds:picos
+          ) utcOffset:utcOffset
     ].
     "/ there was no timezone info, so make it a local timestamp again.
-    ^ (timestampClass
-	year: year month: month day: day
-	hour: hour minute: minute second: second millisecond: millisecond)
+    ^ timestampClass
+        year:year month:month day:day
+        hour:hour minute:minute second:second millisecond:millisecond additionalPicoseconds:picos
 !
 
 yearAlreadyReadAs:yearArg
@@ -4009,7 +4425,7 @@
 
     peek := stream peekOrNil.
     peek ifNil: [
-        "End of stream, only year has been read."
+        "End of stream, only date has been read."
         ^ self timestampWithClass:timestampClass].
 
     (peek asUppercase == $T or: [peek == Character space])
@@ -4033,48 +4449,22 @@
     "Read an arbitrary number of digits representing a fraction."
 
     ^ Fraction readDecimalFractionFrom:stream onError:[self malformed: 'Missing digits after fraction separator'].
-"/
-"/    | 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
-"/
+
+    "
+     (Fraction readDecimalFractionFrom:'12345' readStream onError:nil)
+    "
 !
 
 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."
-
-    millisecond := (self readFraction * 1000) asInteger
-"/    | digit factor |
-"/
-"/    factor := 100.
-"/
-"/    [
-"/        digit := self nextDigit.
-"/        digit >= 0
-"/    ] whileTrue: [
-"/        factor > 0 ifTrue: [
-"/            "Factor still > 0, did not read all three digits of mantissa."
-"/            millisecond := digit * factor + millisecond.
-"/            factor := (factor / 10) integerPart
-"/        ]
-"/    ].
-"/
-"/    factor = 100 ifTrue: [self malformed: 'No digits after millisecond separator']
+    "Read an arbitrary number of digits representing the fractional part
+     (used to be milliseconds, but now we can represent anything down to pico seconds"
+
+    |fraction ms|
+
+    fraction := self readFraction.  "/ 0 .. 0.99999...
+    ms := (fraction * 1000).        "/ 0 .. 999.999999
+    millisecond := (ms // 1).       "/ 0 .. 999
+    picos := (ms \\ 1) * (1000 * 1000 * 1000).
 
     "Created: / 15-06-2005 / 15:25:45 / masca"
 !
@@ -4115,7 +4505,8 @@
 !
 
 readTime
-    "Date read, don't mind it. Read only the time value."
+    "Date already read, don't mind it. 
+     Read only the time value."
 
     | peek f |
 
@@ -4214,8 +4605,8 @@
 
 readTimezone
     "Read time zone information. There are three possibilities of what can occur.
-    If there is nothing more to read, the offset is unknown - this is treated as
-    Zulu time as this may not be true."
+     If there is nothing more to read, the offset is unknown - this is treated as
+     Zulu time as this may not be true."
 
     | peek tzOffset |
 
@@ -4224,7 +4615,7 @@
     peek := peek asUppercase.
 
     "If the time is in Zulu, don't modify the timestamp. This makes the machine
-    run in Zulu time zone, maybe some corrections would be nice."
+     run in Zulu time zone, maybe some corrections would be nice."
     peek == $Z ifTrue: [
         "Time read, skip Zulu signature and exit."
         isUtcTime := true.
@@ -4253,7 +4644,8 @@
 
 readTimezoneOffset
     "Read time zone offset as a number minutes. Generally, there should be hours only
-    but as the format introduces minutes in offsets, we must accept them."
+     but as the format introduces minutes in offsets, we must accept them.
+     (actually: there are countries with half-hour offsets!!)"
 
     | hours digit |
 
@@ -4262,12 +4654,12 @@
     (hours between: 0 and: 12) ifFalse: [self malformed: 'Bad offset hour: ' , hours printString].
 
     stream peekOrNil = $:
-	ifTrue: [
-	    "Colon read, minutes must follow."
-	    stream next.
-	    digit := self nextDigits: 2.
-	    (digit between: 0 and: 59) ifFalse: [self malformed: 'Bad offset minute: ' , digit printString].
-	    ^Array with: hours with: digit].
+        ifTrue: [
+            "Colon read, minutes must follow."
+            stream next.
+            digit := self nextDigits: 2.
+            (digit between: 0 and: 59) ifFalse: [self malformed: 'Bad offset minute: ' , digit printString].
+            ^Array with: hours with: digit].
 
     "Read next digit and check whether minutes follow. If not, return only with hours. If yes,
      check boundaries."
@@ -4283,21 +4675,21 @@
 !
 
 readWeekNumber
+    "Read week number. It is always two digits long."
 
     | week dayInWeek digit |
 
-    "Read week number. It is always two digits long."
     week := self nextDigits: 2.
 
     stream peekOrNil = $-
-	ifTrue: [
-	    "Got dash, day number must follow."
-	    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].
+        ifTrue: [
+            "Got dash, day number must follow."
+            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."
     dayInWeek := self nextDigit.