golang時間與時區相關操作總結


前言

任何語言處理時間的功能都是最基礎也是平時十分常用的,另外需要注意任何脫離時區的時間都是沒有任何意義的!

這里總結一下筆者在這一個多月寫go項目用到的、收集到的一些好用的處理時間的方法以及時間處理的一些基礎知識點。

golang時間操作基礎 ***

go關於時間操作的基礎我這邊自己做了一下筆記:

golang時間操作基礎及重要操作總結

常用的方法 ***

初始化時區的方法

package time_module

import "time"

var TIME_LOCATION *time.Location

func init() {
    var err error
    TIME_LOCATION, err = time.LoadLocation("Asia/Shanghai")
    if err != nil {
        panic(err)
    }
}

// 當前時區的當前時間
func GetCurrentTime() time.Time {
    return time.Now().In(TIME_LOCATION)
}

封裝的一些簡單方法 

package time_module

import (
    "github.com/jinzhu/now"
    "time"
)

/*
    時間比較與加減:注意先轉換到同一個時區!在底層方法中將時間轉換到同一個時區
*/

// 2個日期是否相等(注意先轉到同一個時區)
func DateEqual(date1, date2 time.Time) bool {
    date1 = date1.In(TIME_LOCATION)
    date2 = date2.In(TIME_LOCATION)
    y1, m1, d1 := date1.Date()
    y2, m2, d2 := date2.Date()
    return y1 == y2 && m1 == m2 && d1 == d2
}

// 2個日期的前后 先轉到同一時區
func DateAfter(date1, date2 time.Time) bool {
    // 在同一個時區比較
    date1 = date1.In(TIME_LOCATION)
    date2 = date2.In(TIME_LOCATION)
    return date1.After(date2)
}

// 計算2個時間相差的天數 先轉到同一個時區 // 注意時間前后:t1 需要比 t2 大!如果t1比t2小得出的結果會是一個負數!
func TimeSubDays(t1, t2 time.Time) int {
    // 轉換成同一個時區去比較!
    t1 = t1.In(TIME_LOCATION)
    t2 = t2.In(TIME_LOCATION)

    t1 = time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, TIME_LOCATION)
    t2 = time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, TIME_LOCATION)
    return int(t1.Sub(t2).Hours() / 24)
}

// 獲取當前時間距離當天結束還有多少秒 —— redis中設置過期key常用 // 需要導入第三方包:github.com/jinzhu/now
func GetEndOfDayRemainSeconds() time.Duration {
    currentTime := time.Now().In(TIME_LOCATION)
    return time.Second * time.Duration(now.New(currentTime).EndOfDay().Sub(currentTime).Seconds())
}

計算2個時間相差的天數的測試

package scripts_stroage

import (
    "fmt"
    "testing"
    "time"
)

var TimeLocation *time.Location

// 包初始化的時候初始化時區!!!
func init() {
    var err error
    TimeLocation, err = time.LoadLocation("Asia/Shanghai")
    if err != nil {
        panic(err)
    }
}

// 計算兩個時間相差的天數 TODO 注意時間前后:t1 需要比 t2 大!如果t1比t2小得出的結果會是一個負數!!!
func TimeSubDays(t1, t2 time.Time) int {
    // 轉換成同一個時區去比較!
    t1 = t1.In(TimeLocation)
    t2 = t2.In(TimeLocation)

    fmt.Println("T1>>> ", t1)
    fmt.Println("T2>>> ", t2)

    t1 = time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, TimeLocation)
    t2 = time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, TimeLocation)
    return int(t1.Sub(t2).Hours() / 24)
}

func TestTimeSubDays(t *testing.T) {

    // 1、用東8區時區創建時間,結果是1
    t1 := time.Date(2018, 1, 10, 0, 0, 1, 100, time.Local)
    t2 := time.Date(2018, 1, 9, 23, 59, 22, 100, time.Local)

    fmt.Println("t1: ", t1)
    fmt.Println("t2: ", t2)
    // 即使相差1秒,相差也是1天
    println("1>>> ", TimeSubDays(t1, t2))

    // TODO 2、用UTC時區創造2個時間注意一下~  // TODO 是因為TimeSubDays方法中的In方法將2個時間統一轉換成東8區的時間區處理了!這2個時間轉換成東8區時間確實是同一天!
    t3 := time.Date(2017, 1, 10, 0, 0, 1, 100, time.UTC)
    t4 := time.Date(2017, 1, 9, 23, 59, 22, 100, time.UTC)
    fmt.Println("t3: ", t3)
    fmt.Println("t4: ", t4)
    println("2>>> ", TimeSubDays(t3, t4))

}

