1 files changed, 76 insertions(+), 54 deletions(-)

M episode11/README.md
M episode11/README.md +76 -54
@@ 40,34 40,48 @@ one:
 
 In the [previous episode][ep10] we added better floating point number support to
 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:
+custom stype: time.Time, a timestamp type. The CBOR encoding
+[supports](https://tools.ietf.org/html/rfc7049#section-2.4.1) 3 timestamp types
+natively:
 
 - RFC3339 string like "2019-02-01T17:45:23Z"
 - floating point epoch based values
 - integer value epoch based values
 
-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.
-
-Timestamp have two tagged data item types: 0 for unicode strings encoded as a
-RFC3339 timestamp, or 1 for epoch-based timestamps.
+[CBOR][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 used to determine the tag content’s type. Each tag
+type has a unique integer identifier, a number that corresponds to the tag’s
+type. For example URIs are represented as a tagged unicode string: first there’s
+the header with the major type 6 —indicating it’s a tagged value— encoding the
+integer 32 —the minor that corresponds to URIs—, the header is followed by the
+URI encoded as an UTF-8 CBOR string. Timestamps have two tagged data item types:
+0 for unicode strings encoded as a RFC3339 timestamp, or 1 for epoch-based
+timestamps that include floating point & integer values.
 
 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.
+query and compare value types, so we will check if the value’s type is time.Time
+when we have a reflect.Struct kind and write a CBOR timestamp when that’s the
+case.
+
+In the main switch block we add a if condition 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,
+we can either do:
+
 
-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:
+    reflect.TypeOf(time.Time{})
+
+Or:
+
+    reflect.TypeOf((*time.Time)(nil)).Elem()
+
+In the first case we create an empty time.Time object and get its type, in the
+second case we create an empty interface to time.Time and retreive its type.
+We’ll use the second way because it doesn’t create an empty time.Time object and
+is therefor a tiny bit more efficient:
 
 	case reflect.Struct:
 		if x.Type() == reflect.TypeOf((*time.Time)(nil)).Elem() {

          
@@ 75,10 89,10 @@ to get time.Time’s type without creating an empty timestamp on the stack:
 		}
 		return e.writeStruct(x)
 
-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:
+Let’s add a new function to write CBOR timestamps: writeTime. First we’ll handle
+string timestamps, and implement scalar timestamp types second. We start with
+[RFC3339][rfc3339] strings, we lookup the example from the spec, and we add our
+first test case:
 
     func TestTimestamp(t *testing.T) {
         var rfc3339Timestamp, _ = time.Parse(time.RFC3339, "2013-03-21T20:04:00Z")

          
@@ 104,7 118,8 @@ first test:
         }
     }
 
-We add a few header constants required to encode the new tagged types:
+Back in cbor.go we add a few header constants required to encode the new tagged
+types:
 
     const (
         // major types

          
@@ 118,9 133,9 @@ We add a few header constants required t
         ...
     )
 
-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:
+The function writeTime writes the tag’s header with minorTimeString to indicate
+a string timestamp follows, then it converts the timestamp into a RFC3339 string
+and writes it to the output:
 
     func (e *Encoder) writeTime(v reflect.Value) error {
         if err := e.writeHeader(majorTag, minorTimeString); err != nil {

          
@@ 130,7 145,8 @@ RFC3339 string and writes the string to 
         return e.writeUnicodeString(t.Format(time.RFC3339))
     }
 
-We hook it up to the rest of the code by adding a call to writeTime:
+We hook it up to the rest of the code by adding a call to writeTime in our main
+switch statement:
 
 	case reflect.Struct:
 		if x.Type() == reflect.TypeOf((*time.Time)(nil)).Elem() {

          
@@ 140,21 156,21 @@ We hook it up to the rest of the code by
 
 We run `go test` to confirm everything works.
 
-Now we’ll handle epoch-based timestamps: those are scalar values where 0
-corresponds to the epoch (January 1, 1970), they can either be integer or
-floating point values.
+Now that we are done with string timestamps, we’ll handle epoch-based
+timestamps: they are scalar values where 0 corresponds to the epoch (January 1,
+1970), they can either be integer or floating point values.
 
-We’ll minimize the size of our output by picking the most compact type we can
-use with our encoder. The timestamp can either be an integer, a floating point
-number, or a RFC3339 string. If there’s a timestamp’s timezone isn’t UTC we’ll
-have to use the largest type: the RFC3339 string, because we need to encode the
-timezone. If the timestamp’s timezone is UTC we can use a scalar timestamp
-because we don’t have to set the timezone because it’s assumed to be UTC. We’ll
-use an integer when the timestamp can be represented as whole seconds and use
-floating point number eitherwise.
+We’ll minimize the size of our output by using the most compact type we can use
+without losing precision. The timestamp can either be an integer, a floating
+point number, or a RFC3339 string. If the timestamp’s timezone isn’t UTC
+we’ll have to use the largest type: the RFC3339 string, because we need to
+encode the timezone information and we can’t do it with scalar timestamps. If
+the timestamp’s timezone is UTC we can use a scalar timestamp because it’s
+assumed to be UTC. We’ll use an integer when the timestamp can be represented as
+whole seconds and use floating point number otherwise.
 
 First we’ll add a condition to only use RFC3339 strings when the timestamp has a
-timezone that’s not UTC.
+timezone different from UTC.
 
     func (e *Encoder) writeTime(v reflect.Value) error {
         var t = v.Interface().(time.Time)

          
@@ 168,8 184,8 @@ timezone that’s not UTC.
     }
 
 Because we’re changing the behavior of writeTime when the timezone is UTC, we
-need to fix the test to use timestamp with its timezone set. To do so we replace
-the Z at the end of rfc3339Timestamp with +07:00:
+have to fix the first test case to use a timestamp with its timezone set. To do
+so we replace the Z at the end of rfc3339Timestamp with +07:00:
 
     func TestTimestamp(t *testing.T) {
         var rfc3339Timestamp, _ = time.Parse(time.RFC3339, "2013-03-21T20:04:00+07:00")

          
@@ 195,7 211,7 @@ the Z at the end of rfc3339Timestamp wit
         }
     }
 
-Now let’s implement floating point numbers when there’s no timezone information
+We’ll now implement floating point numbers when there’s no timezone information
 to encode. As usual we start by adding a test case for this from the spec:
 
     func TestTimestamp(t *testing.T) {

          
@@ 214,6 230,11 @@ to encode. As usual we start by adding a
         ...
     }
 
+Note that we had to call the .UTC() method on the time.Time object return by
+time.Unix, that’s because otherwise the object will have the local timezone
+associated to it. So we call the UTC method to get the same timestamp with a UTC
+timezone.
+
 Because time.Time store its internal time as an integer counting the
 number of nanoseconds since the Epoch, we’ll have to convert it into a floating
 point number in seconds. To do this we use the units from the time module to

          
@@ 221,9 242,9 @@ define a constant to convert from nanose
 
     const nanoSecondsInSecond = time.Second / time.Nanosecond
 
-Then we add the new code after the first if block. We write the header with the
-minorTimeEpoch as the sub-type to indicate we have a scalar timestamp, and write
-the converted value as a floating point number:
+Then we add our new code after the if block to handle string timestamps. We
+write the header with the minorTimeEpoch as the sub-type to indicate we have a
+scalar timestamp, the we write the converted value as a floating point number:
 
     func (e *Encoder) writeTime(v reflect.Value) error {
         var t = v.Interface().(time.Time)

          
@@ 243,10 264,10 @@ the converted value as a floating point 
 			float64(unixTimeNano) / float64(nanoSecondsInSecond))
     }
 
-If the timestamp in seconds doesn’t have a fractional part this means we can
-write it as integer without losing data. Integers are usually more compact than
-floating point numbers, so let’s use them when possible. We add another test
-from the spec:
+If the timestamp in seconds can be converted into an integer without losing
+precision this means we can write it as an integer timestamp. Integers are
+usually more compact than floating point numbers, so let’s always use them when
+possible. We add another test from the spec:
 
     func TestTimestamp(t *testing.T) {
         ...

          
@@ 266,8 287,8 @@ from the spec:
 
 To determine if we can write an integer timestamp we check if the fractional
 part of the timestamp in seconds would be zero. Then we convert unixTimeNano
-into seconds, set the minor type depending on the timestamp’s sign, and use
-writeInteger to write the timestamp:
+into seconds, set the CBOR integer’s header minor type depending on the
+timestamp’s sign, and use writeInteger to write the timestamp:
 
     const nanoSecondsInSecond = time.Second / time.Nanosecond
 

          
@@ 293,6 314,7 @@ writeInteger to write the timestamp:
         }
     }
 
-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.
+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.