How to construct time.Time with timezone offset [duplicate] - go

This question already has answers here:
How do you convert a time offset to a location/timezone in Go
(2 answers)
Closed 3 years ago.
This is an example date from an Apache log:
[07/Mar/2004:16:47:46 -0800]
I have successfully parsed this into year(int), month(time.Month), day(int), hour(int), minute(int), second(int), and timezone(string).
How can I construct time.Time such that it includes the -0800 time zone offset?
This is what I have so far:
var nativeDate time.Time
nativeDate = time.Date(year, time.Month(month), day, hour, minute, second, 0, ????)
What should I use in place of ????? time.Local or time.UTC is not appropriate here.

You may use time.FixedZone() to construct a time.Location with a fixed offset.
Example:
loc := time.FixedZone("myzone", -8*3600)
nativeDate := time.Date(2019, 2, 6, 0, 0, 0, 0, loc)
fmt.Println(nativeDate)
Output (try it on the Go Playground):
2019-02-06 00:00:00 -0800 myzone
If you have the zone offset as a string, you may use time.Parse() to parse it. Use a layout string that only contains the reference zone offset:
t, err := time.Parse("-0700", "-0800")
fmt.Println(t, err)
This outputs (try it on the Go Playground):
0000-01-01 00:00:00 -0800 -0800 <nil>
As you can see, the result time.Time has a zone offset of -0800 hours.
So our original example can also be written as:
t, err := time.Parse("-0700", "-0800")
if err != nil {
panic(err)
}
nativeDate := time.Date(2019, 2, 6, 0, 0, 0, 0, t.Location())
fmt.Println(nativeDate)
Output (try it on the Go Playground):
2019-02-06 00:00:00 -0800 -0800

Related

How do you convert a time offset to a location/timezone in Go

Given an arbitrary time offset, how does one go about creating a usable time.Location object that represents that time offset?
The following code parses a time using an offset, but fmt.Println(t.Location()) subsequently returns no information:
func main() {
offset := "+1100"
t, err := time.Parse("15:04 GMT-0700","15:06 GMT"+offset)
if err != nil {
fmt.Println("fail", err)
}
fmt.Println(t)
fmt.Println(t.UTC())
fmt.Println(t.Location())
}
Playground: https://play.golang.org/p/j_E28qJ8Vgy
Basically I have some time data with time offsets, but without location data, I want to create a time.Location object to ensure the GMT offset is recorded. And then be able to output the time relative to the end users actual location time offset.
Use:
loc := time.FixedZone("UTC+11", +11*60*60)
Then set to this location:
t = t.In(loc)
Try this:
package main
import (
"fmt"
"time"
)
func main() {
loc := time.FixedZone("UTC+11", +11*60*60)
t := time.Now()
fmt.Println(t)
fmt.Println(t.Location())
t = t.In(loc)
fmt.Println(t)
fmt.Println(t.Location())
fmt.Println(t.UTC())
fmt.Println(t.Location())
}
Output:
2009-11-10 23:00:00 +0000 UTC m=+0.000000001
UTC
2009-11-11 10:00:00 +1100 UTC+11
UTC+11
2009-11-10 23:00:00 +0000 UTC
UTC+11
if len(offset) == 5 {
hours, ok1 := strconv.ParseInt(offset[:3], 10, 0)
mins, ok2 := strconv.ParseInt(offset[3:5], 10, 0)
if ok1 == nil && ok2 == nil {
t = t.In(time.FixedZone("Fixed", int((hours*60+mins)*60)))
fmt.Println(t)
fmt.Println(t.Location())
}
}

Local time loses 52 seconds after marshal/unmarshal

I parse a time in Local, marshal it to JSON, un-marshal it and the times no longer match.
timeA, _ := time.ParseInLocation("15:04", "8:00", time.Local)
jBytes, _ := json.Marshal(timeA)
var timeB time.Time
json.Unmarshal(jBytes, &timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA, timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.Local(), timeB.Local())
fmt.Printf("Diff: %s\n", timeA.Sub(timeB))
fmt.Printf("Marshaled: %s", string(jBytes))
Time A: 0000-01-01 08:00:00 -0733 LMT, Time B: 0000-01-01 08:00:00 -0733 -0733
Time A: 0000-01-01 08:00:00 -0733 LMT, Time B: 0000-01-01 07:59:08 -0733 LMT
Diff: 52s
Marshaled: "0000-01-01T08:00:00-07:33"
This is running on linux with Edmonton/Mountain as my local time so I guess it's not recognizing the location and showing offset twice -733 -733. When I call local, the parsed one consistently loses 52 seconds for some reason.
I'd expect the times to match. Is my clock 52 seconds off a remote one it's referencing or something?
Prior to September 1, 1906, your time zone difference was UTC-7:33:52. json.Unmarshal is just using the 7:33 in the marshaled text for the offset, instead of the correct value of 7:33:52, so the time.Time value it calculates is off by 52 seconds. But your time.Local implementation seems to be getting it right (to the extent we can describe backdating time zone differences to year 1 as "right") and subtracting the full 7:33:52 from the time.Time value, resulting in the difference you're seeing.
If you output:
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.UTC(), timeB.UTC())
with your current code you should see that the UTC time for timeB is getting set to 15:33:00 after unmarshaling, whereas the UTC time for timeA is getting set to 15:33:52. I suspect if you include a year after 1906 in your time string you'll see this 52 seconds difference disappear.
For example:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
func main() {
zone, err := time.LoadLocation("America/Edmonton")
if err != nil {
log.Fatalf("%v", err)
}
for _, timestring := range []string{
"01 02 1905 8:00",
"01 02 1907 8:00",
} {
timeA, err := time.ParseInLocation("01 02 2006 15:04", timestring, zone)
if err != nil {
log.Fatalf("%v", err)
}
jBytes, _ := json.Marshal(timeA)
var timeB time.Time
json.Unmarshal(jBytes, &timeB)
fmt.Printf("Time string: %s\n", timestring)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA, timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.UTC(), timeB.UTC())
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA.In(zone), timeB.In(zone))
fmt.Printf("Diff: %s\n", timeA.Sub(timeB))
fmt.Printf("Marshaled: %s\n", string(jBytes))
}
}
outputs:
paul#mac:got$ ./got
Time string: 01 02 1905 8:00
Time A: 1905-01-02 08:00:00 -0733 LMT, Time B: 1905-01-02 08:00:00 -0733 -0733
Time A: 1905-01-02 15:33:52 +0000 UTC, Time B: 1905-01-02 15:33:00 +0000 UTC
Time A: 1905-01-02 08:00:00 -0733 LMT, Time B: 1905-01-02 07:59:08 -0733 LMT
Diff: 52s
Marshaled: "1905-01-02T08:00:00-07:33"
Time string: 01 02 1907 8:00
Time A: 1907-01-02 08:00:00 -0700 MST, Time B: 1907-01-02 08:00:00 -0700 -0700
Time A: 1907-01-02 15:00:00 +0000 UTC, Time B: 1907-01-02 15:00:00 +0000 UTC
Time A: 1907-01-02 08:00:00 -0700 MST, Time B: 1907-01-02 08:00:00 -0700 MST
Diff: 0s
Marshaled: "1907-01-02T08:00:00-07:00"
paul#mac:got$
showing that the 52 second difference is there for 1905, but not for 1907 after the time zone difference changed to a straight UTC-7:00:00.
Short answer: marshaling to and unmarshaling from json by default appears unable to correctly handle seconds in time zone offsets, because no seconds appear in the offset in the marshaled string and this is the only time zone information json.Unmarshal has available to it.
For sure there is no referencing of clocks, remote or otherwise, in any of this code - it's just manipulating values.
You are defaulting to pseudo-date 0000-01-01 when local time was likely based on the position of the Sun at midday.
Simply parse the time-of-day. For example,
package main
import (
"encoding/json"
"fmt"
"time"
)
func main() {
timeA, err := time.Parse("15:04", "8:00")
fmt.Println(timeA, err)
jBytes, _ := json.Marshal(timeA)
var timeB time.Time
json.Unmarshal(jBytes, &timeB)
fmt.Printf("Time A: %+v, Time B: %+v\n", timeA, timeB)
fmt.Printf("Diff: %s\n", timeA.Sub(timeB))
fmt.Printf("Marshaled: %s\n", string(jBytes))
}
Output:
0000-01-01 08:00:00 +0000 UTC <nil>
Time A: 0000-01-01 08:00:00 +0000 UTC, Time B: 0000-01-01 08:00:00 +0000 UTC
Diff: 0s
Marshaled: "0000-01-01T08:00:00Z"

How to update hour, min, sec in golang time?

Ex: How can I update hour in t time?
fmt.Println(t)
//=> 2006-01-02 15:04:05 +0000 UTC
Expect to get: 2006-01-02 00:00:00 +0000 UTC
Edited: similar to: time.Time Round to Day
Use:
t1 := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, t.Nanosecond(), t.Location())
Ref: https://golang.org/pkg/time/#Date
This seems to do it:
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now().UTC()
t = t.Truncate(24 * time.Hour)
fmt.Println(t)
}
https://golang.org/pkg/time#Time.Truncate

Time conversion issue in go language

I'm trying to understand the issue with time conversion in Go language. Here is code example:
package main
import (
"fmt"
"time"
)
func unix2Str(ts int64) string {
const layout = "20060102"
t := time.Unix(ts, 0)
return t.Format(layout)
}
func unixTime(ts string) int64 {
const layout = "20060102"
t, err := time.Parse(layout, ts)
if err != nil {
fmt.Println(err)
return 0
}
return t.Unix()
}
func main() {
ts1 := "20110320"
ts2 := "20110321"
ut1 := unixTime(ts1)
ut2 := unixTime(ts2)
fmt.Println(ts1, ut1, unix2Str(ut1))
fmt.Println(ts2, ut2, unix2Str(ut2))
}
It prints the following output:
20110320 1300579200 20110319
20110321 1300665600 20110320
But since I do the conversion from string format to Unix and reverse I would expect the same results for the date in string format. But it is not the case. In fact, the printed unix time 1300579200 is converted in python to original date I started with, e.g.
>>> time.strftime("%Y%m%d", time.gmtime(1300579200))
'20110320'
Is it a bug in Go code or am I missing something?
It is because of the difference between your local time zone and UTC. Parse returned UTC time and Unix returned local time. For example,
package main
import (
"fmt"
"time"
)
func unix2Str(ts int64) string {
const layout = "20060102"
t := time.Unix(ts, 0)
fmt.Println(t)
return t.Format(layout)
}
func unixTime(ts string) int64 {
const layout = "20060102"
t, err := time.Parse(layout, ts)
if err != nil {
fmt.Println(err)
return 0
}
fmt.Println(t)
return t.Unix()
}
func main() {
ts1 := "20110320"
ts2 := "20110321"
ut1 := unixTime(ts1)
ut2 := unixTime(ts2)
fmt.Println(ts1, ut1, unix2Str(ut1))
fmt.Println(ts2, ut2, unix2Str(ut2))
}
Output:
2011-03-20 00:00:00 +0000 UTC
2011-03-21 00:00:00 +0000 UTC
2011-03-19 20:00:00 -0400 EDT
20110320 1300579200 20110319
2011-03-20 20:00:00 -0400 EDT
20110321 1300665600 20110320
func Parse
func Parse(layout, value string) (Time, error)
Parse parses a formatted string and returns the time value it
represents. The layout defines the format by showing how the reference
time, defined to be
Mon Jan 2 15:04:05 -0700 MST 2006
would be interpreted if it were the value; it serves as an example of
the input format. The same interpretation will then be made to the
input string.
In the absence of a time zone indicator, Parse returns a time in UTC.
func Unix
func Unix(sec int64, nsec int64) Time
Unix returns the local Time corresponding to the given Unix time, sec
seconds and nsec nanoseconds since January 1, 1970 UTC.

How do you convert "Month dd, yyyy" to yyyy-mm-dd in Go?

I need to convert for example "April 20, 1996" to 1996-04-20. I have tried the following code but I have a feeling that I am doing it in reverse somehow.
func main() {
value := "April 20, 1996"
layout := "January 1, 1996"
t, _ := time.Parse(layout, value)
fmt.Println(t)
mydate, _ := time.Parse("2006-01-02", "2016-07-08")
fmt.Println("time:", mydate.Format("April 20, 1996 (MST)"))
}
You just need to parse the time using the input layout and then print it using the output layout. The layout always specifies how the reference time (Mon Jan 2 15:04:05 MST 2006) would look in the given format. I think this is what you want:
func main() {
value := "April 20, 1996"
layout := "January 2, 2006"
t, _ := time.Parse(layout, value)
fmt.Println(t)
fmt.Println("time:", t.Format("2006-01-02"))
}
See https://golang.org/pkg/time/ for more information.

Resources