封裝好的一些方法合集(使用上面初始化的時區) ******

package time_module

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


// GetMillis is a convenience method to get milliseconds since epoch.
func GetMillis() int64 {
    return time.Now().UnixNano() / int64(time.Millisecond)
}

func GetMillisStr() string {
    return fmt.Sprint(GetMillis())
}

// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
func GetMillisForTime(thisTime time.Time) int64 {
    return thisTime.UnixNano() / int64(time.Millisecond)
}

func GetSecondsForMillisecond(millisecond int64) int64 {
    return millisecond / int64(time.Microsecond)
}

// PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format
func PadDateStringZeros(dateString string) string {
    parts := strings.Split(dateString, "-")
    for index, part := range parts {
        if len(part) == 1 {
            parts[index] = "0" + part
        }
    }
    dateString = strings.Join(parts[:], "-")
    return dateString
}

// GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day
func GetStartOfDayMillis(thisTime time.Time, loc *time.Location) int64 {
    resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, loc)
    return GetMillisForTime(resultTime)
}

// GetEndOfDayMillis is a convenience method to get milliseconds since epoch for provided date's end of day
func GetEndOfDayMillis(thisTime time.Time, loc *time.Location) int64 {
    resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 23, 59, 59, 999999999, loc)
    return GetMillisForTime(resultTime)
}

// GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day
func GetStartOfMonthMillis(thisTime time.Time, loc *time.Location) int64 {
    resultTime := time.Date(thisTime.Year(), thisTime.Month(), 1, 0, 0, 0, 0, loc)
    return GetMillisForTime(resultTime)
}

// GetEndOfMonthMillis is a convenience method to get milliseconds since epoch for provided date's end of day
func GetStartOfNextMonthMillis(thisTime time.Time, loc *time.Location) int64 {
    resultTime := time.Date(thisTime.Year(), thisTime.Month(), 1, 0, 0, 0, 0, loc)
    resultTime = resultTime.AddDate(0, 1, 0)
    return GetMillisForTime(resultTime)
}

// 下一個月開始1號的時間戳
func GetStartOfNextMonthMillisByTimeStamp(timeStamp int64) int64 {
    tm := time.Unix(0, timeStamp*int64(1000*1000)).In(TIME_LOCATION)
    return GetStartOfNextMonthMillis(tm, TIME_LOCATION)
}

func GetStartOfMonthMillisByTimeStamp(timeStamp int64) int64 {
    tm := time.Unix(0, timeStamp*int64(1000*1000)).In(TIME_LOCATION)
    return GetStartOfMonthMillis(tm, TIME_LOCATION)
}

// 當前月份開始1號的時間戳
func GetDataStorePartitionByTimeStamp(timeStamp int64) string {
    tm := time.Unix(0, timeStamp*int64(1000*1000)).In(TIME_LOCATION)
    return fmt.Sprintf("p%d", GetStartOfMonthMillis(tm, TIME_LOCATION))
}

// 按東八區解析當前date, format,返回時間戳
func GetTimeStampsFromDateCST(date, format string) (int64, string) {
    date = PadDateStringZeros(date)
    resultTime, err := time.ParseInLocation(format, date, TIME_LOCATION)
    if err != nil {
        return 0, "Model.GetTimeStampsFromDateCST"
    }
    return GetMillisForTime(resultTime), ""
}

func GetDateCSTFromTimeStamp(timeStamp int64, format string) string {
    tm := time.Unix(0, timeStamp*int64(1000*1000)).In(TIME_LOCATION)
    return tm.Format(format)
}

func GetDateCSTFromTimeStampStr(timeStamp string, format string) string {
    ts, err := strconv.ParseInt(timeStamp, 10, 64)
    if err != nil {
        return "NAN"
    }
    tm := time.Unix(0, ts*int64(1000*1000)).In(TIME_LOCATION)
    return tm.Format(format)
}

func WeekStart(year, week int) time.Time {
    // Start from the middle of the year:
    t := time.Date(year, 7, 1, 0, 0, 0, 0, TIME_LOCATION)

    // Roll back to Monday:
    if wd := t.Weekday(); wd == time.Sunday {
        t = t.AddDate(0, 0, -6)
    } else {
        t = t.AddDate(0, 0, -int(wd)+1)
    }

    // Difference in weeks:
    _, w := t.ISOWeek()
    t = t.AddDate(0, 0, (week-w)*7)

    return t
}

func WeekRange(year, week int) (start, end time.Time) {
    start = WeekStart(year, week)
    end = start.AddDate(0, 0, 6)
    return
}

