40

How can I convert UTC time to local time?

I've created a map with the UTC difference for all the countries I need the local time. Then I add that difference as duration to the current time (UTC) and print the result hoping that's the local time of that specific country.

For some reasons the result is wrong. For example with Hungary there is one hour difference. Any idea why I'm getting incorrect results?

package main

import "fmt"
import "time"

func main() {

    m := make(map[string]string)
    m["Hungary"] = "+01.00h"

    offSet, err := time.ParseDuration(m["Hungary"])
    if err != nil {
        panic(err)
    }
    t := time.Now().UTC().Add(offSet)
    nice := t.Format("15:04")

    fmt.Println(nice)
}
informatik01
  • 16,038
  • 10
  • 74
  • 104
hey
  • 7,299
  • 14
  • 39
  • 57
  • What exactly isn't working? Your code outputs the clock time one hour after UTC. – JimB Aug 14 '14 at 22:18
  • I expected to get the local time in Hungary which is UTC+1 but there is an hour difference between the time reported by tz [1] which I think is correct and the time generated by go [1] http://www.timeanddate.com/worldclock/hungary/budapest – hey Aug 14 '14 at 22:19
  • The playground's time starts at `2009-11-10 23:00:00 +0000 UTC` – OneOfOne Aug 14 '14 at 22:22
  • 1
    @OneOfOne I'm actually testing it on my machine, thus no playground link. – hey Aug 14 '14 at 22:24
  • There's already a database of the timezones on your system. Use `time.Location` – JimB Aug 14 '14 at 22:24
  • @JimB I need to provide one or more local time(s) by country. I'm not sure I can do that using time.Location as it provides the offset / time zone by regions ( e.g. Europe/London, not United Kingdom or Scotland etc ) ? – hey Aug 14 '14 at 22:26
  • 1
    @hey, ok I can see that, but you probably want to map those to time.Locations, not custom durations. – JimB Aug 14 '14 at 22:27

4 Answers4

57

Keep in mind that the playground has the time set to 2009-11-10 23:00:00 +0000 UTC, so it is working.

The proper way is to use time.LoadLocation though, here's an example:

var countryTz = map[string]string{
    "Hungary": "Europe/Budapest",
    "Egypt":   "Africa/Cairo",
}

func timeIn(name string) time.Time {
    loc, err := time.LoadLocation(countryTz[name])
    if err != nil {
        panic(err)
    }
    return time.Now().In(loc)
}

func main() {
    utc := time.Now().UTC().Format("15:04")
    hun := timeIn("Hungary").Format("15:04")
    eg := timeIn("Egypt").Format("15:04")
    fmt.Println(utc, hun, eg)
}
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • it still displays a wrong time compared with http://www.timeanddate.com/worldclock/hungary/budapest . On my machine it says it's 23:21 but it actually seems to be 00:24. I get the same difference with my code thus the question. – hey Aug 14 '14 at 22:22
  • or you could `time.LoadLocation("Europe/Budapest")` – JimB Aug 14 '14 at 22:23
  • http://play.golang.org/p/n_D9fA6WBQ works correct on my system and prints the right time, maybe your computer clock is messed up? are you in Egypt by any chance? – OneOfOne Aug 14 '14 at 22:26
  • I already made a map with country -> UTC(s) as I need to provide all the local time(s) in a specific country. I guess that using time.Loadlocation would require yet another map ? Isn't possible to get the local time given the UTC offset ? – hey Aug 14 '14 at 22:28
  • You would implement it the same way `m:= map[string]string{"Hungary": "Europe/Budapest"}` then pass that to LoadLocation, i'll update the code. – OneOfOne Aug 14 '14 at 22:29
  • yeah I've got the point ... just that UTC seemed more "universal" and I already had the map country[UTC]. It may be time consuming to look for the "Location" of all the countries. Concerning my location I've tested the code on both my machine and on a remote server and I get the same result ( 1 hour difference). Both of them are set on BST which I think is UTC+1 – hey Aug 14 '14 at 22:32
  • 2
    @hey while that seems more universal, but keeping track of DST by yourself (specially since some governments can change it few times a year, like here in Egypt) can get messy real quick. – OneOfOne Aug 14 '14 at 22:38
  • ahh... I didn't know that DST is that messed up. – hey Aug 15 '14 at 07:39
  • 1
    Thank you for showing the use of the time.Time.In method. It, and it's purpose, totally escaped me when I thought I knew how to use the time package. As usual, when I think I've run into a bug in a golang package, I've really discovered an assumption I was making. – WeakPointer May 23 '18 at 14:21
  • List of countries: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones – ninhjs.dev Feb 21 '21 at 12:14
