Timestamp.st
changeset 22834 a70e3ac5710e
parent 22827 9ea9757226e5
child 22837 c8ab3b3ad654
--- a/Timestamp.st	Wed May 09 15:06:33 2018 +0200
+++ b/Timestamp.st	Wed May 09 16:14:27 2018 +0200
@@ -16,7 +16,7 @@
 "{ NameSpace: Smalltalk }"
 
 AbstractTime subclass:#Timestamp
-	instanceVariableNames:'osTime picoseconds'
+	instanceVariableNames:'osTime additionalPicoseconds'
 	classVariableNames:'Epoch MaxOSTime MinOSTime TimeZoneInfo'
 	poolDictionaries:''
 	category:'Magnitude-Time'
@@ -56,12 +56,14 @@
 documentation
 "
     This class represents time values in milliseconds, starting some time in the past.
+    This base-time is called 'epoch' and always an UTC time.
+
     When printing and accessing values like #hour,
     the timestamp will be interpreted in the local timezone.
     (as opposed to UtcTimestamp, which presents itself in UTC,
      and as opposed to TZTimestamp, which remembers the timezone in which it was created).
 
-    The internal representation, osTime, will typically start with 1970-01-01 0:00,
+    The internal representation, osTime, will typically start with 1970-01-01 00:00 UTC,
     as used in the Unix operating system, but other systems may bias the time differently.
     Actually, the implementation does not depend or even know which time/date
     the OperatingSystem bases its time upon - it is simply keeping the value(s)
@@ -97,6 +99,11 @@
         the so called 'proleptic gregorian calendar' is used. This assumes leap years to continue in
         the past as if a gregorian calendar was used. Thus, 0000 is considered a leap year.
 
+    ALso Note:
+        because all timestamps keep the internal time value in UTC, they can be easily compared
+        for being before/same/after another. Only when printing, a difference is made.
+        The timezone is compensated out when a timestamp is created and recalculated in when printed.
+
     News:
         The additional instance variable picoSeconds can be used to add more resolution. 
         If non-nil, it holds additional picoseconds to be added to the millisecond osTime
@@ -104,6 +111,9 @@
         Although, not all OSs give us that detail when asking for the current time,
         the picos can still be used in physical computations. 
         Some OSs will provide microsecond resolution.
+        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.
 
     [author:]
         Claus Gittinger
@@ -256,15 +266,19 @@
 !
 
 secondsSince1970:secs
-    "set time from elapsed seconds since 1-1-1970, 00:00:00.
-     This is the format used in the UNIX world"
+    "set time from elapsed seconds since 1-1-1970, 00:00:00 (UTC).
+     This is the format used in the UNIX world.
+     Notice that the internal storage is always UTC based."
 
     ^ self basicNew setSeconds:secs.
 
     "
-     Timestamp secondsSince1970:0
+     UtcTimestamp secondsSince1970:0        -> 1970-01-01 00:00:00Z       
+     Timestamp secondsSince1970:0           -> 1970-01-01 01:00:00 (local germany, ST)
+
      Timestamp secondsSince1970:3600
      Timestamp secondsSince1970:3600*24
+
      (Timestamp year:2010 month:7 day:1 hour:0 minute:0 second:0)
        =
      (Timestamp secondsSince1970:1277935200)
@@ -295,19 +309,19 @@
 !
 
 utcSecondsSince1970:secs
-    "set time from elapsed seconds since 1-1-1970, 00:00:00.
-     This is the format used in the UNIX world"
-
-    ^ self secondsSince1970:secs
-
-"/    |divMod|
-"/
-"/    divMod := secs divMod:3600.
-"/    ^ self year:1970 month:1 day:1 hour:(divMod at:1) minute:0 second:(divMod at:2) millisecond:0.
+    "set time from the elapsed seconds since 1-1-1970, 00:00:00 UTC.
+     This is the format used in the UNIX world.
+     Notice that the internal storage is always UTC based."
+
+    ^ UtcTimestamp secondsSince1970:secs
 
     "