func WeekRangeFormat(year, week int, format string) string {
    start, end := WeekRange(year, week)
    return start.Format(format) + "~" + end.Format(format)
}
封裝好的方法的合集

根據開始的時間戳返回一段時間的方法 ***

有時候我們會拿到一個時間點作為開始時間,而這個時間還有可能是個時間戳,這時候需要將時間戳轉成時間(注意時區),而開始時間一般只有年月日,我們還需要做一些中間處理,我這里寫了一個方法供大家參考:

var TIME_LOCATION_CST *time.Location

// 時區默認為東八區!
func init() {
    TIME_LOCATION_CST, _ = time.LoadLocation("Asia/Shanghai")
}

// 根據月與日是單位數還是雙數返回不同的字符串
func getDateStr(month time.Month, day int) (monthStr, dayStr string) {
    if month < 10 {
        monthStr = fmt.Sprintf("0%d", month)
    } else {
        monthStr = fmt.Sprintf("%d", month)
    }
    if day < 10 {
        dayStr = fmt.Sprintf("0%d", day)
    } else {
        dayStr = fmt.Sprintf("%d", day)
    }
    return monthStr, dayStr
}

// 根據開始的時間戳獲取開始與結束的日期 —— 默認東八區
func GetQueryTimeFromTimeStamp(StartTimeStamp int64) (queryStart, queryEnd time.Time, appError *AppError) {
    // TODO 注意:時間戳是 毫秒 級別的,所以下面要除以1000
    // 1、開始時間
    createTime := time.Unix(StartTimeStamp/1000, 0)
    // 先轉字符串再轉成年月日的格式
    year, month, day := createTime.Date()
    // 如果月與日是單位數,需要在前面加上0!否則下面parse的時候可能會報錯!
    monthStr, dayStr := getDateStr(month, day)
    startTimeStr := fmt.Sprintf("%d-%s-%s", year, monthStr, dayStr)
    if ret1, err := time.ParseInLocation("2006-01-02", startTimeStr, TIME_LOCATION_CST); err == nil {
        queryStart = ret1
    } else {
        appError = NewAppError("GetQueryTimeFromTimeStamp", "time.ParseInLocation.error", err.Error(), nil)
        return ret1, ret1, appError
    }
    // 2、結束時間 TODO:注意這里返回 "明天" 的日期,返回后根據自己的實際情況做AddDate操作!
    nowTime := time.Now()
    endTime := nowTime.AddDate(0, 0, 1)
    year, month, day = endTime.Date()
    // 如果月與日是單位數,需要在前面加上0!
    monthStr, dayStr = getDateStr(month, day)
    endTImeStr := fmt.Sprintf("%d-%s-%s", year, monthStr, dayStr)
    if ret2, err := time.ParseInLocation("2006-01-02", endTImeStr, TIME_LOCATION_CST); err == nil {
        queryEnd = ret2
    } else {
        appError = NewAppError("GetQueryTimeFromTimeStamp", "time.ParseInLocation.error", err.Error(), nil)
        return ret2, ret2, appError
    }
    return queryStart, queryEnd, nil
}

根據開始與結束日期以5天為間隔返回每一個時間段的開始與結束日期 ***

還需要用到上面的 GetQueryTimeFromTimeStamp 方法,在此基礎上獲取這個時間段內以5天為間隔的開始與結束日期的組合:

func (s *SnapchatAccountHistoryAdCostTask) getTimeRange(createTimeStamp int64) ([]map[string]string, *model.AppError) {
    var timeRangeLst []map[string]string
    // 根據賬號創建的時間戳獲取開始與結束的日期 結束時間是"明天"
    startTime, endTime, err := model.GetQueryTimeFromTimeStamp(createTimeStamp)
    if err != nil {
        return nil, err
    }
    fmt.Printf("startTime: %v, endTime: %v \n", startTime, endTime)
    // startTime: 2020-08-21 00:00:00 +0800 CST, endTime: 2020-12-23 00:00:00 +0800 CST
    // 算一下兩個時間差幾天
    timeDUration := endTime.Sub(startTime)
    fmt.Println("相差的小時數... ", timeDUration.Hours()) // 2976 // 小時數轉天數 肯定是整數,因為開始與結束日期都是0點
    days := int(int64(timeDUration.Hours()) / 24) 
    fmt.Println("相差的天數... ", days)  // 124 // 構建返回的列表 // 按照每5天一組組合數據 每一組的最后一個日期不算作統計的日期
    for i := 0; i < days; i += 5 { currStartDate := startTime.AddDate(0, 0, i) currEndDate := startTime.AddDate(0, 0, i+5) // 如果 currEndDate超過了 "明天" 那最后的結束日期就用明天
        if currEndDate.After(endTime) { currEndDate = endTime } currStartStr := currStartDate.Format("2006-01-02") currEndStr := currEndDate.Format("2006-01-02") currMap := map[string]string{ "startDate": currStartStr, "endDate": currEndStr, } timeRangeLst = append(timeRangeLst, currMap) } return timeRangeLst, nil
}

