How to get monotonic part of time.Time in Go - go

am currently working with timestamps and I would like to store in a variable the monotonic clock reading section.
Let's say that I have this: 2022-03-31 10:20:26.370463 +0200 CEST m=+0.007725255 then I would like to get m=+0.007725255 in a different var, or at least the +0.007725255 section. What is the idiomatic way in Go to get it?

A Go time.Time stores 2 timestamps:
Wall clock
Monotonic duration since process start (optional, via time.Now)
m=+0.007725255 represents the monotonic duration since the start of the process (when present in a time.Time).
Go calculates this offset by recording time.startNano timestamp during initialisation (not public). time.Now uses startNano to calculate the monotonic duration stored in time.Time. There is no simple public API to directly retrieve this value since it should never be needed.
In practice, you should simply subtract 2 timestamps generated via time.Now in your current process and the result will be the monotonic duration. If you need to know the duration since process startup you should record a startup timestamp during initalisation.
Example:
package main
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
"time"
)
func main() {
t0 := time.Now()
fmt.Println("...example event...")
time.Sleep(time.Millisecond)
t1 := time.Now()
fmt.Println("Event start:", t0)
fmt.Println("Event completed:", t1)
fmt.Println("=== Not recommended ===")
offsetT0, _ := monoOffset(t0)
fmt.Println("Parsed start offset:", offsetT0)
startNano, _ := calculateStartNano()
fmt.Println("Calculate start offset via startNano: ", t0.Sub(startNano))
fmt.Println("=== Recommended ===")
fmt.Println("Example event duration:", t1.Sub(t0))
fmt.Println("Time since startup", time.Since(t0))
}
// You should never need anything below here (code smell).
func monoOffset(t time.Time) (time.Duration, error) {
// Recommend strings.Cut on Go1.18+.
parts := strings.Split(t.String(), " m=")
if len(parts) != 2 {
return 0, errors.New("missing monotonic offset")
}
seconds, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
return 0, err
}
nanos := math.Round(seconds * 1e9)
return time.Duration(nanos), nil
}
func calculateStartNano() (time.Time, error) {
now := time.Now()
offset, err := monoOffset(now)
if err != nil {
return time.Time{}, err
}
return now.Add(-offset), nil
}
Outputs:
...example event...
Event start: 2022-04-16 16:54:25.088159496 +1000 AEST m=+0.000079273
Event completed: 2022-04-16 16:54:25.089438935 +1000 AEST m=+0.001358685
=== Not recommended ===
Parsed start offset : 79.273µs
Calculate start offset via startNano: 79.273µs
=== Recommended ===
Example event duration: 1.279412ms
Time since startup 2.016789ms

The monotonic clock is just used for differences between times. The absolute value of the monotonic clock is undefined and you should not try to get it. I think what you really want for your timestamp is the duration from a base time.
func init() {
baseTime = time.Now()
}
// NowTimestamp returns really just the duration from the base time
func NowTimestamp() time.Duration {
return time.Now().Sub(baseTime)
}

Related

How to set a timezone to an existing timestamp without reinterpreting it?

