1. 程式人生 > 其它 >responseheader沒有location欄位_在 Go 中使用 Time, Timezones 和 Location

responseheader沒有location欄位_在 Go 中使用 Time, Timezones 和 Location

技術標籤:responseheader沒有location欄位

今天我遇到個問題。我在編寫程式碼處理 NOAA 的潮汐站 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&timestamp=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 返回的資訊。

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

const googleURI = "https://maps.googleapis.com/maps/api/timezone/json?location=%f,%f&timestamp=%d&sensor=false "

type GoogleTimezone struct {
    DstOffset    float64 bson:&quot;dstOffset&quot;
    RawOffset    float64 bson:&quot;rawOffset&quot;
    Status       string  bson:&quot;status&quot;
    TimezoneID   string  bson:&quot;timeZoneId&quot;
    TimezoneName string  bson:&quot;timeZoneName&quot;
}

Go 對 JSON 和 XML 有非常好的支援。如果您看 GoogleTimezone 結構,您會看到每個欄位都包含一個"標籤"。標籤是額外的資料附加在每個欄位,它能通過使用反射獲取到。要了解標籤的更多資訊可以讀這個文件。

http://golang.org/pkg/reflect/#StructTag

encoding/json 包定義了一組標籤,它可以幫助封裝和拆封 JSON 資料。要了解更多關於 Go 對 JSON 的支援可以讀這些文件。

http://golang.org/doc/articles/json_and_go.html

http://golang.org/pkg/encoding/json/

如果在結構中您定義的欄位名與 JSON 文件中的欄位名相同,您就不需要使用標籤了。我沒那樣做是因為標籤能告訴 Unmarshal 函式如何對映資料。

讓我們來看下這個函式,它能訪問 Google API 並將 JSON 文件 Unmarshal 到我們的新型別上:

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
}

這個 web 請求和錯誤處理是相當的模式化,所以讓我們只簡單的談論下 Unmarshal 呼叫。

rawDocument, err = ioutil.ReadAll(resp.Body)

err = json.Unmarshal(rawDocument, googleTimezone)

當這個 web 呼叫返回時,我們獲取到響應資料並把它儲存在一個位元組陣列中。然後我們呼叫這個 json Unmarshal 函式,傳遞位元組陣列和一個引用到我們返回的指標型別變數。這個 Unmarshal 呼叫能建立一個 GoogleTimezone型別物件,從返回的 JSON 文件提取並拷貝資料,然後設定這個值到我們的指標變數。它相當聰明,如果任務欄位不能對映就被忽略。如果有異常發 Unmarshal 呼叫會返回一個錯誤。

所以這很好,我們能得到 timezone 資料並把它解封為一個只有三行程式碼的物件。現在唯一的問題是我們如何使用 timezoneid 來設定我們的位置?

這又有個問題。我們必須從 feed 文件提取本地時間,使用 timezone 資訊轉換所有 UTC。

讓我們再看一下 feed 文件:

<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 呼叫獲得的時區 ID 來解決這個問題。

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%sn", googleTimezone.TimezoneId)
    fmt.Printf("Local Time: %vn", localTime)
    fmt.Printf("UTC Time: %vn", utcTime)
}

這是輸出:

Timezone:   America/Chicago
Local Time: 2013-01-01 02:06:00 -0600 CST
Time:       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 函式如果獲取時區 ID 字串並使其工作。時區 ID 包含一個國家和城市(美國/芝加哥)。一定有數以千計這樣的時區 ID。

如果我們看一下 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檔案。讓我們看下:

0d63915fb6b50289da0c4baf2048c8d1.png

果然有個 zoneinfo.zip 檔案在 Go 的安裝位置下的 lib/time 目錄下。非常酷!!!

您有它了。現在您知道如何使用 time.LoadLocation函式來幫助確保您的時間值始終在正確的時區。如果您有經緯度,則可以使用任一 API 獲取該時區 ID。

如果您想要這兩個API 都被調的程式碼可重用副本的話,我已經在 Github 的 GoingGo 庫中添加了一個名為 timezone 的新包。以下是整個工作示例程式:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

const (
    googleURI = "https://maps.googleapis.com/maps/api/timezone/json?location=%f,%f&timestamp=%d&sensor=false"
)

type GoogleTimezone struct {
    DstOffset    float64 bson:&quot;dstOffset&quot;
    RawOffset    float64 bson:&quot;rawOffset&quot;
    Status       string  bson:&quot;status&quot;
    TimezoneID   string  bson:&quot;timeZoneId&quot;
    TimezoneName string  bson:&quot;timeZoneName&quot;
}

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%sn", googleTimezone.TimezoneID)
    fmt.Printf("Local Time: %vn", localTime)
    fmt.Printf("UTC Time: %vn", 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
}