浏览代码

faster and unified date formatting, flexible lengths, better tests

Arne Hormann 11 年之前
父节点
当前提交
6d51ca56f5
共有 4 个文件被更改,包括 235 次插入190 次删除
  1. 78 41
      driver_test.go
  2. 28 84
      packets.go
  3. 128 64
      utils.go
  4. 1 1
      utils_test.go

+ 78 - 41
driver_test.go

@@ -334,7 +334,7 @@ type timeTests struct {
 }
 }
 
 
 type timeTest struct {
 type timeTest struct {
-	s string
+	s string // leading "!": do not use t as value in queries
 	t time.Time
 	t time.Time
 }
 }
 
 
@@ -351,15 +351,21 @@ func (t timeTest) genQuery(dbtype string, binaryProtocol bool) string {
 	return `SELECT CAST(` + inner + ` AS ` + dbtype + `)`
 	return `SELECT CAST(` + inner + ` AS ` + dbtype + `)`
 }
 }
 
 
-func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, binaryProtocol bool) {
+func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, mode int) {
 	var rows *sql.Rows
 	var rows *sql.Rows
-	var protocol string
-	if query := t.genQuery(dbtype, binaryProtocol); binaryProtocol {
-		protocol = "binary"
+	query := t.genQuery(dbtype, mode < 2)
+	var protocol = "binary"
+	switch mode {
+	case 0:
+		rows = dbt.mustQuery(query, t.s)
+	case 1:
 		rows = dbt.mustQuery(query, t.t)
 		rows = dbt.mustQuery(query, t.t)
-	} else {
+	case 2:
 		protocol = "text"
 		protocol = "text"
-		rows = dbt.mustQuery(fmt.Sprintf(query, t.s))
+		query = fmt.Sprintf(query, t.s)
+		rows = dbt.mustQuery(query)
+	default:
+		panic("unsupported mode")
 	}
 	}
 	defer rows.Close()
 	defer rows.Close()
 	var err error
 	var err error
@@ -368,17 +374,13 @@ func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, binaryProtocol bool)
 		if err == nil {
 		if err == nil {
 			err = fmt.Errorf("no data")
 			err = fmt.Errorf("no data")
 		}
 		}
-		dbt.Errorf("%s [%s]: %s",
-			dbtype, protocol, err,
-		)
+		dbt.Errorf("%s [%s]: %s", dbtype, protocol, err)
 		return
 		return
 	}
 	}
 	var dst interface{}
 	var dst interface{}
 	err = rows.Scan(&dst)
 	err = rows.Scan(&dst)
 	if err != nil {
 	if err != nil {
-		dbt.Errorf("%s [%s]: %s",
-			dbtype, protocol, err,
-		)
+		dbt.Errorf("%s [%s]: %s", dbtype, protocol, err)
 		return
 		return
 	}
 	}
 	switch val := dst.(type) {
 	switch val := dst.(type) {
@@ -387,7 +389,7 @@ func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, binaryProtocol bool)
 		if str == t.s {
 		if str == t.s {
 			return
 			return
 		}
 		}
-		dbt.Errorf("%s to string [%s]: expected '%s', got '%s'",
+		dbt.Errorf("%s to string [%s]: expected %q, got %q",
 			dbtype, protocol,
 			dbtype, protocol,
 			t.s, str,
 			t.s, str,
 		)
 		)
@@ -395,13 +397,15 @@ func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, binaryProtocol bool)
 		if val == t.t {
 		if val == t.t {
 			return
 			return
 		}
 		}
-		dbt.Errorf("%s to string [%s]: expected '%s', got '%s'",
+		dbt.Errorf("%s to string [%s]: expected %q, got %q",
 			dbtype, protocol,
 			dbtype, protocol,
 			t.s, val.Format(tlayout),
 			t.s, val.Format(tlayout),
 		)
 		)
 	default:
 	default:
-		dbt.Errorf("%s [%s]: unhandled type %T (is '%s')",
-			dbtype, protocol, val, val,
+		fmt.Printf("%#v\n", []interface{}{dbtype, tlayout, mode, t.s, t.t})
+		dbt.Errorf("%s [%s]: unhandled type %T (is '%v')",
+			dbtype, protocol,
+			val, val,
 		)
 		)
 	}
 	}
 }
 }
@@ -428,6 +432,10 @@ func TestDateTime(t *testing.T) {
 			{t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)},
 			{t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)},
 			{t: t0, s: tstr0[:19]},
 			{t: t0, s: tstr0[:19]},
 		}},
 		}},
+		{"DATETIME(0)", format[:21], []timeTest{
+			{t: time.Date(2011, 11, 20, 21, 27, 37, 0, time.UTC)},
+			{t: t0, s: tstr0[:19]},
+		}},
 		{"DATETIME(1)", format[:21], []timeTest{
 		{"DATETIME(1)", format[:21], []timeTest{
 			{t: time.Date(2011, 11, 20, 21, 27, 37, 100000000, time.UTC)},
 			{t: time.Date(2011, 11, 20, 21, 27, 37, 100000000, time.UTC)},
 			{t: t0, s: tstr0[:21]},
 			{t: t0, s: tstr0[:21]},
@@ -438,23 +446,40 @@ func TestDateTime(t *testing.T) {
 		}},
 		}},
 		{"TIME", format[11:19], []timeTest{
 		{"TIME", format[11:19], []timeTest{
 			{t: afterTime(t0, "12345s")},
 			{t: afterTime(t0, "12345s")},
-			{t: afterTime(t0, "-12345s")},
+			{s: "!-12:34:56"},
+			{s: "!-838:59:59"},
+			{s: "!838:59:59"},
+			{t: t0, s: tstr0[11:19]},
+		}},
+		{"TIME(0)", format[11:19], []timeTest{
+			{t: afterTime(t0, "12345s")},
+			{s: "!-12:34:56"},
+			{s: "!-838:59:59"},
+			{s: "!838:59:59"},
 			{t: t0, s: tstr0[11:19]},
 			{t: t0, s: tstr0[11:19]},
 		}},
 		}},
 		{"TIME(1)", format[11:21], []timeTest{
 		{"TIME(1)", format[11:21], []timeTest{
 			{t: afterTime(t0, "12345600ms")},
 			{t: afterTime(t0, "12345600ms")},
-			{t: afterTime(t0, "-12345600ms")},
+			{s: "!-12:34:56.7"},
+			{s: "!-838:59:58.9"},
+			{s: "!838:59:58.9"},
 			{t: t0, s: tstr0[11:21]},
 			{t: t0, s: tstr0[11:21]},
 		}},
 		}},
 		{"TIME(6)", format[11:], []timeTest{
 		{"TIME(6)", format[11:], []timeTest{
 			{t: afterTime(t0, "1234567890123000ns")},
 			{t: afterTime(t0, "1234567890123000ns")},
-			{t: afterTime(t0, "-1234567890123000ns")},
+			{s: "!-12:34:56.789012"},
+			{s: "!-838:59:58.999999"},
+			{s: "!838:59:58.999999"},
 			{t: t0, s: tstr0[11:]},
 			{t: t0, s: tstr0[11:]},
 		}},
 		}},
 		{"TIMESTAMP", format[:19], []timeTest{
 		{"TIMESTAMP", format[:19], []timeTest{
 			{t: afterTime(ts0, "12345s")},
 			{t: afterTime(ts0, "12345s")},
 			{t: ts0, s: "1970-01-01 00:00:00"},
 			{t: ts0, s: "1970-01-01 00:00:00"},
 		}},
 		}},
+		{"TIMESTAMP(0)", format[:19], []timeTest{
+			{t: afterTime(ts0, "12345s")},
+			{t: ts0, s: "1970-01-01 00:00:00"},
+		}},
 		{"TIMESTAMP(1)", format[:21], []timeTest{
 		{"TIMESTAMP(1)", format[:21], []timeTest{
 			{t: afterTime(ts0, "12345600ms")},
 			{t: afterTime(ts0, "12345600ms")},
 			{t: ts0, s: "1970-01-01 00:00:00.0"},
 			{t: ts0, s: "1970-01-01 00:00:00.0"},
@@ -464,38 +489,50 @@ func TestDateTime(t *testing.T) {
 			{t: ts0, s: "1970-01-01 00:00:00.000000"},
 			{t: ts0, s: "1970-01-01 00:00:00.000000"},
 		}},
 		}},
 	}
 	}