I'm parsing timestamps sent by users. The timestamps are local to a location but the source string doesn't specify it. Server-side I'm looking up the timezone of the location and need to shift the time into that timezone, without changing its display value.
I know I can do this to give me the equivalent time at a different location:
package main
import (
"fmt"
"time"
)
func main() {
myTime := time.Now()
fmt.Println(myTime.Format(time.RFC3339))
loc, err := time.LoadLocation("America/New_York")
if err != nil {
panic(err)
}
fmt.Println(myTime.In(loc).Format(time.RFC3339))
}
This just prints:
2009-11-10T23:00:00Z
2009-11-10T18:00:00-05:00
Which isn't what I want.
I'm trying to find a way of setting the timezone to e.g. America/New_York, so I should get e.g. 2009-11-10T23:00:00-05:00, which is the original local time, but with the New York offset applied.
How can I do this in Go?
The confusion comes from the fact that the API that intuitively comes to mind In simply interprets the same point in time as if it were in a different time zone. So when you print it, the display isn't what you want.
To set the time zone to a timestamp while keeping the same display value you can simply construct the new timestamp with time.Date with the same values as the original timestamp and the new location:
t := time.Date(myTime.Year(), myTime.Month(), myTime.Day(), myTime.Hour(), myTime.Minute(), myTime.Second(), myTime.Nanosecond(), loc)
// 2009-11-10T23:00:00-05:00 in the playground
Another option is to set the time instance to the new time zone, then use Zone() to get the offset, and then subtract its value in seconds from the localized time.
package main
import (
"fmt"
"time"
)
func main() {
myTime := time.Now()
fmt.Println(myTime.Format(time.RFC3339))
loc, err := time.LoadLocation("America/New_York")
if err != nil {
panic(err)
}
locTime := myTime.In(loc)
_, zoneOffset := locTime.Zone()
inZoneTime := locTime.Add(-time.Duration(zoneOffset) * time.Second)
// handle DST transitions
if inZoneTime.IsDST() {
inZoneTime = inZoneTime.Add(1*time.Hour)
}
fmt.Println(inZoneTime.Format(time.RFC3339))
// 2009-11-10T23:00:00-05:00
}
To test the DST transition in your local machine today (assuming you are in a non-DST country, as I am) you can change the location to a place where DST is active, e.g. Australia/Canberra.
With an input of time.Now() without DST into Australia/Canberra, the above program prints the following:
2021-11-12T13:27:33+01:00
is DST: true
2021-11-12T13:27:33+11:00
Playground: https://play.golang.org/p/5qy2tOcIMwn

How to write a function to check if current time is inside time window

