Parsing a time with the format HHMMSS00 - go

I'm working with some data from multiple sources and one of these sources is a Sage ERP system.
I am trying to reference two files in Sage in particular, an audit date and audit time (AUDTDATE and AUDTTIME).
I need to parse this and store it as a DATETIME in a Microsoft SQL Server database.
Currently, I am just trying to figure out the best way to parse this.
An example of what the data might look like is below:
+----------+----------+
| AUDTDATE | AUDTTIME |
+----------+----------+
| 20170228 | 5013756 |
+----------+----------+
AUDTDATE is a yyyymmdd format and the AUDTTIME is HHMMSS00.
So I tried the below as a test:
func main() {
value := "20170228 5013756"
layout := "20060102 15040500"
t, _ := time.Parse(layout, value)
fmt.Println(t)
}
This doesn't work, it just returns 0001-01-01 00:00:00 +0000 UTC when run.
If I change the time to this 050137 and the layout to 150405 then this works fine:
func main() {
value := "20170228 050137"
layout := "20060102 150405"
t, _ := time.Parse(layout, value)
fmt.Println(t)
}
One way that I can think of to deal with this is to strip the milliseconds off from the end and then check the length and add a zero to the beginning if it needs one.
This seems like a pretty ugly solution and would involve doing something like this:
func main() {
date := "20170228"
timeString := "5013756"
value := date + prepareTime(timeString)
layout := "20060102150405"
t, _ := time.Parse(layout, value)
fmt.Println(t)
}
func prepareTime(time string) string {
if len(time) == 7 {
time = "0" + time
}
return time[:6]
}
Is there a way to do this without going through the above? Perhaps natively with the time package?

Assuming that you're pulling back 2 separate values from the DB, you can use fmt.Sprintf to 0 pad timeString. Combining it with the date string, you can use the following:
value := fmt.Sprintf("%s %08s", date, timeString)[:15]
In your code:
func main() {
date := "20170228"
timeString := "5013756"
value := fmt.Sprintf("%s %08s", date, timeString)[:15]
layout := "20060102 150405"
t, _ := time.Parse(layout, value)
fmt.Println(t)
}
Results:
2017-02-28 05:01:37 +0000 UTC
This approach is useful because it will also correctly pad any shorter value of time, e.g. 13756 will be converted to 00013756.
The fmt.Sprintf function is useful to format arguments into a string using the formatting you desire as specified by a format string and a list of arguments (...interface{}). The format string tells the function how to render the arguments.
This format string uses two items of note:
String verb (%s): The format string uses a variety of verbs that are used for string substitutions. %s is specifically to render a string or a slice. Other popular verbs include %d for base 10 integer and %f for float with a complete list in the docs. The %v verb is very useful can also be used here as it will render an argument's default value.
0 left padding: To 0 left pad an argument, use 0 followed by a length number in the verb after the %. This will prepended the argument with a maximum number of 0s specified in the length number. For example, %08s will render a string with up to 8 prepended zeros. This means a string "" will be "00000000" while a string "1234567" will result in "01234567". If the string is longer than the length, nothing will be prepended.
From the documentation:
%s the uninterpreted bytes of the string or slice
0 pad with leading zeros rather than spaces;
for numbers, this moves the padding after the sign
More detailed is available in the documentation: https://golang.org/pkg/fmt/

Related

Work out if 2022-01-14T20:56:55Z is a valid date time in Go

I am attempting to create a function that tells me if a timestamp is valid or not.
My function looks like
// IsTimestamp checks if a string contains a timestamp.
func IsTimestamp(str string) bool {
_, err := time.Parse("2006-01-02 15:04:05.999", str)
if err != nil {
return false
}
return true
}
However, passing in 2022-01-14T20:56:55Z returns false when is it a valid timestamp.
I'm thinking this might be something to do with the layout I am using in time.Parse but I've tried just using the date with no luck.
Your layout doesn't match your input string, so it's expected that it isn't parsed successfully.
The docs say:
Parse parses a formatted string and returns the time value it represents. See the documentation for the constant called Layout to see how to represent the format. The second argument must be parseable using the format string (layout) provided as the first argument.
Therefore, you should use a layout that's matching your input. Below, I am using RFC3339, which is the layout of your input string.
if _, err := time.Parse(time.RFC3339, str); err != nil {
...
}
https://go.dev/play/p/_Q26NS2wwfy
2022-01-14T20:56:55Z does not match the layout 2006-01-02 15:04:05.999 because:
the layout expects a whitespace after day, not T
the layout expects exactly three digits for milliseconds (only two are given 55)
the layout does not expect the timezone to be specified. Z is not a valid.
You can match 2022-01-14T20:56:55Z with the layout 2006-01-02T15:04:05.99Z, or 2006-01-02T15:04:05Z. Or even better, use The Fool's answer.

