2 files changed, 48 insertions(+), 62 deletions(-)

M episode11/README.md
M episode11/cbor.go
M episode11/README.md +47 -59
@@ 20,6 20,7 @@ one:
 - [Episode 7, maps][ep7]
 - [Episode 8, structs][ep8]
 - [Episode 9, floating point numbers][ep9]
+- [Episode 10, special floating point numbers][ep10]
 
 [ep1]: http://henry.precheur.org/scratchpad/2018-03-19T15%3A50%3A41-07%3A00
 [ep2]: http://henry.precheur.org/scratchpad/20180402_181348

          
@@ 32,46 33,52 @@ one:
 [ep9]: http://henry.precheur.org/scratchpad/20190419_114828
 [ep10]: http://henry.precheur.org/scratchpad/20191103_102445
 [rfc7049]: https://tools.ietf.org/html/rfc7049
+[rfc3339]: https://tools.ietf.org/html/rfc3339
 [repo]: https://bitbucket.org/henry/cbor/src/default/episode11/
 
 ----
 
 In the [previous episode][ep10] we added better floating point number support to
-our encoder.
+our encoder. We implemented all the Go native types, now we’ll implement a
+custom stype: time.Time, a timestamp type. According the the [CBOR
+spec](https://tools.ietf.org/html/rfc7049#section-2.4.1) there are 3 timestamp
+formats supported:
 
-----
-
-Plan:
+- RFC3339 string like "2019-02-01T17:45:23Z"
+- floating point epoch based values
+- integer value epoch based values
 
-1. Implement RFC3339 first
-2. Introduce epoch base timestamp
-3. Optimize: rfc3339 for time.Time w/ timezone, epoch for time.Time w/o
-3. Fix old RFC3339 example
+The [CBOR spec][rfc7049] has special values called tags. They are used
+to represent data with additional semantics like timestamps. A tag’s header major
+type 6 and represents an integer number. So each tag type has a unique integer
+identifier, the number corresponds to the tag’s type. For example URI’s are
+represented as a tagged unicode string: first there’s the header with the major
+type 6 —indicating it’s a tag— with the value 32 —the minor that corresponds to
+URIs—, the header is followed by an UTF-8 CBOR string with the URI.
 
-We implemented all the Go native types, now we’ll implement a custom stype:
-time.Time, a timestamp type. According the the [CBOR spec][rfc7049]
-
-FIXME find link to section
-
-There are 3 types of timestamps supported by CBOR:
+Timestamp have two tagged data item types: 0 for unicode strings encoded as a
+RFC3339 timestamp, or 1 for epoch-based timestamps.
 
-1. RFC3339 string like "2019-02-01T17:45:23Z"
-2. floating point value ...
-3. integer value ...
+How can we detect if we have a time.Time value in the encoder? Looking at
+[time.Time’s definition](https://godoc.org/time#Time) we see that it’s a struct,
+a kind of value we already handle in the encoder. The reflect package lets us
+query the type of a value, so we will check if the value’s type is time.Time
+when we have a reflect.Struct kind.
+
+In the main switch block we add a branch in the reflect.Struct case to handle
+time.Time if we have a value with that type. There’s a bit of gymnastic needed
+to get time.Time’s type without creating an empty timestamp on the stack:
 
-The [CBOR spec][rfc7049] has a special type of value called tags. This is used
-to represent data with additional semantics like timestamps. A tag is a major
-type with the value 6, it represents an integer number as indicated by the tag's
-integer value. That number corresponds to the tag’s type. For example URI’s are
-represented as a tagged unicode string in CBOR: first there’s the header with
-the major type 6 —indicating it’s a tag— with the value 32 —that corresponds to
-URIs—, this is followed by an UTF-8 CBOR string with the URI.
+	case reflect.Struct:
+		if x.Type() == reflect.TypeOf((*time.Time)(nil)).Elem() {
+			return ErrNotImplemented
+		}
+		return e.writeStruct(x)
 
-Timestamps are like that, but they have two data item types: 0 for unicode
-strings encoded as a RFC3339 timestamp, or 1 for epoch-based timestamps.
-
-We'll start with RFC3339 string timestamps, lookup the example in the spec and
-add our first test:
+Let’s add a new function to write CBOR encoded timestamps: writeTime. First
+we’ll do string timestamps, and do scalar timestamp types second. We start with
+[RFC3339][rfc3339] strings, and lookup the example from the spec and add our
+first test:
 
     func TestTimestamp(t *testing.T) {
         var rfc3339Timestamp, _ = time.Parse(time.RFC3339, "2013-03-21T20:04:00Z")

          
@@ 97,30 104,7 @@ add our first test:
         }
     }
 
-How can we detect if we have a time.Time object in the encoder? Looking at
-[time.Time’s definition](https://godoc.org/time#Time) we see that it’s a struct,
-a kind of object we already handle in the encoder. The reflect package lets us
-query the type of a value, so what we have to check is the struct’s type is
-time.Time when we have a struct.
-
-First we’ll set a variable to be time.Time’s representation of its type:
-
-    var typeTime = reflect.TypeOf(time.Time{})
-
-And in the main switch block we add a branch in the reflect.Struct case to
-handle time.Time if we have a value with that type:
-
-	case reflect.Struct:
-		if x.Type() == typeTime {
-            return ErrNotImplemented
-		}
-		return e.writeStruct(x)
-
-Let’s add a new function to write timestamps as CBOR values: writeTime. First
-we’ll implement RFC3339 string timestamps, and support other timestamp types
-after.
-
-We add a few constants required to encode our new values:
+We add a few header constants required to encode the new tagged types:
 
     const (
         // major types

          
@@ 134,9 118,9 @@ We add a few constants required to encod
         ...
     )
 
-And then implement writeTime: it writes the tag’s header with minorTimeString
-to indicates an unicode string follows, and write the unicode string based on
-the timestamp’s value:
+The function writeTime writes the tag’s header with minorTimeString
+to indicate a unicode string follows, then it converts the timestamp into a
+RFC3339 string and writes the string to the output:
 
     func (e *Encoder) writeTime(v reflect.Value) error {
         if err := e.writeHeader(majorTag, minorTimeString); err != nil {

          
@@ 146,11 130,11 @@ the timestamp’s value:
         return e.writeUnicodeString(t.Format(time.RFC3339))
     }
 
-We hook it up to the rest of the code:
+We hook it up to the rest of the code by adding a call to writeTime:
 
 	case reflect.Struct:
-		if x.Type() == typeTime {
-            return e.writeTime(x)
+		if x.Type() == reflect.TypeOf((*time.Time)(nil)).Elem() {
+			return e.writeTime(x)
 		}
 		return e.writeStruct(x)
 

          
@@ 308,3 292,7 @@ writeInteger to write the timestamp:
                 float64(unixTimeNano) / float64(nanoSecondsInSecond))
         }
     }
+
+And it’s all we needed to do to support the non-native type time.Time! We are
+done writing our CBOR encoder. It you would like to see other things covered
+feel free to reach me at henry@precheur.org.

          
M episode11/cbor.go +1 -3
@@ 296,7 296,7 @@ func (e *Encoder) encode(x reflect.Value
 	case reflect.Map:
 		return e.writeMap(x)
 	case reflect.Struct:
-		if x.Type() == typeTime {
+		if x.Type() == reflect.TypeOf((*time.Time)(nil)).Elem() {
 			return e.writeTime(x)
 		}
 		return e.writeStruct(x)

          
@@ 337,5 337,3 @@ const (
 	simpleValueTrue  = 21
 	simpleValueNil   = 22
 )
-
-var typeTime = reflect.TypeOf(time.Time{})