iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
自我挑戰組

Go in 3o系列 第 27

[Day27] Go in 30 - 時間處理

  • 分享至 

  • xImage
  •  

一、本篇提要

本篇開始介紹Go中的time套件,時間處裡是Go程式的核心之一,time套件官方文件

  • 建立時間資料
  • 時間值的格式化
  • 時間值的管理
  • 時間值的比較與時間長度處理

二、建立時間資料

2.1 取得系統時間

Go 中,時間資料會是 time.Time 結構型別。

package main

import "fmt"

func main() {
    start := time.Now() //取得當下系統時間
    fmt.Println("程式開始時間:" , start)
    fmt.Println("資料處理中...")
    time.Sleep(2 * time.Second) // 讓程式停兩秒
    end := time.Now() //再次取得當下系統時間
    fmt.Println("程式結束時間:", end)
}

結果 :

https://ithelp.ithome.com.tw/upload/images/20231005/20162693owFwIzc4F7.png

以上時間值實際上分為兩個部分 :

  • wall clock : 2023-10-05 17:02:17.5800256 +0800 CST
  • monotonic clock : m=+0.001224801

wall clock 就是所謂的電腦系統時間,會透過NTP(network time protocal, 網路時間協定)來同步。而 monotonic clock 是程序啟動後經過的時間(單位為奈秒),它不會跟外界同步,可能在關機後就停擺,唯一的功能 : 拿來比較時間。

Go 語言時間值一定包括 wall clock 值,但不一定有 monotonic clock 值,而在使用time套件時也不必擔心這個細節。

2.2 取得時間特定項目

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now() //當下時間
    day := now.Weekday() //取星期幾
    hour := now.Hour() //取得小時
    fmt.Println("Day:", day, "/ hour:", hour)
    if day.String() == "Monday" && (hour >= 0 && hour <2) {
        fmt.Println("執行全功能測試")
    }else{
         fmt.Println("執行簡易測試")
    }
}

time.Time結構常用 :

方法名稱 描述 回傳值
Date() 獲取年、月、日 int, time.Month, int
Day() 獲取日期(1-31) int
Month() 獲取月份 time.Month
Year() 獲取年份 int
Hour() 獲取小時(0-23) int
Minute() 獲取分鐘(0-59) int
Second() 獲取秒數(0-59) int
Nanosecond() 獲取奈秒 int
Weekday() 獲取星期幾 time.Weekday
YearDay() 獲取年中的第幾天(1-366) int
IsZero() 判斷 Time 值是否為零時間 bool
After(u) 判斷時間是否在另一時間之後 bool
Before(u) 判斷時間是否在另一時間之前 bool
Equal(u) 判斷兩個時間是否相等 bool
Add(d) 加上一段時間 time.Time
Sub(u) 計算兩個時間的差值 time.Duration
Format(layout) 根據指定的格式格式化時間 string
Local() 轉換時間為本地時區 time.Time
UTC() 轉換時間為UTC時區 time.Time
Unix() 獲取從1970年1月1日至今的秒數 int64
MarshalJSON() 將時間轉換為JSON格式 []byte, error

對於月分和星期幾,time套件也有定一相關常數。
而且在Go語言中如果要將int轉為string,就得是用strconv套件提供的轉換功能:

package main

import (
    "fmt"
    "strconv"
    "time"
)

func main() {
    appName := "HTTPCHECKER"
    action := "BASIC"
    date := time.Now()
    logFileName := appName + "_" + action + "_" + 
        strconv.Itoa(date.Day()) + ".log"
    fmt.Println("log檔案名稱:", logFileName)
}

https://ithelp.ithome.com.tw/upload/images/20231005/20162693sENahso8u1.png

三、時間值的格式化

3.1 將時間轉成指定格式的字串

time.Time 結構的 Format() 方法可以將時間轉成特定格式字串:

func (t Time) Format(layout string) string 

**參數layout為時間格式字串。**關於這個time套件也定義一系列常數 :