Golang map[string]float where float is getting overwirtten instead of adding to the existing value present for the key

I have a string like so
00:01:07,400-234-090 00:05:01, 701-080-080 00:05:00, 400-234-090
where the on the right side is the phone number and on the left is the duration of the call in hh:mm:ss format. I am trying to put this in a map[string]float64 by splitting the string first on "," and the split the left side on ":". Then make a Duration from the duration of the call and convert in to minutes. It works fine till this.
Now I am trying to put this in a map, I expected that if the key which is the phone number on the right is already present in the map then it will just add the float64 value to the existing value of the key. However, that is not the case, it seems to be having the same key twice in the map.
Here is my code:
phoneBill := `00:01:07,400-234-090
00:05:01, 701-080-080
00:05:00, 400-234-090`
callLog := strings.Split(phoneBill, "\n")
mapDetails := make(map[string]float64)
for _, v := range callLog {
callDetails := strings.Split(strings.TrimSpace(v), ",")
timeDetails := strings.Split(strings.TrimSpace(callDetails[0]), ":")
durationString := strings.TrimSpace(timeDetails[0]) + "h" + strings.TrimSpace(timeDetails[1]) + "m" + strings.TrimSpace(timeDetails[2]) + "s"
t, _ := time.ParseDuration(durationString)
total := t.Minutes()
fmt.Printf("phone number is: %v \n", callDetails[1])
fmt.Printf("duration of call in minutes is %v \n", total)
if v, found := mapDetails[callDetails[1]]; found {
total += v
fmt.Println("total is :v", total)
}
mapDetails[(callDetails[1])] = total
}
fmt.Println("Values in map are: %v", mapDetails)
https://go.dev/play/p/fLcEDbgQ-7q
Fix by trimming spaces on the duration and the number. The current code does not handle spaces before or after the comma.
i := strings.Index(v, ",")
if i < 0 {
log.Fatal("bad line", v)
}
dur := strings.TrimSpace(v[:i])
num := strings.TrimSpace(v[i+1:])
Taking advantage of the fact that maps return the zero value for missing keys, the code to update the map can be simplified to the following.
mapDetails[num] += total
Run the code on the playground.
When debugging code that parses strings, it's helpful to make whitespace visible by printing with %q. The bug in the original program is more visible with:
fmt.Printf("phone number is: %q \n", callDetails[1])

How to parse timestamp with underscores in Golang

I'm trying to parse access log timestamp like "2020/11/06_18:17:25_455" in Filebeat according to Golang spec.
Here is my test program to verify layout:
package main
import (
"fmt"
"log"
"time"
)
func main() {
eventDateLayout := "2006/01/02_15:04:05_000"
eventCheckDate, err := time.Parse(eventDateLayout, "2020/11/06_18:17:25_455")
if err != nil {
log.Fatal(err)
}
fmt.Println(eventCheckDate)
}
Result:
2009/11/10 23:00:00 parsing time "2020/11/06_18:17:25_455" as
"2006/01/02_15:04:05_000": cannot parse "455" as "_000"
As I understand underscore has a special meaning in Golang, but from documentation it's not clear how to escape it.
Any ideas, please?
It doesn't seem possible to use any escape characters for the time layout (e.g. "\\_" doesn't work), so one would have to do something different.
This issue describes the same problem, but it was solved in a very non-general way that doesn't seem to apply to your format.
So your best bet seems to be replacing _ with something else/stripping it from the string, then using a layout without it. To make sure that the millisecond part ist also parsed, it must be separated with a . instead of _, then it's recognized as part of the seconds (05) format.
eventDateLayout := "2006/01/02.15:04:05"
val := strings.Replace("2020/11/06_18:17:25_455", "_", ".", 2)
eventCheckDate, err := time.Parse(eventDateLayout, val)
if err != nil {
panic(err)
}
fmt.Println(eventCheckDate)
Playground link
From time.Format
A fractional second is represented by adding a period and zeros to the
end of the seconds section of layout string, as in "15:04:05.000" to
format a time stamp with millisecond precision.
You cannot specify millisecond precision with an underscore you need 05.000 instead:
// eventDateLayout := "2006/01/02_15:04:05_000" // invalid format
eventDateLayout := "2006/01/02_15:04:05.000"
eventCheckDate, err := time.Parse(eventDateLayout, "2020/11/06_18:17:25.455")
So basically use a simple translate function to convert the final _ to a . and use the above parser.
https://play.golang.org/p/POPgXC_qe81