-	dsns := map[string]bool{
-		dsn + "&parseTime=true":                               true,
-		dsn + "&sql_mode=ALLOW_INVALID_DATES&parseTime=true":  true,
-		dsn + "&parseTime=false":                              false,
-		dsn + "&sql_mode=ALLOW_INVALID_DATES&parseTime=false": false,
-	}
-	var withFrac bool
-	if db, err := sql.Open("mysql", dsn); err != nil {
-		t.Fatal(err)
-	} else {
-		rows, err := db.Query(`SELECT CAST("00:00:00.123" AS TIME(3)) = "00:00:00.123"`)
-		if err == nil {
-			withFrac = true
-			rows.Close()
-		}
-		db.Close()
+	dsns := []string{
+		dsn + "&parseTime=true",
+		dsn + "&parseTime=false",
 	}
 	}
-	for testdsn, parseTime := range dsns {
-		var _ = parseTime
+	for _, testdsn := range dsns {
 		runTests(t, testdsn, func(dbt *DBTest) {
 		runTests(t, testdsn, func(dbt *DBTest) {
+			var withFrac, allowsZero bool
+			var rows *sql.Rows
+			var err error
+			rows, err = dbt.db.Query(`SELECT CAST("00:00:00.1" AS TIME(1)) = "00:00:00.1"`)
+			if err == nil {
+				rows.Scan(&withFrac)
+				rows.Close()
+			}
+			rows, err = dbt.db.Query(`SELECT CAST("0000-00-00" AS DATE) = "0000-00-00"`)
+			if err == nil {
+				rows.Scan(&allowsZero)
+				rows.Close()
+			}
 			for _, setups := range testcases {
 			for _, setups := range testcases {
 				if t := setups.dbtype; !withFrac && t[len(t)-1:] == ")" {
 				if t := setups.dbtype; !withFrac && t[len(t)-1:] == ")" {
-					// skip fractional tests if unsupported by DB
+					// skip fractional second tests if unsupported by server
 					continue
 					continue
 				}
 				}
 				for _, setup := range setups.tests {
 				for _, setup := range setups.tests {
+					timeArgBinary := true
 					if setup.s == "" {
 					if setup.s == "" {
 						// fill time string whereever Go can reliable produce it
 						// fill time string whereever Go can reliable produce it
 						setup.s = setup.t.Format(setups.tlayout)
 						setup.s = setup.t.Format(setups.tlayout)
+					} else if setup.s[0] == '!' {
+						// skip tests using setup.t as source in queries
+						timeArgBinary = false
+						// fix setup.s - remove the "!"
+						setup.s = setup.s[1:]
+					}
+					if !allowsZero && setup.s == tstr0[:len(setup.s)] {
+						// skip disallowed 0000-00-00 date
+						continue
+					}
+					setup.run(dbt, setups.dbtype, setups.tlayout, 0)
+					if timeArgBinary {
+						setup.run(dbt, setups.dbtype, setups.tlayout, 1)
 					}
 					}
-					setup.run(dbt, setups.dbtype, setups.tlayout, true)
-					setup.run(dbt, setups.dbtype, setups.tlayout, false)
+					setup.run(dbt, setups.dbtype, setups.tlayout, 2)
 				}
 				}
 			}
 			}
 		})
 		})

+ 28 - 84
packets.go

@@ -954,7 +954,6 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 
 
 // http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
 // http://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
 func (rows *binaryRows) readRow(dest []driver.Value) error {
 func (rows *binaryRows) readRow(dest []driver.Value) error {
-	timestr := "00:00:00.000000"
 	data, err := rows.mc.readPacket()
 	data, err := rows.mc.readPacket()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -1060,98 +1059,43 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			}
 			}
 			return err
 			return err
 
 
-		// Date YYYY-MM-DD
-		case fieldTypeDate, fieldTypeNewDate:
+		case
+			fieldTypeDate, fieldTypeNewDate, // Date YYYY-MM-DD
+			fieldTypeTime,                         // Time [-][H]HH:MM:SS[.fractal]
+			fieldTypeTimestamp, fieldTypeDateTime: // Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
+
 			num, isNull, n := readLengthEncodedInteger(data[pos:])
 			num, isNull, n := readLengthEncodedInteger(data[pos:])
 			pos += n
 			pos += n
 
 
-			if isNull {
+			switch {
+			case isNull:
 				dest[i] = nil
 				dest[i] = nil
 				continue
 				continue
-			}
-
-			if rows.mc.parseTime {
+			case rows.columns[i].fieldType == fieldTypeTime:
+				// database/sql does not support an equivalent to TIME, return a string
+				var dstlen uint8
+				switch decimals := rows.columns[i].decimals; decimals {
+				case 0x00, 0x1f:
+					dstlen = 8
+				case 1, 2, 3, 4, 5, 6:
+					dstlen = 8 + 1 + decimals
+				}
+				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
+			case rows.mc.parseTime:
 				dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
 				dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
-			} else {
-				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], 10)
-			}
-
-			if err == nil {
-				pos += int(num)
-				continue
-			} else {
-				return err
-			}
-
-		// Time [-][H]HH:MM:SS[.fractal]
-		case fieldTypeTime:
-			num, isNull, n := readLengthEncodedInteger(data[pos:])
-			pos += n
-
-			if num == 0 {
-				if isNull {
-					dest[i] = nil
-					continue
+			default:
+				var dstlen uint8
+				if rows.columns[i].fieldType == fieldTypeDate {
+					dstlen = 10
 				} else {
 				} else {
-					length := uint8(8)
-					if rows.columns[i].decimals > 0 {
-						length += 1 + uint8(rows.columns[i].decimals)
+					switch decimals := rows.columns[i].decimals; decimals {
+					case 0x00, 0x1f:
+						dstlen = 19
+					case 1, 2, 3, 4, 5, 6:
+						dstlen = 19 + 1 + decimals
 					}
 					}
-					dest[i] = []byte(timestr[:length])
-					continue
-				}
-			}
-
-			var result string
-			if data[pos] == 1 {
-				result = "-"
-			}
-			var microsecs uint32
-			switch num {
-			case 8:
-				result += fmt.Sprintf(
-					"%02d:%02d:%02d",
-					uint16(data[pos+1])*24+uint16(data[pos+5]),
-					data[pos+6],
-					data[pos+7],
-				)
-				pos += 8
-			case 12:
-				result += fmt.Sprintf(
-					"%02d:%02d:%02d",
-					uint16(data[pos+1])*24+uint16(data[pos+5]),
-					data[pos+6],
-					data[pos+7],
-				)
-				microsecs = binary.LittleEndian.Uint32(data[pos+8 : pos+12])
-				pos += 12
-			default:
-				return fmt.Errorf("Invalid TIME-packet length %d", num)
-			}
-			if decimals := rows.columns[i].decimals; decimals > 0 && decimals <= 6 {
-				result += fmt.Sprintf(".%06d", microsecs)[:1+decimals]
-			}
-			dest[i] = []byte(result)
-
-		// Timestamp YYYY-MM-DD HH:MM:SS[.fractal]
-		case fieldTypeTimestamp, fieldTypeDateTime:
-			num, isNull, n := readLengthEncodedInteger(data[pos:])
-
-			pos += n
-
-			if isNull {
-				dest[i] = nil
-				continue
-			}
-
-			if rows.mc.parseTime {
-				dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.loc)
-			} else {
-				length := uint8(19)
-				if rows.columns[i].decimals > 0 {
-					length += 1 + uint8(rows.columns[i].decimals)
 				}
 				}
-				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], length)
+				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
 			}
 			}
 
 
 			if err == nil {
 			if err == nil {

+ 128 - 64
utils.go

@@ -520,76 +520,140 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
 // The current behavior depends on database/sql copying the result.
 // The current behavior depends on database/sql copying the result.
 var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
 var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
 
 
-func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
+func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
+	// length expects the deterministic length of the zero value,
+	// negative time and 100+ hours are automatically added if needed
+	const pairs = "00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899"
 	if len(src) == 0 {
 	if len(src) == 0 {
+		if justTime {
+			return zeroDateTime[11 : 11+length], nil
+		}
 		return zeroDateTime[:length], nil
 		return zeroDateTime[:length], nil
 	}
 	}
-	var dst []byte
-	switch length {
-	case 10:
-		dst = []byte("0000-00-00")
-	case 19:
-		dst = []byte("0000-00-00 00:00:00")
-	case 21, 22, 23, 24, 25, 26:
-		dst = []byte("0000-00-00 00:00:00.000000")
+	var dst []byte          // return value
+	var pt, p1, p2, p3 byte // current digit pair
+	var zOffs byte          // offset of value in zeroDateTime
+	if justTime {
+		switch length {
+		case
+			8,                      // time (can be up to 10 when negative and 100+ hours)
+			10, 11, 12, 13, 14, 15: // time with fractional seconds
+		default:
+			return nil, fmt.Errorf("illegal TIME length %d", length)
+		}
+		switch len(src) {
+		case 8, 12:
+		default:
+			return nil, fmt.Errorf("Invalid TIME-packet length %d", len(src))
+		}
+		// +2 to enable negative time and 100+ hours
+		dst = make([]byte, 0, length+2)
+		if src[0] == 1 {
+			dst = append(dst, '-')
+		}
+		if src[1] != 0 {
+			hour := uint16(src[1])*24 + uint16(src[5])
+			pt = byte(hour / 100)
+			p1 = byte(hour - 100*uint16(pt))
+			dst = append(dst, '0'+pt)
+		} else {
+			p1 = src[5]
+		}
+		zOffs = 11
+		src = src[6:]
+	} else {
+		switch length {
+		case 10, 19, 21, 22, 23, 24, 25, 26:
+		default:
+			t := "DATE"
+			if length > 10 {
+				t += "TIME"
+			}
+			return nil, fmt.Errorf("illegal %s length %d", t, length)
+		}
+		switch len(src) {
+		case 4, 7, 11:
+		default:
+			t := "DATE"
+			if length > 10 {
+				t += "TIME"
+			}
+			return nil, fmt.Errorf("illegal %s-packet length %d", t, len(src))
+		}
+		dst = make([]byte, 0, length)
+		// start with the date
+		year := binary.LittleEndian.Uint16(src[:2])
+		pt = byte(year / 100)
+		p1 = byte(year - 100*uint16(pt))
+		p2, p3 = src[2], src[3]
+		dst = append(dst,
+			pairs[2*pt], pairs[2*pt+1],
+			pairs[2*p1], pairs[2*p1+1], '-',
+			pairs[2*p2], pairs[2*p2+1], '-',
+			pairs[2*p3], pairs[2*p3+1],
+		)
+		if length == 10 {
+			return dst, nil
+		}
+		if len(src) == 4 {
+			return append(dst, zeroDateTime[10:length]...), nil
+		}
+		dst = append(dst, ' ')
+		p1 = src[4] // hour
+		src = src[5:]
+	}
+	// p1 is 2-digit hour, src is after hour
+	p2, p3 = src[0], src[1]
+	dst = append(dst,
+		pairs[2*p1], pairs[2*p1+1], ':',
+		pairs[2*p2], pairs[2*p2+1], ':',
+		pairs[2*p3], pairs[2*p3+1],
+	)
+	if length <= byte(len(dst)) {
+		return dst, nil
+	}
+	src = src[2:]
+	if len(src) == 0 {
+		return append(dst, zeroDateTime[19:zOffs+length]...), nil
+	}
+	microsecs := binary.LittleEndian.Uint32(src[:4])
+	p1 = byte(microsecs / 10000)
+	microsecs -= 10000 * uint32(p1)
+	p2 = byte(microsecs / 100)
+	microsecs -= 100 * uint32(p2)
+	p3 = byte(microsecs)
+	switch decimals := zOffs + length - 20; decimals {
 	default:
 	default:
-		return nil, fmt.Errorf("illegal datetime length %d", length)
-	}
-	switch len(src) {
-	case 11:
-		microsecs := binary.LittleEndian.Uint32(src[7:11])
-		tmp32 := microsecs / 10
-		dst[25] += byte(microsecs - 10*tmp32)
-		tmp32, microsecs = tmp32/10, tmp32
-		dst[24] += byte(microsecs - 10*tmp32)
-		tmp32, microsecs = tmp32/10, tmp32
-		dst[23] += byte(microsecs - 10*tmp32)
-		tmp32, microsecs = tmp32/10, tmp32
-		dst[22] += byte(microsecs - 10*tmp32)
-		tmp32, microsecs = tmp32/10, tmp32
-		dst[21] += byte(microsecs - 10*tmp32)
-		dst[20] += byte(microsecs / 10)
-		fallthrough
-	case 7:
-		second := src[6]
-		tmp := second / 10
-		dst[18] += second - 10*tmp
-		dst[17] += tmp
-		minute := src[5]
-		tmp = minute / 10
-		dst[15] += minute - 10*tmp
-		dst[14] += tmp
-		hour := src[4]
-		tmp = hour / 10
-		dst[12] += hour - 10*tmp
-		dst[11] += tmp
-		fallthrough
+		return append(dst, '.',
+			pairs[2*p1], pairs[2*p1+1],
+			pairs[2*p2], pairs[2*p2+1],
+			pairs[2*p3], pairs[2*p3+1],
+		), nil
+	case 1:
+		return append(dst, '.',
+			pairs[2*p1],
+		), nil
+	case 2:
+		return append(dst, '.',
+			pairs[2*p1], pairs[2*p1+1],
+		), nil
+	case 3:
+		return append(dst, '.',
+			pairs[2*p1], pairs[2*p1+1],
+			pairs[2*p2],
+		), nil
 	case 4:
 	case 4:
-		day := src[3]
-		tmp := day / 10
-		dst[9] += day - 10*tmp
-		dst[8] += tmp
-		month := src[2]
-		tmp = month / 10
-		dst[6] += month - 10*tmp
-		dst[5] += tmp
-		year := binary.LittleEndian.Uint16(src[:2])
-		tmp16 := year / 10
-		dst[3] += byte(year - 10*tmp16)
-		tmp16, year = tmp16/10, tmp16
-		dst[2] += byte(year - 10*tmp16)
-		tmp16, year = tmp16/10, tmp16
-		dst[1] += byte(year - 10*tmp16)
-		dst[0] += byte(tmp16)
-		return dst[:length], nil
-	}
-	var t string
-	if length >= 19 {
-		t = "DATETIME"
-	} else {
-		t = "DATE"
+		return append(dst, '.',
+			pairs[2*p1], pairs[2*p1+1],
+			pairs[2*p2], pairs[2*p2+1],
+		), nil
+	case 5:
+		return append(dst, '.',
+			pairs[2*p1], pairs[2*p1+1],
+			pairs[2*p2], pairs[2*p2+1],
+			pairs[2*p3],
+		), nil
 	}
 	}
-	return nil, fmt.Errorf("invalid %s-packet length %d", t, len(src))
 }
 }
 
 
 /******************************************************************************
 /******************************************************************************

+ 1 - 1
utils_test.go

@@ -192,7 +192,7 @@ func TestFormatBinaryDateTime(t *testing.T) {
 	rawDate[6] = 23                                    // seconds
 	rawDate[6] = 23                                    // seconds
 	binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
 	binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
 	expect := func(expected string, inlen, outlen uint8) {
 	expect := func(expected string, inlen, outlen uint8) {
-		actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen)
+		actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
 		bytes, ok := actual.([]byte)
 		bytes, ok := actual.([]byte)
 		if !ok {
 		if !ok {
 			t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
 			t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)