常數名稱 描述
ANSIC 預設的 ANSIC 格式 "Mon Jan _2 15:04:05 2006"
UnixDate 預設的 Unix 格式 "Mon Jan _2 15:04:05 MST 2006"
RubyDate 預設的 Ruby 格式 "Mon Jan 02 15:04:05 -0700 2006"
RFC822 RFC 822 格式 "02 Jan 06 15:04 MST"
RFC822Z RFC 822 with numeric zone 格式 "02 Jan 06 15:04 -0700"
RFC850 RFC 850 格式 "Monday, 02-Jan-06 15:04:05 PST"
RFC1123 RFC 1123 格式 "Mon, 02 Jan 2006 15:04:05 PST"
RFC1123Z RFC 1123 with numeric zone 格式 "Mon, 02 Jan 2006 15:04:05 -0700"
RFC3339 RFC 3339 格式 "2006-01-02T15:04:05Z07:00"
RFC3339Nano RFC 3339 with nanoseconds 格式 "2006-01-02T15:04:05.999999999Z07:00"
Kitchen Kitchen format 格式 "3:04PM"
Stamp Stamp format 格式 "Jan _2 15:04:05"
StampMilli Stamp with millisecond format 格式 "Jan _2 15:04:05.000"
StampMicro Stamp with microsecond format 格式 "Jan _2 15:04:05.000000"
StampNano Stamp with nanosecond format 格式 "Jan _2 15:04:05.000000000"

範例 :

package main

import(
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Format(time.ANSIC))
    fmt.Println(time.Now().Format(time.UnixDate))
    fmt.Println(time.Now().Format(time.RFC3339))
    fmt.Println(time.Now().Format("2006/1/2 3:4:5") //自訂時間格式
}

3.2 自訂時間格式

一些細節規則請看官方文件 : 時間格式官方文件

Go 語言使用一個叫 "magical reference data" 的字串,讓你自訂時間格式。
在 Go 語言的 time 套件中,有一些預定義的格式常數,這些常數通常被用來格式化和解析日期和時間。以下是其中的一些常用的時間格式常數及其對應的值,每個值會照順序對應1234567:

時區
Jan 2 15 04 05 2006 -0700
1 2 3 4 5 6 7

"2006-01-02 15:04:05" 在 Go 語言的 time 套件中是一個特殊的時間格式。這並不是隨便選擇的日期,而是有特定的含義。在 Go 中,這個日期時間對應到:

  • 2006 年
  • 01 月
  • 02 日
  • 15 小時
  • 04 分鐘
  • 05 秒

這個時間格式是基於 Go 語言之父 Rob Pike 的建議選擇的,主要是因為它獨特(每個部分的數字都不同),所以在格式化和解析時間時不會造成混淆。

當你使用 time.Format 或 time.Parse 來格式化或解析時間時,你可以使用這個特定日期時間的任何部分來指示你想要的輸出格式。例如:

  • 2006 代表完整的年份
  • 01 代表月份
  • 02 代表日期
  • 15 代表小時(24小時制)
  • 04 代表分鐘
  • 05 代表秒

這樣的設計方式有助於使時間格式化和解析在 Go 中變得直觀。而不是使用一些其他語言中的碼(例如 %Y、%m、%d 等),你只需要使用這個特定的日期時間組合。

以下是 time.Date() 函數的一些使用範例:

  1. 建立特定日期的時間:
t := time.Date(2023, time.January, 15, 0, 0, 0, 0, time.UTC)
fmt.Println(t) // 輸出: 2023-01-15 00:00:00 +0000 UTC
  1. 建立特定日期的時間:
t := time.Date(2023, time.January, 15, 14, 30, 0, 0, time.UTC)
fmt.Println(t) // 輸出: 2023-01-15 14:30:00 +0000 UTC
  1. 使用特定時區:
loc, _ := time.LoadLocation("Asia/Taipei")
t := time.Date(2023, time.January, 15, 14, 30, 0, 0, loc)
fmt.Println(t) // 這將會輸出該時區的對應時間

四、時間值的管理

4.1 AddDate()

AddDate() 方法允許你向 time.Time 值添加(或減少)指定的年、月和日。這是一個方便的方法,用於計算從特定日期開始的相對日期。

以下是使用 time.AddDate() 方法的一些示例:

  1. 添加一年、兩個月和三天:
t := time.Now()
newTime := t.AddDate(1, 2, 3)
fmt.Println("Original Time:", t)
fmt.Println("New Time:", newTime)
  1. 減少一年和兩個月:
t := time.Now()
newTime := t.AddDate(-1, -2, 0)
fmt.Println("Original Time:", t)
fmt.Println("New Time:", newTime)
  1. 只添加天數:
t := time.Now()
newTime := t.AddDate(0, 0, 10)
fmt.Println("Original Time:", t)
fmt.Println("New Time:", newTime)

4.2 使用Time結構本身的ln()來指定時區

func (t Time) In(loc *Location) Time

這個方法是 time.Time 結構的一個方法。此方法用於將 Time 值從它當前的時區轉換到指定的時區 loc,並回傳新的 Time 值。
time 套件中的時區time.Location結構型別,之前使用time.UTC、time.Local 都屬與這種結構型別。

如果想使用特定時區、甚至是建立自定時區,有兩種方式 :

  1. 使用已知的時區名稱進行查找:
    使用 time.LoadLocation() 函數,你可以使用時區的名稱來查找一個已知的時區。
loc, err := time.LoadLocation("Asia/Taipei")
if err != nil {
    fmt.Println("Error:", err)
    return
}
t := time.Now().In(loc)
fmt.Println("Time in Asia/Taipei:", t)

這裡的 "Asia/Taipei" 是 IANA 時區資料庫中的一個時區名稱。

  1. 建立自定義的時區:
    使用 time.FixedZone() 函數,你可以建立一個具有指定名稱和偏移量的自定義時區。
offset := 8 * 60 * 60  // 偏移量為8小時,轉換為秒
loc := time.FixedZone("MyTimeZone", offset)
t := time.Now().In(loc)
fmt.Println("Time in MyTimeZone:", t)

這裡的 "MyTimeZone" 是你給定的自定義時區名稱,而偏移量是從 UTC 時間計算的秒數。

範例 :

package main

import (
	"fmt"
	"time"
)

func main() {
	// 獲得當前的時間(可能是本地時區或UTC,取決於系統設定)
	now := time.Now()

	// 輸出當前時間
	fmt.Println("Current Time:", now)

	// 使用time.LoadLocation()取得特定時區(例如 "Asia/Taipei")
	taipei, err := time.LoadLocation("Asia/Taipei")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// 使用In()方法將時間轉換到指定的時區
	taipeiTime := now.In(taipei)

	// 輸出轉換後的時間
	fmt.Println("Time in Asia/Taipei:", taipeiTime)
}

4.3 ParseInLocation() 說明

ParseInLocation() 是 time 包中的一個功能,它允許你根據指定的layout和字串解析日期和時間,并同時指定時區。當你知道字串代表的時區,且希望確保轉換過程中使用該時區時,可以使用這個。

ParseInLocation() 函式:

func ParseInLocation(layout, value string, loc *Location) (Time, error)
  • layout:用於指定日期和時間的格式。
  • value:你要轉換的實際日期和時間字串。
  • loc:指定的時區,它是一個指向 time.Location 的指標。

範例 :

package main

import (
	"fmt"
	"time"
)

func main() {
	const layout = "2006-01-02 15:04:05"
	value := "2023-10-01 14:15:45"

	// 要哪個時區
	location, err := time.LoadLocation("Asia/Taipei")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// 轉換時間值到特定時區
	t, err := time.ParseInLocation(layout, value, location)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("Parsed time:", t)
}

在上述範例中,我們首先定義了日期和時間的格式(layout)。然後,我們將字串 value 在 "Asia/Taipei" 時區的上下文中轉換成 time.Time 值。

ParseInLocation() 與 Parse() 的主要區別在於,Parse() 假定字串表示的是UTC時間,除非字符串本身指定了時區。而 ParseInLocation() 允許你明確地指定字串應該在哪個時區上下文中解析,即使字串本身不包含時區信息。

4.4 比較時間

time.Time 提供了幾個方法來比較兩個時間值。以下是 Equal(), Before(), 和 After() 方法的詳細說明和比較:

  1. Equal()

方法定義:
func (t Time) Equal(u Time) bool
說明:Equal 比較時間 t 和 u 是否代表同一時刻。因為time.Time 值可能是在不同的時區,這個方法會考慮時區的轉換,所以即使兩個時間值在不同的時區,只要他們代表的是同一時刻,就會返回 true。

  1. Before()