-     Timestamp secondsSince1970:0
-     Timestamp secondsSince1970:3600
+     UtcTimestamp secondsSince1970:0        -> 1970-01-01 00:00:00Z       
+     Timestamp secondsSince1970:0           -> 1970-01-01 01:00:00 (local germany, ST)
+
+     UtcTimestamp secondsSince1970:3600     -> 1970-01-01 01:00:00Z
+     Timestamp secondsSince1970:3600        -> 1970-01-01 02:00:00 (local)
+
      Timestamp secondsSince1970:3600*24
     "
 
@@ -1733,6 +1747,41 @@
     "
 ! !
 
+!Timestamp methodsFor:'Compatibility-ST80'!
+
+hour
+    "return the hour (0..23).
+     ST-80 Timestamp compatibility 
+     (I'd prefer the name #hours, for Time compatibility)."
+
+    ^ self hours
+
+    "Created: 1.7.1996 / 15:14:50 / cg"
+    "Modified: 1.7.1996 / 15:15:32 / cg"
+!
+
+minute
+    "return the minute (0..59).
+     ST-80 Timestamp compatibility 
+     (I'd prefer the name #minutes, for Time compatibility)."
+
+    ^ self minutes
+
+    "Created: 1.7.1996 / 15:14:29 / cg"
+    "Modified: 1.7.1996 / 15:15:37 / cg"
+!
+
+second
+    "return the second (0..59).
+     ST-80 Timestamp compatibility 
+     (I'd prefer the name #seconds, for Time compatibility)."
+
+    ^ self seconds
+
+    "Created: 1.7.1996 / 15:14:19 / cg"
+    "Modified: 1.7.1996 / 15:15:49 / cg"
+! !
+
 !Timestamp methodsFor:'accessing'!
 
 day
@@ -1817,16 +1866,6 @@
     "Created: / 20-01-2011 / 12:28:46 / cg"
 !
 
-hour
-    "return the hour (0..23).
-     ST-80 Timestamp compatibility (I'd prefer the name #hours, for Time compatibility)."
-
-    ^ self hours
-
-    "Created: 1.7.1996 / 15:14:50 / cg"
-    "Modified: 1.7.1996 / 15:15:32 / cg"
-!
-
 hours
     "return the hours (0..23)"
 
@@ -1842,6 +1881,39 @@
     "Modified: 2.7.1996 / 09:20:32 / cg"
 !
 
+microseconds
+    "return the 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.
+    additionalPicoseconds notNil ifTrue:[
+        ^ microsFromMillis + (additionalPicoseconds // (1000*1000))
+    ].
+    ^ microsFromMillis.
+
+    "
+     Timestamp now microseconds
+     Timestamp nowWithMicroseconds microseconds
+
+     |t1 t2|
+     t1 := Timestamp nowWithMicroseconds microseconds.
+     t2 := Timestamp nowWithMicroseconds microseconds.
+     t2-t1
+
+     |t1 t2|
+     t1 := Timestamp now microseconds.
+     t2 := Timestamp nowWithMicroseconds microseconds.
+     t2-t1
+    "
+
+    "Created: 1.7.1996 / 15:15:02 / cg"
+    "Modified: 2.7.1996 / 09:21:41 / cg"
+!
+
 millisecond
     "return the millisecond within the stamp's second (0..999).
      ST-80 Timestamp compatibility (I'd prefer the name #milliseconds)."
@@ -1865,16 +1937,6 @@
     "Modified: 2.7.1996 / 09:21:41 / cg"
 !
 
-minute
-    "return the minute (0..59).
-     ST-80 Timestamp compatibility (I'd prefer the name #minutes, for Time compatibility)."
-
-    ^ self minutes
-
-    "Created: 1.7.1996 / 15:14:29 / cg"
-    "Modified: 1.7.1996 / 15:15:37 / cg"
-!
-
 minutes
     "return the minutes (0..59)"
 
@@ -1917,6 +1979,39 @@
     "
 !
 
+nanoseconds
+    "return the 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."
+
+    |nanosFromMillis|
+
+    nanosFromMillis := (osTime \\ 1000) * (1000 * 1000).
+    additionalPicoseconds notNil ifTrue:[
+        ^ nanosFromMillis + (additionalPicoseconds // 1000)
+    ].
+    ^ nanosFromMillis.
+
+    "
+     Timestamp now nanoseconds
+     Timestamp nowWithMicroseconds nanoseconds
+
+     |t1 t2|
+     t1 := Timestamp nowWithMicroseconds nanoseconds.
+     t2 := Timestamp nowWithMicroseconds nanoseconds.
+     t2-t1
+
+     |t1 t2|
+     t1 := Timestamp now nanoseconds.
+     t2 := Timestamp nowWithMicroseconds nanoseconds.
+     t2-t1
+    "
+
+    "Created: 1.7.1996 / 15:15:02 / cg"
+    "Modified: 2.7.1996 / 09:21:41 / cg"
+!
+
 osTime
     "get the internal representation of the time.
      Warning: do not depend on the value (unix vs. win32 - differences)"
@@ -1931,35 +2026,22 @@
 !
 
 picoseconds
-    "return the additional picoseconds (0..1000*1000*1000)"
-
-    ^ picoseconds ? 0.
+    "return the picoseconds within the stamp's second (0..999999999999).
+     notice: that is NOT the total number of picoseconds,
+     but the fractional part (within the second) only. 
+     Use this only for printing."
+
+    |picosFromMillis|
+
+    picosFromMillis := (osTime \\ 1000) * (1000 * 1000 * 1000).
+    ^ picosFromMillis + (additionalPicoseconds ? 0)
 
     "
      Timestamp now picoseconds
-    "
-!
-
-picoseconds:anInteger
-    "set the additional picoseconds (0..999999999)"
-
-    picoseconds:= anInteger
-
-    "
-     Timestamp now picoseconds
+     Timestamp nowWithMicroseconds picoseconds
     "
 !
 
-second
-    "return the second (0..59).
-     ST-80 Timestamp compatibility (I'd prefer the name #seconds, for Time compatibility)."
-
-    ^ self seconds
-
-    "Created: 1.7.1996 / 15:14:19 / cg"
-    "Modified: 1.7.1996 / 15:15:49 / cg"
-!
-
 seconds
     "return the seconds (0..59)"
 
@@ -2985,6 +3067,37 @@
 
 !Timestamp methodsFor:'private'!
 
+additionalPicoseconds
+    "return the additional picoseconds (0..999999999).
+     These must alwyas be smaller than 1000*1000*1000 (i.e. 1ms),
+     to avoid overflow into the millis.
+     These are to be added to any milliseconds"
+
+    ^ additionalPicoseconds ? 0.
+
+    "
+     Timestamp now picoseconds
+     Timestamp nowWithMicroseconds picoseconds
+    "
+!
+
+additionalPicoseconds:anInteger
+    "set the additional picoseconds (0..999999999).
+     These must alwyas be smaller than 1000*1000*1000 (i.e. 1ms),
+     to avoid overflow into the millis."
+
+    self assert:(anInteger < (1000*1000*1000)).
+    additionalPicoseconds:= anInteger
+
+    "
+     Timestamp now picoseconds
+     Timestamp now additionalPicoseconds
+
+     Timestamp nowWithMicroseconds picoseconds
+     Timestamp nowWithMicroseconds additionalPicoseconds
+    "
+!
+
 computeTimeInfo
     |d t info|
 
@@ -3033,16 +3146,17 @@
 !
 
 fromOSTimeWithMilliseconds:anUninterpretedOSTime
-    "strictly private: set the milliseconds from an OS time (since the epoch)"
+    "strictly private: set the milliseconds from an OS time (since the epoch).
+     Notice: timestamps always have millisecond precision (in contrast to Time, where it is optional)"
 
     osTime := anUninterpretedOSTime
 !
 
-fromOSTimeWithMilliseconds:anUninterpretedOSTime picoseconds:picos
+fromOSTimeWithMilliseconds:anUninterpretedOSTime additionalPicoseconds:picos
     "strictly private: set the milliseconds and picoSeconds from an OS time (since the epoch)"
 
     osTime := anUninterpretedOSTime.
-    picoseconds := picos
+    additionalPicoseconds := picos
 
     "
      Timestamp nowWithMicroseconds
@@ -3410,10 +3524,19 @@
      such as '2014-11-06T11:48:09Z'.
      The time is printed as UTC time"
 
-    self print:aTimestamp compact:false asLocal:false asUTC:true withMilliseconds:true on:aStream
+    self 
+        print:aTimestamp 
+        compact:false asLocal:false asUTC:true 
+        withMilliseconds:true timeSeparator:$T timeOnly:false
+        on:aStream
 
     "
-     self print:(Timestamp now) on:Transcript
+     self print:(Timestamp now) on:Transcript.
+     Transcript cr.
+     self print:(Time now) on:Transcript.
+     Transcript cr.
+     self print:(Time nowWithMilliseconds) on:Transcript.
+     Transcript cr.
     "
 
     "Created: / 15-06-2005 / 17:56:51 / masca"
@@ -3424,7 +3547,10 @@
      such as '2014-11-06T11:48:09+01'.
      The time is printed as local time"
 
-    self print:aTimestamp compact:false asLocal:true asUTC:false withMilliseconds:true on:aStream
+    self 
+        print:aTimestamp compact:false asLocal:true asUTC:false 
+        withMilliseconds:true timeSeparator:$T timeOnly:false 
+        on:aStream
 
     "
      self printAsLocalTime:(Timestamp now) on:Transcript
@@ -3449,10 +3575,32 @@
     "Created: / 15-06-2005 / 17:52:52 / masca"
 !
 
-printCompressed:aTimestamp asLocal:asLocal on: aStream
-    "generates a compressed string representation, such as '20141106T114636Z'"
-
-    self print:aTimestamp compact:true asLocal:asLocal asUTC:asLocal not withMilliseconds:true on:aStream
+printCompressed:aTimestamp asLocal:asLocal on:aStream
+    "generates a compressed string representation, 
+     (optionally as localtime) such as '20141106T114636Z'"
+
+    self 
+        print:aTimestamp 
+        compact:true asLocal:asLocal asUTC:asLocal not 
+        withMilliseconds:true timeSeparator:$T timeOnly:false
+        on:aStream
+
+    "
+     self printCompressed:(Timestamp now) on:Transcript
+    "
+
+    "Created: / 15-06-2005 / 17:54:17 / masca"
+!
+
+printCompressed:aTimestamp on:aStream
+    "generates a compressed string representation, such as '20141106T114636Z'.
+     The time is printed as UTC time"
+
+    self 
+        print:aTimestamp 
+        compact:true asLocal:false asUTC:true 
+        withMilliseconds:true timeSeparator:$T timeOnly:false 
+        on:aStream
 
     "
      self printCompressed:(Timestamp now) on:Transcript
@@ -3461,11 +3609,15 @@
     "Created: / 15-06-2005 / 17:54:17 / masca"
 !
 
-printCompressed: aTimestamp on: aStream
+printCompressedAsLocalTime:aTimestamp on:aStream
     "generates a compressed string representation, such as '20141106T114636Z'.
-     The time is printed as UTC time"
-
-    self print:aTimestamp compact:true asLocal:false asUTC:true withMilliseconds:true on:aStream
+     The time is printed as local time"
+
+    self 
+        print:aTimestamp 
+        compact:true asLocal:true asUTC:false 
+        withMilliseconds:true timeSeparator:$T timeOnly:false 
+        on:aStream
 
     "
      self printCompressed:(Timestamp now) on:Transcript
@@ -3474,17 +3626,26 @@
     "Created: / 15-06-2005 / 17:54:17 / masca"
 !
 
-printCompressedAsLocalTime: aTimestamp on: aStream
-    "generates a compressed string representation, such as '20141106T114636Z'.
-     The time is printed as local time"
-
-    self print:aTimestamp compact:true asLocal:true asUTC:false withMilliseconds:true on:aStream
+printTime:aTimeOrTimestamp on:aStream
+    "Print the given time in general ISO8601 format,
+     such as 'T11:48:09Z'.
+     The time is printed as UTC time.
+     No date is printed."
+
+    self 
+        print:aTimeOrTimestamp 
+        compact:false asLocal:false asUTC:true 
+        withMilliseconds:true timeSeparator:$T timeOnly:true
+        on:aStream
 
     "
-     self printCompressed:(Timestamp now) on:Transcript
+     self print:(Time nowWithMilliseconds) on:Transcript.
+     Transcript cr.
+     self printTime:(Time nowWithMilliseconds) on:Transcript.
+     Transcript cr.
     "
 
-    "Created: / 15-06-2005 / 17:54:17 / masca"
+    "Created: / 15-06-2005 / 17:56:51 / masca"
 !
 
 printTimeZone:tzOffsetArg on: aStream
@@ -3574,7 +3735,38 @@
                     otherwise it is a timestamp from another timezone (TZTimestamp), then print in its timezone
         withMilliseconds: if false, no milliseconds are generated"
 
-    |timeInfo millis |
+    self 
+        print:aTimestamp 
+        compact:compact asLocal:asLocal asUTC:asUTC 
+        withMilliseconds:withMillis timeSeparator:tSep timeOnly:false
+        on:aStream
+
+    "
+     self print:(Timestamp now) on:Transcript
+     self printAsLocalTime:(Timestamp now) on:Transcript
+     self printAsLocalTime:(Timestamp now asTZTimestamp:-7200) on:Transcript
+    "
+
+    "Created: / 15-06-2005 / 17:56:51 / masca"
+!
+
+print:aTimeOrTimestamp compact:compact asLocal:asLocal asUTC:asUTC withMilliseconds:withMillis timeSeparator:tSep timeOnly:timeOnly on:aStream
+    "Print the given timestamp in general ISO8601 format,
+     such as '2014-11-06T11:48:09Z'.
+        compact: if true, the compact format (without separating dashes and colons is generated)
+        asLocal: if true, generates a localtime string (without any timezone info)
+        asUTC: if true, generates a utc string
+            if both are false:
+                generate a string depending on the type of timestamp:
+                    if local: generate a local timezone string
+                    if utc: generate a utc string
+                    otherwise it is a timestamp from another timezone (TZTimestamp), then print in its timezone
+        withMilliseconds: if false, no milliseconds are generated.
+     if timeOnly is true, only the time is printed."
+
+    |aTimestamp timeInfo millis |
+
+    aTimestamp := aTimeOrTimestamp asTimestamp.
 
     asLocal ifTrue:[
         "/ force local
@@ -3589,11 +3781,13 @@
         ]
     ].
 
-    timeInfo year printOn:aStream leftPaddedTo:4 with:$0.
-    compact ifFalse:[ aStream nextPut: $- ].
-    timeInfo month printOn:aStream leftPaddedTo:2 with:$0.
-    compact ifFalse:[ aStream nextPut: $- ].
-    timeInfo day printOn:aStream leftPaddedTo:2 with:$0.
+    timeOnly ifFalse:[
+        timeInfo year printOn:aStream leftPaddedTo:4 with:$0.
+        compact ifFalse:[ aStream nextPut: $- ].
+        timeInfo month printOn:aStream leftPaddedTo:2 with:$0.
+        compact ifFalse:[ aStream nextPut: $- ].
+        timeInfo day printOn:aStream leftPaddedTo:2 with:$0.
+    ].
     aStream nextPut:tSep.
     timeInfo hours printOn:aStream leftPaddedTo:2 with:$0.
     compact ifFalse:[ aStream nextPut: $:].
@@ -3621,6 +3815,7 @@
 
     "
      self print:(Timestamp now) on:Transcript
+     self print:(Time now) on:Transcript
      self printAsLocalTime:(Timestamp now) on:Transcript
      self printAsLocalTime:(Timestamp now asTZTimestamp:-7200) on:Transcript
     "