How to use multiple formats to parse dates in a loop?

I am trying to read a huge csv file with a date column having value in 2 possible formats which are non-standard...
12/28/2015 -- mm/dd/yyyy
11/2/2013 -- mm/d/yyyy
...meaning the middle day component can be single or double digit.
I learnt how to use format from this nice old question: Parsing date/time strings which are not 'standard' formats. But since i am going in a loop trying to parse each row, i can specify only one format to be used at a time. Now it errors on finding date value of different format. Maybe i can code to catch error when parse-using-format#1 fails and then apply format#2, rather than erroring out. But could someone please point me to a better/correct way?
A sample code with array of date strings: https://play.golang.org/p/aloIQnrkOjK
package main
import (
"fmt"
"time"
)
func main() {
const format = "01/02/2006" //mm/dd/yyyy
var list [2]string = [2]string{"12/28/2015", "11/2/2013"}
for _, data := range list {
t, err := time.Parse(format, data)
if err != nil {
fmt.Println("Error is: ", err)
} else {
fmt.Println("Value is: ", t)
}
}
}
//Output:
Value is: 2015-12-28 00:00:00 +0000 UTC
Error is: parsing time "11/2/2013" as "01/02/2006": cannot parse "2/2013" as "02"
The code in the question parses multiple dates with a single format. The problem is that one of the dates does not match the format (missing leading zero on day). Fix by making the leading zero optional:
const format = "1/2/2006" // remove zero before day (and month just in case)

Dynamically built formatting strings in Golang

I am attempting to create a formatting string dynamically in Go. The idea is that a map is created with the formatting types. Then a loop goes through them to output the various types.
The end result is to see how the formatting affects the output.
(I appreciate the example will produce the same output, but I would change f over time to other types)
Below is an example of this:
import (
"fmt"
"strings"
)
var formats = []string{"%f", "%v"}
var f float32 = 1 << 24
func main() {
for format := range formats {
// Generate formatting string here
parts := "%q => " + format + "\n"
fmt.Printf(parts, format, f)
}
}
The compiler is complaining about a int() conversion at the parts: line:
at line 11, file ch3/floating_point.go cannot convert "%q => " to type int
at line 11, file ch3/floating_point.go invalid operation: "%q => " + format` (mismatched types string and int)
I have attempted joining strings, but no luck:
parts:= strings.Join([]string{"%q =>",format,"\n"), " ")
fmt.Printf(parts,format,f)
Also fmt.Fprintf isn't helping either:
for format := range formats {
// Generate formatting string here
parts := fmt.Fprintf("%q => " + format, format, f)
fmt.Println(parts)
}
The issue is in your for format := range formats construct. Keep in mind that range formats will return two values: the actual value from the slice *and its index (which is the first value). So "%q => " + format + "\n" will actually try to concatenate "%s => " with the numeric iteration index.
If you want to iterate over the values contained in the slice, use the following loop for that:
for _, format := range formats {
// ...
}
See golang For and blank identifier
If you're looping over an array, slice, string, or map, or reading
from a channel, a range clause can manage the loop.
for key, value := range oldMap {
newMap[key] = value
}
Right way to range with array
for index, value := range formats {
if you want to skip index do
for _, value := range formats {

Resources