func Observe(d Datum, v float64, ts time.Time) {
	switch d := d.(type) {
	case *Buckets:
		d.Observe(v, ts)
	default:
		panic(fmt.Sprintf("datum %v is not a Buckets", d))
	}
}
Observe records an observation v at time ts in d, or panics if d is not a BucketsDatum.
package datum
import (
	"fmt"
	"math"
	"sort"
	"sync/atomic"
	"time"
)
type Datum interface {
	ValueString() string
	TimeString() string
	TimeUTC() time.Time
}
type BaseDatum struct {
	Time int64  
}
var zeroTime time.Time
func (d *BaseDatum) stamp(timestamp time.Time) {
	if timestamp.IsZero() {
		atomic.StoreInt64(&d.Time, time.Now().UTC().UnixNano())
	} else {
		atomic.StoreInt64(&d.Time, timestamp.UnixNano())
	}
}
func (d *BaseDatum) TimeString() string {
	return fmt.Sprintf("%d", atomic.LoadInt64(&d.Time)/1e9)
}
func (d *BaseDatum) TimeUTC() time.Time {
	tNsec := atomic.LoadInt64(&d.Time)
	return time.Unix(tNsec/1e9, tNsec%1e9)
}
func NewInt() Datum {
	return MakeInt(0, zeroTime)
}
func NewFloat() Datum {
	return MakeFloat(0., zeroTime)
}
func NewString() Datum {
	return MakeString("", zeroTime)
}
func NewBuckets(buckets []Range) Datum {
	return MakeBuckets(buckets, zeroTime)
}
func MakeInt(v int64, ts time.Time) Datum {
	d := &Int{}
	d.Set(v, ts)
	return d
}
func MakeFloat(v float64, ts time.Time) Datum {
	d := &Float{}
	d.Set(v, ts)
	return d
}
func MakeString(v string, ts time.Time) Datum {
	d := &String{}
	d.Set(v, ts)
	return d
}
func MakeBuckets(buckets []Range, ts time.Time) Datum {
	d := &Buckets{}
	seenInf := false
	highest := 0.0
	for _, b := range buckets {
		d.AddBucket(b)
		if math.IsInf(b.Max, +1) {
			seenInf = true
		} else if b.Max > highest {
			highest = b.Max
		}
	}
	if !seenInf {
		d.AddBucket(Range{highest, math.Inf(+1)})
	}
	return d
}
func GetInt(d Datum) int64 {
	switch d := d.(type) {
	case *Int:
		return d.Get()
	default:
		panic(fmt.Sprintf("datum %v is not an Int", d))
	}
}
func GetFloat(d Datum) float64 {
	switch d := d.(type) {
	case *Float:
		return d.Get()
	default:
		panic(fmt.Sprintf("datum %v is not a Float", d))
	}
}
func GetString(d Datum) string {
	switch d := d.(type) {
	case *String:
		return d.Get()
	default:
		panic(fmt.Sprintf("datum %v is not a String", d))
	}
}
func SetInt(d Datum, v int64, ts time.Time) {
	switch d := d.(type) {
	case *Int:
		d.Set(v, ts)
	case *Buckets:
		d.Observe(float64(v), ts)
	default:
		panic(fmt.Sprintf("datum %v is not an Int", d))
	}
}
func SetFloat(d Datum, v float64, ts time.Time) {
	switch d := d.(type) {
	case *Float:
		d.Set(v, ts)
	case *Buckets:
		d.Observe(v, ts)
	default:
		panic(fmt.Sprintf("datum %v is not a Float", d))
	}
}
func SetString(d Datum, v string, ts time.Time) {
	switch d := d.(type) {
	case *String:
		d.Set(v, ts)
	default:
		panic(fmt.Sprintf("datum %v is not a String", d))
	}
}
func IncIntBy(d Datum, v int64, ts time.Time) {
	switch d := d.(type) {
	case *Int:
		d.IncBy(v, ts)
	default:
		panic(fmt.Sprintf("datum %v is not an Int", d))
	}
}
func DecIntBy(d Datum, v int64, ts time.Time) {
	switch d := d.(type) {
	case *Int:
		d.DecBy(v, ts)
	default:
		panic(fmt.Sprintf("datum %v is not an Int", d))
	}
}
func GetBuckets(d Datum) *Buckets {
	switch d := d.(type) {
	case *Buckets:
		return d
	default:
		panic(fmt.Sprintf("datum %v is not a Buckets", d))
	}
}