20

Your approach is flawed. A country can have several time zones, for example, US and Russia. Because of daylight saving time (DST), a time zone can have more than one time, for example, Hungary. Hungary is UTC +1:00 and is also UTC+2:00 for DST.

For each location that you want the local time for a given UTC time, use the IANA (tzdata) time zone location. For example,

package main

import (
    "fmt"
    "time"
)

func main() {
    utc := time.Now().UTC()
    fmt.Println(utc)
    local := utc
    location, err := time.LoadLocation("Europe/Budapest")
    if err == nil {
        local = local.In(location)
    }
    fmt.Println("UTC", utc.Format("15:04"), local.Location(), local.Format("15:04"))
    local = utc
    location, err = time.LoadLocation("America/Los_Angeles")
    if err == nil {
        local = local.In(location)
    }
    fmt.Println("UTC", utc.Format("15:04"), local.Location(), local.Format("15:04"))
}

Output:

2014-08-14 23:57:09.151377514 +0000 UTC
UTC 23:57 Europe/Budapest 01:57
UTC 23:57 America/Los_Angeles 16:57

References:

IANA Time Zone Database

tz database

tz database time zones

Time zone

Time in Hungary

peterSO
  • 158,998
  • 31
  • 281
  • 276
3

Save yourself the hassle of messing with specific zones, use location "Local". Here's a full and practical example of local and UTC conversion:

package main

import (
    "fmt"
    "log"
    "time"
)

const (
    dateTimeFormat = "2006-01-02 15:04 MST"
    dateFormat    = "2006-01-02"
    timeFormat    = "15:04"
)

// A full cycle example of receiving local date and time,
// handing off to a database, retrieving as UTC, and formatting as local datetime
// This should be good in *any* timezone
func main() {
    // If using a form for entry, I strongly suggest a controlled format input like
    // <input type="date" ... > and <input type="time" ... >
    locallyEnteredDate := "2017-07-16"
    locallyEnteredTime := "14:00"

    // Build a time object from received fields (time objects include zone info)
    // We are assuming the code is running on a server that is in the same zone as the current user
    zone, _ := time.Now().Zone() // get the local zone
    dateTimeZ := locallyEnteredDate + " " + locallyEnteredTime + " " + zone
    dte, err := time.Parse(dateTimeFormat, dateTimeZ)
    if err != nil {
        log.Fatal("Error parsing entered datetime", err)
    }
    fmt.Println("dte:", dte) // dte is a legit time object
    // Perhaps we are saving this in a database.
    // A good database driver should save the time object as UTC in a time with zone field,
    // and return a time object with UTC as zone.

    // For the sake of this example, let's assume an object identical to `dte` is returned
    // dte := ReceiveFromDatabase()

    // Convert received date to local.
    // Note the use of the convenient "Local" location https://golang.org/pkg/time/#LoadLocation.
    localLoc, err := time.LoadLocation("Local")
    if err != nil {
        log.Fatal(`Failed to load location "Local"`)
    }
    localDateTime := dte.In(localLoc)

    fmt.Println("Date:", localDateTime.Format(dateFormat))
    fmt.Println("Time:", localDateTime.Format(timeFormat))
}
Rohanthewiz
  • 947
  • 9
  • 9
  • 1
    Yes, I gave extra info in this answer, but it will help noobs see the full picture of dealing with date, time, and zones. – Rohanthewiz Jul 17 '17 at 07:08
  • 1
    Dealing with times is very complicated. Being explicit about timezones when you need to deal with them is good practice. The OP mentions multiple countries, there is a fair chance they need to consider different time zones. Your hostility towards people still learning isn't appreciated. – Dynom Nov 21 '19 at 11:28
0

NB: It's better to use your editor instead of the go playground.

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    location, _ := time.LoadLocation("UTC")
    fmt.Printf("UTC time is ------ %s\n", now.In(location))

    location, _ = time.LoadLocation("Europe/Berlin")
    fmt.Printf("Berlin time is ------ %s\n", now.In(location))

    location, _ = time.LoadLocation("Africa/Casablanca")
    fmt.Printf("Casablanca time is ------ %s\n", now.In(location))

    location, _ = time.LoadLocation("Asia/Dubai")
    fmt.Printf("Dubai time is ------ %s\n", now.In(location))
}
Mrkouhadi
  • 446
  • 5
  • 14