Skip to content

Commit

Permalink
Pick changes from #15872 to support nanos
Browse files Browse the repository at this point in the history
  • Loading branch information
kvch committed May 9, 2022
1 parent ac7071b commit 578bb8e
Show file tree
Hide file tree
Showing 17 changed files with 357 additions and 138 deletions.
41 changes: 34 additions & 7 deletions libbeat/common/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,36 @@ import (
"errors"
"hash"
"time"

"github.com/elastic/beats/v7/libbeat/common/dtfmt"
)

// TsLayout is the layout to be used in the timestamp marshaling/unmarshaling everywhere.
// The timezone must always be UTC.
const TsLayout = "2006-01-02T15:04:05.000Z"
const (
// TsLayout is the seconds layout to be used in the timestamp marshaling/unmarshaling everywhere.
// The timezone must always be UTC.
TsLayout = "2006-01-02T15:04:05.000Z"

tsLayoutMillis = "2006-01-02T15:04:05.000Z"
tsLayoutMicros = "2006-01-02T15:04:05.000000Z"
tsLayoutNanos = "2006-01-02T15:04:05.000000000Z"
)

// Time is an abstraction for the time.Time type
type Time time.Time

var defaultTimeFormatter = dtfmt.MustNewFormatter("yyyy-MM-dd'T'HH:mm:ss.fffffffff'Z'")

var defaultParseFormats = []string{
tsLayoutMillis,
tsLayoutMicros,
tsLayoutNanos,
}

// MarshalJSON implements json.Marshaler interface.
// The time is a quoted string in the JsTsLayout format.
func (t Time) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(t).UTC().Format(TsLayout))
str, _ := defaultTimeFormatter.Format(time.Time(t).UTC())
return json.Marshal(str)
}

// UnmarshalJSON implements js.Unmarshaler interface.
Expand All @@ -54,14 +71,24 @@ func (t Time) Hash32(h hash.Hash32) error {
return err
}

// ParseTime parses a time in the TsLayout format.
// ParseTime parses a time in the NanoTsLayout format first, then use millisTsLayout format
func ParseTime(timespec string) (Time, error) {
t, err := time.Parse(TsLayout, timespec)
var err error
var t time.Time

for _, layout := range defaultParseFormats {
t, err = time.Parse(layout, timespec)
if err == nil {
break
}
}

return Time(t), err
}

func (t Time) String() string {
return time.Time(t).UTC().Format(TsLayout)
str, _ := defaultTimeFormatter.Format(time.Time(t).UTC())
return str
}