返回的結果如下(注意返回的結果每個元素是字典):

timeRangeLst>>>  
map[endDate:2020-11-04 startDate:2020-10-30]
map[endDate:2020-11-09 startDate:2020-11-04]
map[endDate:2020-11-14 startDate:2020-11-09] map[endDate:2020-11-19 startDate:2020-11-14] map[endDate:2020-11-24 startDate:2020-11-19] map[endDate:2020-11-29 startDate:2020-11-24] map[endDate:2020-12-04 startDate:2020-11-29] map[endDate:2020-12-09 startDate:2020-12-04] map[endDate:2020-12-14 startDate:2020-12-09]
map[endDate:2020-12-19 startDate:2020-12-14]
map[endDate:2020-12-23 startDate:2020-12-19]]

根據開始時間戳獲取一個開始到結束日期的時間字符串切片 ***

還需要用到上面的 GetQueryTimeFromTimeStamp 方法,寫一個死循環即可實現: 

// 根據賬號的創建時間戳得到一個包含開始到結束日期的字符串切片
func (f *FacebookAccountHistoryAdCostTask) getDateSlice(createTimeStamp int64) ([]string, *model.AppError) {
    var dateSlice []string
    // 根據賬號的創建時間獲取開始與結束日期的0點
    startTime, endTime, err := model.GetQueryTimeFromTimeStamp(createTimeStamp)
    if err != nil {
        return dateSlice, err
    }
    // 這里獲取到的endTime是"明天",Facebook渠道的endTime是今天
    endTime = endTime.AddDate(0, 0, -1)
    mlog.Info(fmt.Sprintf("startTime: %v \n, endTime: %v \n", startTime, endTime))
    // startTime: 2019-05-16 00:00:00 +0800 CST , endTime: 2020-12-24 00:00:00 +0800 CST
    // 得到一個從開始時間到結束時間字符串的列表
    for {
        if startTime.After(endTime){ break }
        startString := startTime.Format("2006-01-02")
        dateSlice = append(dateSlice, startString)
        startTime = startTime.AddDate(0, 0, 1)
    }
    return dateSlice, nil
}

結果如下:

[2020-11-13 
2020-11-14 2020-11-15 2020-11-16 2020-11-17 2020-11-18 2020-11-19 2020-11-20 2020-11-21 2020-11-22 2020-11-23 2020-11-24 2020-11-25 2020-11-26 2020-11-27 2020-11-28 2020-11-29 2020-11-30 2020-12-01 2020-12-02 2020-12-03 2020-12-04 2020-12-05 2020-12-06 2020-12-07 2020-12-08 2020-12-09 2020-12-10 2020-12-11 2020-12-12 2020-12-13 2020-12-14 2020-12-15 2020-12-16 2020-12-17 2020-12-18 2020-12-19 2020-12-20 2020-12-21 2020-12-22 2020-12-23
2020-12-24]

根據上一步的時間切片獲取分割的時間切片

根據開始與結束日期獲取一個開始到結束日期的時間切片:

package t9

import (
    "fmt"
    "testing"
    "time"
)

var TIME_LOCATION_CST, _ = time.LoadLocation("Asia/Shanghai")

// 根據開始與結束日期獲取一個包含開始到結束日期字符串的切片
func GetDateSlice(startTime, endTime time.Time) (dateSlice []string) { for { if startTime.After(endTime) { break } startStr := startTime.Format("2006-01-02") dateSlice = append(dateSlice, startStr) startTime = startTime.AddDate(0, 0, 1) } return }