方法定義:
func (t Time) Before(u Time) bool
說明:Before 比較時間 t 是否在 u 之前。它不會考慮時區,所以兩個 time.Time 值即使在不同的時區,只要 t 在 u 之前,就會返回 true。

  1. After()

方法定義:
func (t Time) After(u Time) bool
說明:After 比較時間 t 是否在 u 之後。這個方法的行為與 Before 相似,但方向相反。它同樣不考慮時區。

範例:

package main

import (
	"fmt"
	"time"
)

func main() {
	t1 := time.Date(2023, 10, 1, 12, 0, 0, 0, time.UTC)
	t2 := time.Date(2023, 10, 1, 14, 0, 0, 0, time.UTC)

	fmt.Println("t1 equals t2:", t1.Equal(t2))
	fmt.Println("t1 is before t2:", t1.Before(t2))
	fmt.Println("t1 is after t2:", t1.After(t2))
}

執行結果 :

t1 equals t2: false
t1 is before t2: true
t1 is after t2: false

五、時間值的比較與時間長度處理

5.1 Sub() 時間差

有時候我們會需要計算程式的耗費時間、或對網站做壓力測試,如果要測量某段執行時間,只需要在該程式頭尾各取一次當下系統時間,然後用第二個時間Sub()減去第一個時間值:

 func (t Time) Sub(u Time) Duration

以下是一個簡單的Go示例,演示如何測量某段程式碼的執行時間:

package main

import (
    "fmt"
    "time"
)

func main() {
    // 開始時間
    startTime := time.Now()

    // 在這裡放置你要測試的程式碼
    // 例如,你可以執行一個循環或計算一個複雜的運算

    // 結束時間
    endTime := time.Now()

    // 計算執行時間
    elapsedTime := endTime.Sub(startTime)

    fmt.Printf("程式執行時間:%v\n", elapsedTime)
}

範例 :

package main

import (
    "fmt"
    "time"
)

func main() {
    // 開始時間
    startTime := time.Now()

    // 在這裡放置你要測試的程式碼
    // 例如,你可以執行一個循環或計算一個複雜的運算

    // 結束時間
    endTime := time.Now()

    // 計算執行時間
    elapsedTime := endTime.Sub(startTime)

    fmt.Printf("程式執行時間:%v\n", elapsedTime)
}

在這個示例中,我們使用time.Now()來獲取開始時間和結束時間,然後使用Sub()方法計算它們之間的差距,最後印出執行時間。

5.2 time.Since() time Until() 說明

在Go語言中,time包提供了一些有用的函數和方法來處理時間和持續時間。
其中包括time.Since()和time.Until()方法。

  1. time.Since()函數:

time.Since()函數返回自某個時間點以來的持續時間(從某個時間點到現在系統時間)。。

寫法相當於 :

time.Since(<時間值>)
//time.Now().Sub(<時間值>)

下面是一個示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    startTime := time.Now() // 獲取開始時間

    // 在這裡放置你要測試的程式碼
    time.Sleep(2 * time.Second) // 模擬一些工作

    endTime := time.Now() // 獲取結束時間

    // 使用time.Since()計算持續時間
    elapsedTime := time.Since(startTime)

    fmt.Printf("程式執行時間:%v\n", elapsedTime)
}

在這個示例中,我們使用time.Now()來獲取開始和結束時間,然後使用time.Since()函數計算兩者之間的持續時間。這允許我們測量程式碼執行的實際時間。

  1. Until()方法:

可計算當下系統時間到未來時間點還有多久 :

time.Until(<時間值>)
//(<時間值>.Sub(time.Now()))

下面是一個示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    // 定義一個未來時間點
    futureTime := time.Now().Add(2 * time.Hour)

    // 使用time.Until()計算未來時間與當前時間的差距
    durationUntil := time.Until(futureTime)

    fmt.Printf("距離未來時間還有:%v\n", durationUntil)
}

以上就是有關於時間的部分整理


上一篇
[Day26] Go in 30 - Debug - 單元測試(unit test)
下一篇
[Day28] Go in 30 - 處理JSON資料
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言