Unix timestamps are a common way to format timestamps. They’re rather convenient because they’re formatted as number (usually integers).
However, in Go we often we often want “time values” to have the time.Time
type because that’s what the rest of the time
package works with.
To convert logs or other textual data to time.Time
values we need to take a two-step approach:
- Parse the text to a number.
- Pass that number to the right
time.Unix*
function.
However, there are some nuances. Not every system, language or library formats Unix timestamps the exact same way. There is often a difference in precision and sometimes fractional notation is used.
To succesfully parse Unix timestamps you will need to know what format your data uses.
What is Unix Time?
Unix timestamps are an expression of "Unix Time". A date and time representation that has its roots in the development of the Unix operating system.
Unix Time measures the number of seconds that have elapsed since "the (Unix) Epoch": 00:00:00 UTC on 1 January 1970. Which is why it's sometimes called "Epoch time".
Every day has exactly 86400
seconds, Unix Time does not adjust for leap seconds.
Precision
Unix timestamps are sometimes formatted in a higher resolution than seconds, modern computers are pretty fast after all:
- Milliseconds (
1000th
of a second). - Microseconds (
1000000th
of a second). - Nanoseconds (
1000000000th
of a second).
The Go time
package provides several functions to create time.Time
values from inputs of different precisions:
time.Unix
takes seconds and nanosecond inputs. It sums the two inputs.time.UnixMicro
takes microseconds as input.time.UnixMilli
takes milliseconds as input.
The example at the bottom of the page shows how they work in detail.
Fractional seconds (timestamp with dot)
Sometimes Unix timestamps contain a fractional component and look like this:
1704196185.8095
This usually means that the decimal part are whole seconds, and the fractional part is a fraction of a second.
To parse timestamps like this into Go time.Time
values I take the following approach:
- Call
strings.Split(txt, ".")
to split on the decimal seperator. - Parse the decimal part and fractional part seperately.
- Multiply the fractional part with
100000000
to go from deciseconds to nanoseconds. - Pass both values to
time.Unix
to construct atime.Time
value.
A concrete example is shown below in the ParseUnixFrac
function.
Local Location
The times returned by the time.Unix*
functions all are in the local location: time.Local
.
Depending on your system configuration, you might want to convert them to another location (like UTC
). This can be done using the t.UTC
or t.In
methods.
If you’re unsure what any of this means, check out this article on Time and Location.
package main
import (
"fmt"
"log"
"strconv"
"strings"
"time"
)
func mustParseInt64(s string) int64 {
d, err := strconv.ParseInt(s, 10, 0)
if err != nil {
log.Fatalf("failed to parse int: %v", err)
}
return d
}
func main() {
// Step 1. first we need to parse strings to integers.
var (
sec = mustParseInt64("1704209323")
milli = mustParseInt64("1704209323000")
micro = mustParseInt64("1704209323000000")
nano = mustParseInt64("1704209323000000000")
)
// Step 2. Pass the integer values to the right functions.
tsec := time.Unix(sec, 0)
tmilli := time.UnixMilli(milli)
tmicro := time.UnixMicro(micro)
tnano := time.Unix(0, nano)
// Should all print the same.
fmt.Println(tsec)
fmt.Println(tmilli)
fmt.Println(tmicro)
fmt.Println(tnano)
// Half a second after the earlier timestamps.
tfrac, err := ParseUnixFrac("1704209323.5")
if err != nil {
log.Fatalf("failed to parse fractional unix timestamp: %v", err)
}
fmt.Println(tfrac)
}
// ParseUnixFrac parses an Unix timestamp that uses fractional seconds.
// It assumes a decimal and fractional part are both present.
func ParseUnixFrac(s string) (time.Time, error) {
// split on the decimal seperator '.'
parts := strings.Split(s, ".")
if len(parts) != 2 {
return time.Time{}, fmt.Errorf("expected decimal and fractional parts, got %d part", len(parts))
}
// parse the decimal and fractional parts.
decimal, err := strconv.ParseInt(parts[0], 10, 0)
if err != nil {
return time.Time{}, fmt.Errorf("failed to parse decimal part: %v", err)
}
frac, err := strconv.ParseInt(parts[1], 10, 0)
if err != nil {
return time.Time{}, fmt.Errorf("failed to parse decimal part: %v", err)
}
// multiply from deciseconds to nanoseconds for the fractional part.
return time.Unix(decimal, frac*100_000_000), nil
}
Get my free newsletter every second week
Used by 500+ developers to boost their Go skills.
"I'll share tips, interesting links and new content. You'll also get a brief guide to time for developers for free."