今天我遇到個問題。我在編寫代碼處理 NOAA 的潮汐站 XML 文檔時,很快意識到我遇到了麻煩。這是一小段 XML 文檔:```xml<timezone>LST/LDT</timezone><item><date>2013/01/01</date><day>Tue</day><time>02:06 AM</time><predictions_in_ft>19.7</predictions_in_ft><predictions_in_cm>600</predictions_in_cm><highlow>H</highlow></item>```如果您注意到 timezone 標籤,它代表當地標準時間/當地日時。這是一個真實存在的問題因為您需要用 UTC 格式儲存這些資料。如果沒有正確的時區我就會迷失。我的生意夥伴抓了我的頭後,給我看了倆個使用經緯度位置並返回時區資訊的 API。很幸運,每個潮汐站我都有經緯度位置資訊。如果您開啟這個網頁您就能讀到這個 Google's Timezone API 文檔:https://developers.google.com/maps/documentation/timezone/這個 API 相當簡單。它需要一個位置,時間戳記和一個標誌來識別請求的應用是否正在使用感應器(如 GPS 裝置)來確定位置。這是一個簡單的 Google API 呼叫和響應:```https://maps.googleapis.com/maps/api/timezone/json?location=38.85682,-92.991714&sensor=false×tamp=1331766000{ "dstOffset" : 3600.0, "rawOffset" : -21600.0, "status" : "OK", "timeZoneId" : "America/Chicago", "timeZoneName" : "Central Daylight Time"}```它限制一天只能訪問2500次。對於我的潮汐站初始載入,我知道我將達到這個限制,而且我不想等幾天再載入所有資料。所有我的商業夥伴從 GeoNames 發現了這個 timezone API。如果您開啟這個網頁您就能讀到這個 GeoNames's API 文檔:http://www.geonames.org/export/web-services.html#timezone這個 API 需要一個免費帳號,它相當快就可以設定好。一旦您啟用您的帳號,為了使用這個 API您需要找到帳號頁去啟用您的使用者名稱。這是一個簡單的 GeoNames API 呼叫和響應:```http://api.geonames.org/timezoneJSON?lat=47.01&lng=10.2&username=demo{ "time":"2013-08-09 00:54", "countryName":"Austria", "sunset":"2013-08-09 20:40", "rawOffset":1, "dstOffset":2, "countryCode":"AT", "gmtOffset":1, "lng":10.2, "sunrise":"2013-08-09 06:07", "timezoneId":"Europe/Vienna", "lat":47.01}```這個 API 返回的資訊多一些。而且沒有訪問限制但是回應時間不能保證。目前我訪問了幾千次沒有遇到問題。至此我們有兩個不同的 web 請求能幫我們獲得 timezone 資訊。讓我們看看怎麼使用 Go 去使用 Google web 請求並獲得一個返回對象用在我們的程式中。首先,我們需要定義一個新的類型來包含從 API 返回的資訊。```gopackage mainimport ( "encoding/json" "fmt" "io/ioutil" "net/http" "time")const googleURI = "https://maps.googleapis.com/maps/api/timezone/json?location=%f,%f×tamp=%d&sensor=false "type GoogleTimezone struct { DstOffset float64 bson:"dstOffset" RawOffset float64 bson:"rawOffset" Status string bson:"status" TimezoneID string bson:"timeZoneId" TimezoneName string bson:"timeZoneName"}```Go 對 JSON 和 XML 有非常好的支援。如果您看 GoogleTimezone 結構,您會看到每個欄位都包含一個"標籤"。標籤是額外的資料附加在每個欄位,它能通過使用反射擷取到。要瞭解標籤的更多資訊可以讀這個文檔。http://golang.org/pkg/reflect/#StructTagencoding/json 包定義了一組標籤,它可以協助封裝和拆封 JSON 資料。要瞭解更多關於 Go 對 JSON 的支援可以讀這些文檔。http://golang.org/doc/articles/json_and_go.htmlhttp://golang.org/pkg/encoding/json/如果在結構中您定義的欄位名與 JSON 文檔中的欄位名相同,您就不需要使用標籤了。我沒那樣做是因為標籤能告訴 Unmarshal 函數如何映射資料。讓我們來看下這個函數,它能訪問 Google API 並將 JSON 文檔 Unmarshal 到我們的新類型上:```gofunc RetrieveGoogleTimezone(latitude float64, longitude float64) (googleTimezone *GoogleTimezone, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("%v", r) } }() uri := fmt.Sprintf(googleURI, latitude, longitude, time.Now().UTC().Unix()) resp, err := http.Get(uri) if err != nil { return googleTimezone, err } defer resp.Body.Close() // Convert the response to a byte array rawDocument, err = ioutil.ReadAll(resp.Body) if err != nil { return googleTimezone, err } // Unmarshal the response to a GoogleTimezone object googleTimezone = new(GoogleTimezone) if err = json.Unmarshal(rawDocument, googleTimezone); err != nil { return googleTimezone, err } if googleTimezone.Status != "OK" { err = fmt.Errorf("Error : Google Status : %s", googleTimezone.Status) return googleTimezone, err } if len(googleTimezone.TimezoneId) == 0 { err = fmt.Errorf("Error : No Timezone Id Provided") return googleTimezone, err } return googleTimezone, err}```這個 web 請求和錯誤處理是相當的模式化,所以讓我們只簡單的談論下 Unmarshal 調用。```gorawDocument, err = ioutil.ReadAll(resp.Body)err = json.Unmarshal(rawDocument, googleTimezone)```當這個 web 調用返回時,我們擷取到響應資料並把它儲存在一個位元組數組中。然後我們調用這個 json Unmarshal 函數,傳遞位元組數組和一個引用到我們返回的指標類型變數。這個 Unmarshal 調用能建立一個 GoogleTimezone類型對象,從返回的 JSON 文檔提取並拷貝資料,然後設定這個值到我們的指標變數。它相當聰明,如果任務欄位不能映射就被忽略。如果有異常發 Unmarshal 調用會返回一個錯誤。所以這很好,我們能得到 timezone 資料並把它解鎖為一個只有三行代碼的對象。現在唯一的問題是我們如何使用 timezoneid 來設定我們的位置?這又有個問題。我們必須從 feed 文檔提取本地時間,使用 timezone 資訊轉換所有 UTC。讓我們再看一下 feed 文檔:```xml<timezone>LST/LDT</timezone><item><date>2013/01/01</date><day>Tue</day><time>02:06 AM</time><predictions_in_ft>19.7</predictions_in_ft><predictions_in_cm>600</predictions_in_cm><highlow>H</highlow></item>```假設我們已經從文檔提取了資料,我們怎樣使用 timezoneid 是我們擺脫困境?看一下我在 main 函數裡寫的代碼。它使用 time.LoadLocation 函數和我們從 API 呼叫獲得的時區識別碼 來解決這個問題。```gofunc main() { // Call to get the timezone for this lat and lng position googleTimezone, err := RetrieveGoogleTimezone(38.85682, -92.991714) if err != nil { fmt.Printf("ERROR : %s", err) return } // Pretend this is the date and time we extracted year := 2013 month := 1 day := 1 hour := 2 minute := 6 // Capture the location based on the timezone id from Google location, err := time.LoadLocation(googleTimezone.TimezoneId) if err != nil { fmt.Printf("ERROR : %s", err) return } // Capture the local and UTC time based on timezone localTime := time.Date(year, time.Month(month), day, hour, minute, 0, 0, location) utcTime := localTime.UTC() // Display the results fmt.Printf("Timezone:\t%s\n", googleTimezone.TimezoneId) fmt.Printf("Local Time: %v\n", localTime) fmt.Printf("UTC Time: %v\n", utcTime)}```這是輸出:```Timezone: America/ChicagoLocal Time: 2013-01-01 02:06:00 -0600 CSTTime: 2013-01-01 08:06:00 +0000 UTC```一切運行像冠軍一樣。我們的 localTime 變數設定為 CST 或 中央標準時間,這是芝加哥所在的位置。Google API 為經緯度提供了正確的時區,因為該位置屬於密蘇里州。https://maps.google.com/maps?q=39.232253,-92.991714&z=6我們要問的最後一個問題是 LoadLocation 函數如果擷取時區識別碼 字串並使其工作。時區識別碼 包含一個國家和城市(美國/芝加哥)。一定有數以千計這樣的時區識別碼。如果我們看一下 LoadLocation 的 time 包文檔,我們就能找到答案:http://golang.org/pkg/time/#LoadLocation這是 LoadLocation 文檔:```LoadLocation returns the Location with the given name.If the name is "" or "UTC", LoadLocation returns UTC. If the name is "Local", LoadLocation returns Local.Otherwise, the name is taken to be a location name corresponding to a file in the IANA Time Zone database, such as "America/New_York".The time zone database needed by LoadLocation may not be present on all systems, especially non-Unix systems. LoadLocation looks in the directory or uncompressed zip file named by the ZONEINFO environment variable, if any, then looks in known installation locations on Unix systems, and finally looks in $GOROOT/lib/time/zoneinfo.zip.```如果您讀最後一段,您將看到 LoadLocation 函數正讀取資料庫檔案擷取資訊。我沒有下載任何資料庫,也沒設定名為 ZONEINFO 的環境變數。唯一的答案是在 GOROOT 下的 zoneinfo.zip檔案。讓我們看下:![](https://raw.githubusercontent.com/studygolang/gctt-images/master/using-time-timezones-and-location-in-go/Screen+Shot+2013-08-08+at+8.06.04+PM.png)果然有個 zoneinfo.zip 檔案在 Go 的安裝位置下的 lib/time 目錄下。非常酷!!!您有它了。現在您知道如何使用 time.LoadLocation函數來協助確保您的時間值始終在正確的時區。如果您有經緯度,則可以使用任一 API 擷取該時區識別碼。如果您想要這兩個API 都被調的代碼可重用副本的話,我已經在 Github 的 GoingGo 庫中添加了一個名為 timezone 的新包。以下是整個工作樣本程式:```gopackage mainimport ( "encoding/json" "fmt" "io/ioutil" "net/http" "time")const ( googleURI = "https://maps.googleapis.com/maps/api/timezone/json?location=%f,%f×tamp=%d&sensor=false")type GoogleTimezone struct { DstOffset float64 bson:"dstOffset" RawOffset float64 bson:"rawOffset" Status string bson:"status" TimezoneID string bson:"timeZoneId" TimezoneName string bson:"timeZoneName"}func main() { // Call to get the timezone for this lat and lng position googleTimezone, err := RetrieveGoogleTimezone(38.85682, -92.991714) if err != nil { fmt.Printf("ERROR : %s", err) return } // Pretend this is the date and time we extracted year := 2013 month := 1 day := 1 hour := 2 minute := 6 // Capture the location based on the timezone id from Google location, err := time.LoadLocation(googleTimezone.TimezoneID) if err != nil { fmt.Printf("ERROR : %s", err) return } // Capture the local and UTC time based on timezone localTime := time.Date(year, time.Month(month), day, hour, minute, 0, 0, location) utcTime := localTime.UTC() // Display the results fmt.Printf("Timezone:\t%s\n", googleTimezone.TimezoneID) fmt.Printf("Local Time: %v\n", localTime) fmt.Printf("UTC Time: %v\n", utcTime)}func RetrieveGoogleTimezone(latitude float64, longitude float64) (googleTimezone *GoogleTimezone, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("%v", r) } }() uri := fmt.Sprintf(googleURI, latitude, longitude, time.Now().UTC().Unix()) resp, err := http.Get(uri) if err != nil { return googleTimezone, err } defer resp.Body.Close() // Convert the response to a byte array rawDocument, err := ioutil.ReadAll(resp.Body) if err != nil { return googleTimezone, err } // Unmarshal the response to a GoogleTimezone object googleTimezone = new(GoogleTimezone) if err = json.Unmarshal(rawDocument, &googleTimezone); err != nil { return googleTimezone, err } if googleTimezone.Status != "OK" { err = fmt.Errorf("Error : Google Status : %s", googleTimezone.Status) return googleTimezone, err } if len(googleTimezone.TimezoneID) == 0 { err = fmt.Errorf("Error : No Timezone Id Provided") return googleTimezone, err } return googleTimezone, err}```
via: https://www.ardanlabs.com/blog/2013/08/using-time-timezones-and-location-in-go.html
作者:William Kennedy 譯者:themoonbear 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
283 次點擊