I am trying to find a way to check if the current time is inside a time window.
The inputs are:
upgradeDay []string - a slice of days (for instance ["Sunday", "Tuesday"])
upgradetime string - hour:minute (for instance "22:04")
upgradeDuration int64 - amount of time, from the upgradetime at which the time window is valid. it can be up to 12 hours.
Full example:
upgradeDay = ["Sunday", Tuesday"] , upgradetime = "10:00", upgradeDuration = 2 -> the time windows is at every Sunday and Tuesday, from 10:00 to 12:00 o'clock.
I tried to write the following function, but it's not working in transition between days/months/years:
func isInsideTimeWindow(upgradeDay []string, upgradeTime string, upgradeDuration int64) bool {
now := time.Now()
ut := strings.Split(upgradeTime, ":")
hour, _ := strconv.Atoi(ut[0])
min, _ := strconv.Atoi(ut[1])
// !! not working when now it's Monday 00:01 and got: upgradeDay = ["Sunday"], upgradeTime = 23:59, upgradeDuration = 2
twStart := time.Date(now.Year(), now.Month(), now.Day(), hour, min, 0, 0, now.Location())
twEnd := twStart.Add(time.Hour * time.Duration(upgradeDuration))
if !(now.After(twStart) && now.Before(twEnd)) {
return false
}
wd := now.Weekday().String()
for i := range upgradeDay {
if upgradeDay[i] == wd {
return true
}
}
return false
}
Does someone got an idea on how to solve that in Go?
Here is one approach to the problem:
package main
import "time"
type window struct { time.Time }
func (w window) isDay(s string) bool {
return w.Weekday().String() == s
}
func (w window) isHourRange(begin, end int) bool {
return w.Hour() >= begin && w.Hour() <= end
}
func main() {
w := window{
time.Now(),
}
{
b := w.isDay("Friday")
println(b)
}
{
b := w.isHourRange(20, 23)
println(b)
}
}
This assume only one day is valid, so you would need to modify this to handle
multiple days. This should get you started though.
There is a lot of complexity in times. For instance:
What if an upgrade day is "Søndag" (Danish) instead of "Sunday"?
Should we work in local time, or UTC? If local, whose location counts? If the server is in London and I am in San Francisco, do we use the server's time, or my time?
If the upgrade interval includes 2 AM, does that count 2 AM PDT and then 2 AM PST as well? These times are one hour apart where I live. If the interval starts at 2 AM and ends at 2:59:59, that time does not exist on one day of the year in many areas with one hour DST shift.
If you get to ignore all these complexities—internationalization (i18n), localization (l10n), DST, and so on—there's still a bit of a problem with the fact that someone can set the date and time, or the upgrade itself might take some time, but usually we get to ignore these too.
Note that Go's time.Now() returns local time—but, whose location? As we have not yet answered the whose time zone to use question yet, we might want to avoid worrying about this. Given the rest of your input constraints, let's write a function to determine if a supplied time meets the input constraints, rather than if time.Now() does so. The caller can then provide either a UTC time or a wall-clock time in the user's location:
someNow = time.Time()
localNow = someNow.In(location) // from time.LoadLocation() or similar
We also have something that seems at odds with your types:
upgradeDuration int64 - amount of time, from the upgradetime at which the time window is valid. it can be up to 12 hours
A value in hours that is between 0 and 12 inclusive fits easily in plain int. Is this already a time.Duration value expressed in nanoseconds? If so, why is it int64 and not time.Duration? Or is it a value in seconds, and therefore can be between 0 and 43200? If so, it still fits in int.
I made a bunch of assumptions and came up with the following, which you can try out on the Go Playground.
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
// startOK determines whether the given starting-time is within a
// time window.
//
// The window starts at a time given as two integers,
// h and m, representing hours and minutes, and extends for
// the given duration d in hours, which in general should not
// extend into another day. If it does extend past the end of
// the day into the next day, we ignore the extension.
//
// The days on which the time *is* in that window are further
// limited by the days[] slice of Weekday values.
//
// Note: it would probably be sensible to return a time.Duration
// value that is how long it will be until the next OK time, but
// we leave that as an exercise.
//
// It would also be sensible to allow the duration d to extend
// into the next day, which is also left as an exercise.
func startOK(when time.Time, days []time.Weekday, h, m, d int) bool {
// Find OK-to-start time, and end-time. If end exceeds
// 24*60, we ignore the extra end time, rather than
// allowing some minutes into the next day.
start := h*60 + m
end := start + d*60
// Convert when to hour-and-minute and see if we are
// in the allowed range.
wh, wm, _ := when.Clock()
now := wh*60 + wm
if now < start || now >= end {
// Not in hh:mm through hh+d:mm; say no.
return false
}
// The time-of-day is OK; check the day-of-week.
// We could do this earlier but by positioning it
// here, we leave room to check to see if it's
// the *next* day, if needed.
if !func(wd time.Weekday) bool {
for _, allowed := range days {
if wd == allowed {
return true
}
}
return false
}(when.Weekday()) {
return false // when.Weekday() not in days[]
}
// time is OK, day is OK
return true
}
// startOKstr is like startOK but the window starts at a time
// given as a string encoded as hh:mm, with the days being a
// slice of strings instead of Weekday. Because of these strings,
// parsing can produce an error, so this function has an error
// return.
func startOKStr(when time.Time, days []string, hhmm string, d int) (bool, error) {
parts := strings.Split(hhmm, ":")
// optional: be strict about two-digit values
if len(parts) != 2 {
return false, fmt.Errorf("invalid time string %q", hhmm)
}
h, err := strconv.Atoi(parts[0])
if err != nil {
return false, err
}
if h < 0 || h >= 60 {
return false, fmt.Errorf("invalid hour value %s", parts[0])
}
m, err := strconv.Atoi(parts[1])
if err != nil {
return false, err
}
if m < 0 || m >= 60 {
return false, fmt.Errorf("invalid minute value %s", parts[1])
}
var wd []time.Weekday
for _, s := range days {
w, err := parseWeekday(s)
if err != nil {
return false, err
}
wd = append(wd, w)
}
ok := startOK(when, wd, h, m, d)
return ok, nil
}
// parseWeekday handles weekday strings.
//
// Ideally we'd use time.Parse for this, as it already has
// these in it, but they are not exported in usable form.
func parseWeekday(s string) (time.Weekday, error) {
strToWeekday := map[string]time.Weekday{
"Sunday": time.Sunday,
"Monday": time.Monday,
"Tuesday": time.Tuesday,
"Wednesday": time.Wednesday,
"Thursday": time.Thursday,
"Friday": time.Friday,
"Saturday": time.Saturday,
}
if v, ok := strToWeekday[s]; ok {
return v, nil
}
return time.Sunday, fmt.Errorf("invalid day-of-week %q", s)
}
// tests should be converted to real tests and put in
// a separate file.
func tests() {
okDays := []string{"Sunday", "Wednesday"}
okStart := "04:00"
okDuration := 2 // hours
tfmt := "Mon Jan 2 15:04:05 2006"
t1 := "Sat Sep 5 04:30:00 2020" // time OK, day not
t2 := "Sun Sep 6 04:30:00 2020" // time OK, day OK
check := func(s string, expect bool) {
when, err := time.Parse(tfmt, s)
if err != nil {
panic(err)
}
result, err := startOKStr(when, okDays, okStart, okDuration)
if err != nil {
panic(err)
}
if result != expect {
fmt.Printf("fail: expected %v for %q\n", expect, s)
}
}
check(t1, false)
check(t2, true)
fmt.Println("2 tests run")
}
func main() {
tests()
}

How to represent the RFC-3339 `-00:00` offset in Go?

RFC-3339 Section 4.3 (https://www.rfc-editor.org/rfc/rfc3339#section-4.3) defines the -00:00 offset as the following, which is different than Z or +00:00.
4.3. Unknown Local Offset Convention
If the time in UTC is known, but the offset to local time is unknown,
this can be represented with an offset of "-00:00". This differs
semantically from an offset of "Z" or "+00:00", which imply that UTC
is the preferred reference point for the specified time. RFC2822
[IMAIL-UPDATE] describes a similar convention for email.
However, I'm not sure how to represent this in Go. When I parse a time with -00:00 and format it, I get a Z offset. For example:
Input: 2018-01-01T00:00:00-00:00
Output: 2018-01-01T00:00:00Z
Here's some example code (https://play.golang.org/p/CVmNnhaSiiT):
package main
import (
"fmt"
"time"
)
func main() {
t := "2018-01-01T00:00:00-00:00"
fmt.Println("Input " + t)
p, err := time.Parse(time.RFC3339, t)
if err != nil {
fmt.Println(err)
} else {
t2 := p.Format(time.RFC3339)
fmt.Println("Output " + t2)
}
}
Package time
import "time"
RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for
formatting; when used with time.Parse they do not accept all the time
formats permitted by the RFCs.
Go does not accept all the time formats permitted by the RFCs.
The Go time.Time type uses integers which, unlike floating-point, have no concept of plus and minus zero. The results for parsing offsets of -00:00 and +00:00 are identical.
For example,
package main
import (
"fmt"
"time"
)
func main() {
var err error
var minus, plus time.Time
t := "2018-01-01T00:00:00-00:00"
minus, err = time.Parse(time.RFC3339, t)
if err != nil {
fmt.Println(err)
}
t = "2018-01-01T00:00:00+00:00"
plus, err = time.Parse(time.RFC3339, t)
if err != nil {
fmt.Println(err)
}
fmt.Println(minus, plus, minus.Equal(plus), minus == plus)
}
Playground: https://play.golang.org/p/Urf8VlKYoMH
Output:
2018-01-01 00:00:00 +0000 UTC 2018-01-01 00:00:00 +0000 UTC true true
PeterSO's answer is perfect IMHO. If you need to act differently based on the information that the offset is unknown, then this might help you.
You can build your own time data type:
type MyTime struct {
// based on time.Time so we can do all normal time.Time stuff
time.Time
offsetUnknown bool
}
func ParseRFC3339(s string) (MyTime, error) {
time, err := time.Parse(time.RFC3339, s)
if err != nil {
return MyTime{}, err
}
return MyTime{
Time: time,
// maybe this condition needs improvement in case of false positives
offsetUnknown: strings.Contains(s, "-00:00"),
}, nil
}
Any functions you need to behave differently based on offsetUnknown you can then override on the MyTime struct. Here one example:
func (s MyTime) Format(layout string) string {
out := s.Time.Format(layout)
// again this is probably not the best solution
if layout == time.RFC3339 && s.offsetUnknown {
out = strings.Replace(out, "+00:00", "-00:00", -1)
}
return out
}

How to parse unix timestamp to time.Time

I'm trying to parse an Unix timestamp but I get out of range error. That doesn't really makes sense to me, because the layout is correct (as in the Go docs):
package main
import "fmt"
import "time"
func main() {
tm, err := time.Parse("1136239445", "1405544146")
if err != nil{
panic(err)
}
fmt.Println(tm)
}
Playground
The time.Parse function does not do Unix timestamps. Instead you can use strconv.ParseInt to parse the string to int64 and create the timestamp with time.Unix:
package main
import (
"fmt"
"time"
"strconv"
)
func main() {
i, err := strconv.ParseInt("1405544146", 10, 64)
if err != nil {
panic(err)
}
tm := time.Unix(i, 0)
fmt.Println(tm)
}
Output:
2014-07-16 20:55:46 +0000 UTC
Playground: http://play.golang.org/p/v_j6UIro7a
Edit:
Changed from strconv.Atoi to strconv.ParseInt to avoid int overflows on 32 bit systems.
You can directly use time.Unix function of time which converts the unix time stamp to UTC
package main
import (
"fmt"
"time"
)
func main() {
unixTimeUTC:=time.Unix(1405544146, 0) //gives unix time stamp in utc
unitTimeInRFC3339 :=unixTimeUTC.Format(time.RFC3339) // converts utc time to RFC3339 format
fmt.Println("unix time stamp in UTC :--->",unixTimeUTC)
fmt.Println("unix time stamp in unitTimeInRFC3339 format :->",unitTimeInRFC3339)
}
Output
unix time stamp in UTC :---> 2014-07-16 20:55:46 +0000 UTC
unix time stamp in unitTimeInRFC3339 format :----> 2014-07-16T20:55:46Z
Check in Go Playground: https://play.golang.org/p/5FtRdnkxAd
Sharing a few functions which I created for dates:
Please note that I wanted to get time for a particular location (not just UTC time). If you want UTC time, just remove loc variable and .In(loc) function call.
func GetTimeStamp() string {
loc, _ := time.LoadLocation("America/Los_Angeles")
t := time.Now().In(loc)
return t.Format("20060102150405")
}
func GetTodaysDate() string {
loc, _ := time.LoadLocation("America/Los_Angeles")
current_time := time.Now().In(loc)
return current_time.Format("2006-01-02")
}
func GetTodaysDateTime() string {
loc, _ := time.LoadLocation("America/Los_Angeles")
current_time := time.Now().In(loc)
return current_time.Format("2006-01-02 15:04:05")
}
func GetTodaysDateTimeFormatted() string {
loc, _ := time.LoadLocation("America/Los_Angeles")
current_time := time.Now().In(loc)
return current_time.Format("Jan 2, 2006 at 3:04 PM")
}
func GetTimeStampFromDate(dtformat string) string {
form := "Jan 2, 2006 at 3:04 PM"
t2, _ := time.Parse(form, dtformat)
return t2.Format("20060102150405")
}
I do a lot of logging where the timestamps are float64 and use this function to get the timestamps as string:
func dateFormat(layout string, d float64) string{
intTime := int64(d)
t := time.Unix(intTime, 0)
if layout == "" {
layout = "2006-01-02 15:04:05"
}
return t.Format(layout)
}
for millis unix timestamp precision, in go1.18
i, err := strconv.ParseInt("1652084489543", 10, 64)
if err != nil {
panic(err)
}
tm := time.UnixMilli(i)
fmt.Println(tm)
According to the go documentation, Unix returns a local time.
Unix returns the local Time corresponding to the given Unix time
This means the output would depend on the machine your code runs on, which, most often is what you need, but sometimes, you may want to have the value in UTC.
To do so, I adapted the snippet to make it return a time in UTC:
i, err := strconv.ParseInt("1405544146", 10, 64)
if err != nil {
panic(err)
}
tm := time.Unix(i, 0)
fmt.Println(tm.UTC())
This prints on my machine (in CEST)
2014-07-16 20:55:46 +0000 UTC
This is an old question but I noticed that a practical answer is missing.
For example, we are working with the MavLink protocol and we need to process a message with a structure defined here.
If we have this data structure:
Field Name
Type
Units
Description
time_boot_ms
uint64_t
ms
Timestamp (time since system boot).
press_abs
float
hPa
Absolute pressure
press_diff
float
hPa
Differential pressure 1
temperature
int16_t
cdegC
Absolute pressure temperature
temperature_press_diff **
int16_t
cdegC
Differential pressure temperature (0, if not available). Report values of 0 (or 1) as 1 cdegC.
So, we receive constant updates that we need to process using the time_boot_ms as reference to insert them on the database and synchronize them with other messages.
What can we do?
As we noticed, the time is in milliseconds and everyone, that has some experience with Go, knows that for some unknown reason it's just way too complex to convert a millisecond resolution Unix timestamp to time.Time. The built-in time.Unix() function only supports second and nanosecond precision.
How we can get millisecond precision?
Well, we might wait until they release the version 1.7 of Go or we either have to multiply the milliseconds to nanoseconds or split them into seconds and nanoseconds.
Lets implement the second idea, spit the into seconds and nanoseconds:
unixUTCtime := time.Unix(ms/int64(1000), (ms%int64(1000))*int64(1000000))
Now we can encapsulate it in a func and use it in our main like this:
package main
import (
"fmt"
"time"
)
const msInSecond int64 = 1e3
const nsInMillisecond int64 = 1e6
// UnixToMS Converts Unix Epoch from milliseconds to time.Time
func UnixToMS (ms int64) time.Time {
return time.Unix(ms/msInSecond, (ms%msInSecond)*nsInMillisecond)
}
func main() {
unixTimes := [...]int64{758991688, 758992188, 758992690, 758993186}
var unixUTCTimes []time.Time
for index, unixTime := range unixTimes {
unixUTCTimes = append(unixUTCTimes, UnixToMS(unixTime))
if index > 0 {
timeDifference := unixUTCTimes[index].Sub(unixUTCTimes[index-1])
fmt.Println("Time difference in ms :--->", timeDifference)
}
}
}
The output will be:
Time difference in ms :---> 500ms
Time difference in ms :---> 502ms
Time difference in ms :---> 496ms
Check in Go Playground

How to get last-accessed date and time of file in Go?

Does anyone know how to check for a file access date and time? The function returns the modified date and time and I need something that compares the accessed date time to the current date and time.
You can use os.Stat to get a FileInfo struct which also contains the last access time (as well as the last modified and the last status change time).
info, err := os.Stat("example.txt")
if err != nil {
// TODO: handle errors (e.g. file not found)
}
// info.Atime_ns now contains the last access time
// (in nanoseconds since the unix epoch)
After that, you can use time.Nanoseconds to get the current time (also in nanoseconds since the unix epoch, January 1, 1970 00:00:00 UTC). To get the duration in nanoseconds, just subtract those two values:
duration := time.Nanoseconds() - info.Atime_ns
By casting os.FileInfo to *syscall.Stat_t:
package main
import ( "fmt"; "log"; "os"; "syscall"; "time" )
func main() {
for _, arg := range os.Args[1:] {
fileinfo, err := os.Stat(arg)
if err != nil {
log.Fatal(err)
}
atime := fileinfo.Sys().(*syscall.Stat_t).Atim
fmt.Println(time.Unix(atime.Sec, atime.Nsec))
}
}
Alternatively, after the Stat you can also do
statinfo.ModTime()
Also you can use Format() on it, should you need it eg for a webserver
see https://gist.github.com/alexisrobert/982674
For windows
syscall.Win32FileAttributeData
info, _ := os.Stat("test.txt")
fileTime := info.Sys().(*syscall.Win32FileAttributeData).LastAccessTime
aTime := time.Unix(0, fileTime.Nanoseconds())
Example
package main
import (
"fmt"
"log"
"os"
"syscall"
"time"
)
func main() {
info, _ := os.Stat("./test.txt")
fileTime := info.Sys().(*syscall.Win32FileAttributeData).LastAccessTime
// _ = info.Sys().(*syscall.Win32FileAttributeData).CreationTime
// _ = info.Sys().(*syscall.Win32FileAttributeData).LastWriteTime
fileAccessTime := time.Unix(0, fileTime.Nanoseconds())
// Compare
// t2, _ := time.Parse("2006/01/02 15:04:05 -07:00:00", "2023/02/08 13:18:00 +08:00:00")
now := time.Now()
log.Println(fileAccessTime)
log.Println(now.Add(-20 * time.Minute))
if fileAccessTime.After(now.Add(-20 * time.Minute)) {
fmt.Println("You accessed this file 20 minutes ago.")
}
}
Linux
see this answer

Resources