// MustParseTime is a convenience equivalent of the ParseTime function
Expand Down
2 changes: 1 addition & 1 deletion libbeat/common/datetime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestParseTimeNegative(t *testing.T) {
tests := []inputOutput{
{
Input: "2015-02-29TT14:06:05.071Z",
Err: "parsing time \"2015-02-29TT14:06:05.071Z\" as \"2006-01-02T15:04:05.000Z\": cannot parse \"T14:06:05.071Z\" as \"15\"",
Err: "parsing time \"2015-02-29TT14:06:05.071Z\" as \"2006-01-02T15:04:05.000000000Z\": cannot parse \"T14:06:05.071Z\" as \"15\"",
},
}

Expand Down
43 changes: 27 additions & 16 deletions libbeat/common/dtfmt/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,26 +97,37 @@ func (b *builder) add(e element) {
b.elements = append(b.elements, e)
}

func (b *builder) millisOfSecond(digits int) {
func (b *builder) nanoOfSecond(digits int) {
if digits <= 0 {
return
}

switch digits {
case 1:
b.appendExtDecimal(ftMillisOfSecond, 100, 1, 1)
case 2:
b.appendExtDecimal(ftMillisOfSecond, 10, 2, 2)
case 3:
b.appendExtDecimal(ftMillisOfSecond, 0, 3, 3)
default:
b.appendExtDecimal(ftMillisOfSecond, 0, 3, 3)
b.appendZeros(digits - 3)
if digits <= 9 {
b.appendExtDecimal(ftNanoOfSecond, 9-digits, digits, digits)
} else {
b.appendExtDecimal(ftNanoOfSecond, 0, 9, 9)
b.appendZeros(digits - 9)
}
}

func (b *builder) millisOfDay(digits int) {
b.appendDecimal(ftMillisOfDay, digits, 8)
func (b *builder) fractNanoOfSecond(digits int) {
const fractDigits = 3

if digits <= 0 {
return
}

// cap number of digits at 9, as we do not support higher precision and
// would remove trailing zeroes anyway.
if digits > 9 {
digits = 9
}

minDigits := fractDigits
if digits < minDigits {
minDigits = digits
}
b.add(paddedNumber{ftNanoOfSecond, 9 - digits, minDigits, digits, fractDigits, false})
}

func (b *builder) secondOfMinute(digits int) {
Expand Down Expand Up @@ -233,12 +244,12 @@ func (b *builder) appendDecimalValue(ft fieldType, minDigits, maxDigits int, sig
if minDigits <= 1 {
b.add(unpaddedNumber{ft, maxDigits, signed})
} else {
b.add(paddedNumber{ft, 0, minDigits, maxDigits, signed})
b.add(paddedNumber{ft, 0, minDigits, maxDigits, 0, signed})
}
}

func (b *builder) appendExtDecimal(ft fieldType, div, minDigits, maxDigits int) {
b.add(paddedNumber{ft, div, minDigits, maxDigits, false})
func (b *builder) appendExtDecimal(ft fieldType, divExp, minDigits, maxDigits int) {
b.add(paddedNumber{ft, divExp, minDigits, maxDigits, 0, false})
}

func (b *builder) appendDecimal(ft fieldType, minDigits, maxDigits int) {
Expand Down
12 changes: 6 additions & 6 deletions libbeat/common/dtfmt/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type ctx struct {
isoWeek, isoYear int

hour, min, sec int
millis int
nano int

tzOffset int

Expand All @@ -43,7 +43,7 @@ type ctxConfig struct {
clock bool
weekday bool
yearday bool
millis bool
nano bool
iso bool
tzOffset bool
}
Expand All @@ -59,8 +59,8 @@ func (c *ctx) initTime(config *ctxConfig, t time.Time) {
c.isoYear, c.isoWeek = t.ISOWeek()
}

if config.millis {
c.millis = t.Nanosecond() / 1000000
if config.nano {
c.nano = t.Nanosecond()
}

if config.yearday {
Expand All @@ -84,8 +84,8 @@ func (c *ctxConfig) enableClock() {
c.clock = true
}

func (c *ctxConfig) enableMillis() {
c.millis = true
func (c *ctxConfig) enableNano() {
c.nano = true
}

func (c *ctxConfig) enableWeekday() {
Expand Down
49 changes: 25 additions & 24 deletions libbeat/common/dtfmt/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,31 @@
//
// Symbol Meaning Type Supported Examples
// ------ ------- ------- --------- -------
// G era text no AD
// C century of era (&gt;=0) number no 20
// Y year of era (&gt;=0) year yes 1996
//
// x weekyear year yes 1996
// w week of weekyear number yes 27
// e day of week number yes 2
// E day of week text yes Tuesday; Tue
//
// y year year yes 1996
// D day of year number yes 189
// M month of year month yes July; Jul; 07
// d day of month number yes 10
//
// a halfday of day text yes PM
// K hour of halfday (0~11) number yes 0
// h clockhour of halfday (1~12) number yes 12
//
// H hour of day (0~23) number yes 0
// k clockhour of day (1~24) number yes 24
// m minute of hour number yes 30
// s second of minute number yes 55
// S fraction of second millis no 978
//
// G era text no AD
// C century of era (&gt;=0) number no 20
// Y year of era (&gt;=0) year yes 1996
//
// x weekyear year yes 1996
// w week of weekyear number yes 27
// e day of week number yes 2
// E day of week text yes Tuesday; Tue
//
// y year year yes 1996
// D day of year number yes 189
// M month of year month yes July; Jul; 07
// d day of month number yes 10
//
// a halfday of day text yes PM
// K hour of halfday (0~11) number yes 0
// h clockhour of halfday (1~12) number yes 12
//
// H hour of day (0~23) number yes 0
// k clockhour of day (1~24) number yes 24
// m minute of hour number yes 30
// s second of minute number yes 55
// S fraction of second nanoseconds yes 978000
// f fraction of seconds nanoseconds yes 123456789
// multiple of 3
// z time zone text no Pacific Standard Time; PST
// Z time zone offset/id zone no -0800; -08:00; America/Los_Angeles
//
Expand Down
63 changes: 51 additions & 12 deletions libbeat/common/dtfmt/dtfmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,37 @@ func TestFormat(t *testing.T) {
{mkTime(8, 5, 24, 0), "kk:mm:ss aa", "09:05:24 AM"},
{mkTime(20, 5, 24, 0), "k:m:s a", "21:5:24 PM"},
{mkTime(20, 5, 24, 0), "kk:mm:ss aa", "21:05:24 PM"},
{mkTime(1, 2, 3, 123), "S", "1"},
{mkTime(1, 2, 3, 123), "SS", "12"},
{mkTime(1, 2, 3, 123), "SSS", "123"},
{mkTime(1, 2, 3, 123), "SSSS", "1230"},
{mkTime(1, 2, 3, 123*time.Millisecond), "S", "1"},
{mkTime(1, 2, 3, 123*time.Millisecond), "SS", "12"},
{mkTime(1, 2, 3, 123*time.Millisecond), "SSS", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "SSSS", "1230"},
{mkTime(1, 2, 3, 123*time.Millisecond), "f", "1"},
{mkTime(1, 2, 3, 123*time.Millisecond), "ff", "12"},
{mkTime(1, 2, 3, 123*time.Millisecond), "fff", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "ffff", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "fffff", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "ffffff", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "fffffff", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "ffffffff", "123"},
{mkTime(1, 2, 3, 123*time.Millisecond), "fffffffff", "123"},
{mkTime(1, 2, 3, 123*time.Microsecond), "f", "0"},
{mkTime(1, 2, 3, 123*time.Microsecond), "ff", "00"},
{mkTime(1, 2, 3, 123*time.Microsecond), "fff", "000"},
{mkTime(1, 2, 3, 123*time.Microsecond), "ffff", "0001"},
{mkTime(1, 2, 3, 123*time.Microsecond), "fffff", "00012"},
{mkTime(1, 2, 3, 123*time.Microsecond), "ffffff", "000123"},
{mkTime(1, 2, 3, 123*time.Microsecond), "fffffff", "000123"},
{mkTime(1, 2, 3, 123*time.Microsecond), "ffffffff", "000123"},
{mkTime(1, 2, 3, 123*time.Microsecond), "fffffffff", "000123"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "f", "0"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "ff", "00"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "fff", "000"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "ffff", "000"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "fffff", "000"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "ffffff", "000"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "fffffff", "0000001"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "ffffffff", "00000012"},
{mkTime(1, 2, 3, 123*time.Nanosecond), "fffffffff", "000000123"},

// literals
{time.Now(), "--=++,_!/?\\[]{}@#$%^&*()", "--=++,_!/?\\[]{}@#$%^&*()"},
Expand All @@ -94,14 +121,26 @@ func TestFormat(t *testing.T) {
{time.Now(), "'plain' '' 'text'", "plain ' text"},
{time.Now(), "'plain '' text'", "plain ' text"},

// beats timestamp
{mkDateTime(2017, 1, 2, 4, 6, 7, 123),
// timestamps with microseconds precision only
{mkDateTime(2017, 1, 2, 4, 6, 7, 123*time.Millisecond),
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"2017-01-02T04:06:07.123Z"},
{mkDateTime(2017, 1, 2, 4, 6, 7, 123456*time.Microsecond),
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"2017-01-02T04:06:07.123Z"},

// beats timestamp
{mkDateTimeWithLocation(2017, 1, 2, 4, 6, 7, 123, time.FixedZone("PST", -8*60*60)),
"yyyy-MM-dd'T'HH:mm:ss.SSSz",
{mkDateTimeWithLocation(2017, 1, 2, 4, 6, 7, 123*time.Millisecond, time.FixedZone("PST", -8*60*60)),
"yyyy-MM-dd'T'HH:mm:ss.fffffffffz",
"2017-01-02T04:06:07.123-08:00"},

// beats nanoseconds timestamp
{mkDateTime(2017, 1, 2, 4, 6, 7, 123*time.Nanosecond),
"yyyy-MM-dd'T'HH:mm:ss.fffffffff'Z'",
"2017-01-02T04:06:07.000000123Z"},

{mkDateTimeWithLocation(2017, 1, 2, 4, 6, 7, 123*time.Millisecond, time.FixedZone("PST", -8*60*60)),
"yyyy-MM-dd'T'HH:mm:ss.fffffffffz",
"2017-01-02T04:06:07.123-08:00"},
}

Expand All @@ -123,14 +162,14 @@ func mkDate(y, m, d int) time.Time {
return mkDateTime(y, m, d, 0, 0, 0, 0)
}

func mkTime(h, m, s, S int) time.Time {
func mkTime(h, m, s int, S time.Duration) time.Time {
return mkDateTime(2000, 1, 1, h, m, s, S)
}

func mkDateTime(y, M, d, h, m, s, S int) time.Time {
func mkDateTime(y, M, d, h, m, s int, S time.Duration) time.Time {
return mkDateTimeWithLocation(y, M, d, h, m, s, S, time.UTC)
}

func mkDateTimeWithLocation(y, M, d, h, m, s, S int, l *time.Location) time.Time {
return time.Date(y, time.Month(M), d, h, m, s, S*1000000, l)
func mkDateTimeWithLocation(y, M, d, h, m, s int, S time.Duration, l *time.Location) time.Time {
return time.Date(y, time.Month(M), d, h, m, s, int(S), l)
}
24 changes: 12 additions & 12 deletions libbeat/common/dtfmt/elems.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ type unpaddedNumber struct {
}

type paddedNumber struct {
ft fieldType
div int
minDigits, maxDigits int
signed bool
ft fieldType
divExp int
minDigits, maxDigits, fractDigits int
signed bool
}

type textField struct {
Expand Down Expand Up @@ -123,12 +123,8 @@ func numRequires(c *ctxConfig, ft fieldType) error {
ftSecondOfMinute:
c.enableClock()

case ftMillisOfDay:
c.enableClock()
c.enableMillis()

case ftMillisOfSecond:
c.enableMillis()
case ftNanoOfSecond:
c.enableNano()
}

return nil
Expand Down Expand Up @@ -191,10 +187,14 @@ func (n unpaddedNumber) compile() (prog, error) {
}

func (n paddedNumber) compile() (prog, error) {
if n.div == 0 {
switch {
case n.fractDigits != 0:
return makeProg(opExtNumFractPadded, byte(n.ft), byte(n.divExp), byte(n.maxDigits), byte(n.fractDigits))
case n.divExp == 0:
return makeProg(opNumPadded, byte(n.ft), byte(n.maxDigits))
default:
return makeProg(opExtNumPadded, byte(n.ft), byte(n.divExp), byte(n.maxDigits))
}
return makeProg(opExtNumPadded, byte(n.ft), byte(n.div), byte(n.maxDigits))
}

func (n twoDigitYear) compile() (prog, error) {
Expand Down
Loading

0 comments on commit 578bb8e

Please sign in to comment.