func TestRange(t *testing.T) {

    startTime := time.Date(2020, 01, 01, 00, 00, 00, 00, TIME_LOCATION_CST)
    endTime := time.Date(2020, 02, 03, 00, 00, 00, 00, TIME_LOCATION_CST)

    timeRangeLst := GetDateSlice(startTime, endTime)
    fmt.Println("timeRangeLst>>> ", timeRangeLst)
    /*
        [2020-01-01 2020-01-02 2020-01-03 2020-01-04 2020-01-05 2020-01-06 2020-01-07 2020-01-08 2020-01-09 
         2020-01-10 2020-01-11 2020-01-12 2020-01-13 2020-01-14 2020-01-15
         2020-01-16 2020-01-17 2020-01-18 2020-01-19 2020-01-20 2020-01-21 2020-01-22 2020-01-23 2020-01-24 
         2020-01-25 2020-01-26 2020-01-27 2020-01-28 2020-01-29 2020-01-30 2020-01-31 2020-02-01 2020-02-02 2020-02-03]
    */

把它里面的字符串按照每4個一組整理成切片套切片的格式:

// 根據存時間字符串的切片獲取一個按照每4天分組的新切片
func getNewDateSlice(dateSlice []string) [][]string {
    var retSlice [][]string
    length := len(dateSlice)
    // 分組
    for i := 0; i < length; i += 4 {
        var currSlice []string
        if i+4 > length{ currSlice = dateSlice[i : length] }else{ currSlice = dateSlice[i : i+4] }
        retSlice = append(retSlice, currSlice)
    }
    return retSlice
}

結果如下:

[[2020-01-01 2020-01-02 2020-01-03 2020-01-04] 
[2020-01-05 2020-01-06 2020-01-07 2020-01-08]
[2020-01-09 2020-01-10 2020-01-11 2020-01-12]
[2020-01-13 2020-01-14 2020-01-15 2020-01-16]
[2020-01-17 2020-01-18 2020-01-19 2020-01-20]
[2020-01-21 2020-01-22 2020-01-23 2020-01-24]
[2020-01-25 2020-01-26 2020-01-27 2020-01-28]
[2020-01-29 2020-01-30 2020-01-31 2020-02-01]
[2020-02-02 2020-02-03]]

下一個時間的開始是上一個時間的結尾的切片嵌套的構建方法:

package t9

import (
    "fmt"
    "testing"
    "time"
)

var TIME_LOCATION_CST, _ = time.LoadLocation("Asia/Shanghai")

// 根據開始與結束日期獲取一個包含開始到結束日期字符串的切片
func GetDateSlice(startTime, endTime time.Time) (dateSlice []string) {
    for {
        if startTime.After(endTime) {
            break
        }
        startStr := startTime.Format("2006-01-02")
        dateSlice = append(dateSlice, startStr)
        startTime = startTime.AddDate(0, 0, 1)
    }
    return
}

// 整理下dateSlice的格式:下一個列表的第一個元素是上一個列表最后一個元素
func MinInt(a, b int) int { if a < b { return a } return b } func getNewDateSlice(dateSlice []string) (newDateSlice [][]string) { for i := 1; i < len(dateSlice); i += 5 { currSlice := dateSlice[i-1 : MinInt(i+5, len(dateSlice))] newDateSlice = append(newDateSlice, currSlice) } return }

func TestRange(t *testing.T) {

    startTime := time.Date(2020, 01, 01, 00, 00, 00, 00, TIME_LOCATION_CST)
    endTime := time.Date(2020, 02, 03, 00, 00, 00, 00, TIME_LOCATION_CST)

    timeRangeLst := GetDateSlice(startTime, endTime)
    fmt.Println("timeRangeLst>>> ", timeRangeLst)
    /*
            [2020-01-01 2020-01-02 2020-01-03 2020-01-04 2020-01-05 2020-01-06 2020-01-07 2020-01-08 2020-01-09
             2020-01-10 2020-01-11 2020-01-12 2020-01-13 2020-01-14 2020-01-15
             2020-01-16 2020-01-17 2020-01-18 2020-01-19 2020-01-20 2020-01-21 2020-01-22 2020-01-23 2020-01-24
             2020-01-25 2020-01-26 2020-01-27 2020-01-28 2020-01-29 2020-01-30 2020-01-31 2020-02-01 2020-02-02 2020-02-03]
    */

    ret := getNewDateSlice(timeRangeLst)
    fmt.Println("ret>>> ", ret)
    /* [[2020-01-01 2020-01-02 2020-01-03 2020-01-04 2020-01-05 2020-01-06] [2020-01-06 2020-01-07 2020-01-08 2020-01-09 2020-01-10 2020-01-11] [2020-01-11 2020-01-12 2020-01-13 2020-01-14 2020-01-15 2020-01-16] [2020-01-16 2020-01-17 2020-01-18 2020-01-19 2020-01-20 2020-01-21] [2020-01-21 2020-01-22 2020-01-23 2020-01-24 2020-01-25 2020-01-26] [2020-01-26 2020-01-27 2020-01-28 2020-01-29 2020-01-30 2020-01-31] [2020-01-31 2020-02-01 2020-02-02 2020-02-03]] */

}

~~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM