Best way to parse date and time in golang

I have many datetime values โ€‹โ€‹included in a string in my golang program. The format is fixed in the number of digits:

2006/01/02 15:04:05 

I started analyzing these dates using the time.Parse function

 const dtFormat = "2006/01/02 15:04:05" func ParseDate1(strdate string) (time.Time, error) { return time.Parse(dtFormat, strdate) } 

but I had a problem with my program. So I tried to tweak it by writing my own parsing function, given that my format is fixed:

 func ParseDate2(strdate string) (time.Time, error) { year, _ := strconv.Atoi(strdate[:4]) month, _ := strconv.Atoi(strdate[5:7]) day, _ := strconv.Atoi(strdate[8:10]) hour, _ := strconv.Atoi(strdate[11:13]) minute, _ := strconv.Atoi(strdate[14:16]) second, _ := strconv.Atoi(strdate[17:19]) return time.Date(year, time.Month(month), day, hour, minute, second, 0, time.UTC), nil } 

Finally, I did a test on top of these two functions and got the following result:

  BenchmarkParseDate1 5000000 343 ns/op BenchmarkParseDate2 10000000 248 ns/op 

This is a 27% performance improvement. Is there a better way in terms of performance that could improve such an analysis of time and time?

+6
source share
2 answers

I would expect to make your program much faster. For example, ParseDate3 ,

 func ParseDate3(date []byte) (time.Time, error) { year := (((int(date[0])-'0')*10+int(date[1])-'0')*10+int(date[2])-'0')*10 + int(date[3]) - '0' month := time.Month((int(date[5])-'0')*10 + int(date[6]) - '0') day := (int(date[8])-'0')*10 + int(date[9]) - '0' hour := (int(date[11])-'0')*10 + int(date[12]) - '0' minute := (int(date[14])-'0')*10 + int(date[15]) - '0' second := (int(date[17])-'0')*10 + int(date[18]) - '0' return time.Date(year, month, day, hour, minute, second, 0, time.UTC), nil } 

Landmarks:

 $ go test -bench=. testing: warning: no tests to run PASS BenchmarkParseDate1 5000000 308 ns/op BenchmarkParseDate2 10000000 225 ns/op BenchmarkParseDate3 30000000 44.9 ns/op ok so/test 5.741s $ go test -bench=. testing: warning: no tests to run PASS BenchmarkParseDate1 5000000 308 ns/op BenchmarkParseDate2 10000000 226 ns/op BenchmarkParseDate3 30000000 45.4 ns/op ok so/test 5.757s $ go test -bench=. testing: warning: no tests to run PASS BenchmarkParseDate1 5000000 312 ns/op BenchmarkParseDate2 10000000 225 ns/op BenchmarkParseDate3 30000000 45.0 ns/op ok so/test 5.761s $ 

Link:

Profile Go Programs


If you insist on using date string , use ParseDate4 ,

 func ParseDate4(date string) (time.Time, error) { year := (((int(date[0])-'0')*10+int(date[1])-'0')*10+int(date[2])-'0')*10 + int(date[3]) - '0' month := time.Month((int(date[5])-'0')*10 + int(date[6]) - '0') day := (int(date[8])-'0')*10 + int(date[9]) - '0' hour := (int(date[11])-'0')*10 + int(date[12]) - '0' minute := (int(date[14])-'0')*10 + int(date[15]) - '0' second := (int(date[17])-'0')*10 + int(date[18]) - '0' return time.Date(year, month, day, hour, minute, second, 0, time.UTC), nil } 
+3
source

From what you have already shown, using strconv.Atoi directly improved your performance. You can push it further and flip your own atoi for your specific use case.

You expect each element to be a positive base number of 10. You also know that it cannot overflow, since the maximum length of the transmitted string representation is 4. The only possible error is an asymmetric character in the string. Knowing this, we can simply do the following:

 var atoiError = errors.New("invalid number") func atoi(s string) (x int, err error) { i := 0 for ; i < len(s); i++ { c := s[i] if c < '0' || c > '9' { err = atoiError return } x = x*10 + int(c) - '0' } return } 

Wrapping this in ParseDate3 , I get the following result:

 BenchmarkParseDate1 5000000 355 ns/op BenchmarkParseDate2 10000000 278 ns/op BenchmarkParseDate3 20000000 88 ns/op 

You can do it faster without returning an error in atoi , but I urge you to test the input anyway (unless it is verified somewhere else in your code).

Alternative atoi approach after viewing the inline solution:

By clicking this even further, you can take advantage of the fact that all but one of the transmitted lines are 2 digits in length (a 4-digit year, but it is multiplied by two). Creating atoi using a 2-digit string will eliminate the for loop. Example:

 // Converts string of 2 characters into a positive integer, returns -1 on error func atoi2(s string) int { x := uint(s[0]) - uint('0') y := uint(s[1]) - uint('0') if x > 9 || y > 9 { return -1 // error } return int(x*10 + y) } 

Converting a year into a number will require a two-step approach:

 year := atoi2(strdate[0:2])*100 + atoi2(strdate[2:4]) 

This gives an additional improvement:

 BenchmarkParseDate4 50000000 61 ns/op 

Please note that the embedded version suggested by @peterSO is only slightly faster (54 ns / op in my case), but the solution above gives you the ability to check for errors, while the embedded version blindly accepts all characters, converting them to dates.

+6
source

Source: https://habr.com/ru/post/978902/


All Articles