Golang で日付操作(UTC→JST)

f:id:pigggg:20210811201006p:plain

UTCJSTに変えたくなった。

やったこと

package main

import (
    "fmt"
    "time"
)

const (
  ISO8601Format = "2006-01-02T15:04:05Z"
)

func main() {
  // UTC
  value := "2021-08-10T13:41:32Z"
  jst, _ := time.LoadLocation("Asia/Tokyo")
  r, _ := time.ParseInLocation(ISO8601Format, value, jst)

  fmt.Println(r)
  // 2021-08-10 13:41:32 +0900 JST
}

locationは変わっているが、+9時間されてない。
本当はこうなってほしい。

2021-08-10 22:41:32 +0900 JST

 

こう変えた

func main() {
  value := "2021-08-10T13:41:32Z"
  jst, _ := time.LoadLocation("Asia/Tokyo")

  r, _ := time.Parse(ISO8601Format, value)
  r2 := r.In(jst)

  fmt.Println(r)  // 2021-08-10 13:41:32 +0000 UTC
  fmt.Println(r2) // 2021-08-10 22:41:32 +0900 JST
}

意図通りにはなった。
が time.In は何をしているのか...
 

time.In は何をしているのか

// In returns a copy of t representing the same time instant, but
// with the copy's location information set to loc for display
// purposes.
//
// In panics if loc is nil.
func (t Time) In(loc *Location) Time {
  if loc == nil {
    panic("time: missing Location in call to Time.In")
  }
  t.setLoc(loc)
  return t
}

 
ほぉ。

Inは、同じ時間の瞬間を表すtのコピーを返しますが、表示のためにコピーの位置情報をlocに設定します。 locがnilの場合、Inはパニックになります。

Copyした time に位置情報を設定しているらしい。
ただ、中身は time.setLoc をしているだけ。
 

time.setLoc は何をしているのか

// setLoc sets the location associated with the time.
func (t *Time) setLoc(loc *Location) {
    if loc == &utcLoc {
        loc = nil
    }
    t.stripMono()
    t.loc = loc
}

 
ロケーションを設定しているだけのよう…

setLocは、時間に関連付けられたロケーションを設定します。  

time.stripMono とは…
 

time.stripMono は何をしているのか

// stripMono strips the monotonic clock reading in t.
func (t *Time) stripMono() {
    if t.wall&hasMonotonic != 0 {
        t.ext = t.sec()
        t.wall &= nsecMask
    }
}

 

stripMonoは、tに読み込まれた単調なクロックをストリップします。  

なるほどね。全然わからん。
 

動かしながらみる

Parseした段階ではこう

r, _ := time.Parse(ISO8601Format, value)

f:id:pigggg:20210811202908p:plain Parseした際のデフォルトTimezoneはUTCなのでこの段階では +0000
 
setLoc にきた段階ではこう

func (t *Time) setLoc(loc *Location) {

f:id:pigggg:20210811203102p:plain 変わらない。
 
stripMono に来た段階でも

if t.wall&hasMonotonic != 0

この分岐に入らず終わったので変わらなかった
 
時間が変わったのは setLoc のここ

t.loc = loc

f:id:pigggg:20210811203338p:plain Copy した(?) time に location を追加したところで +0900 された
 
う〜ん・・・
とりあえず time の loc に jst 入れると変わる。
 

time.loc

// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location

locは、このTimeに対応する分、時間、月、日、年を決定するために使用すべきLocationを指定します。nilのLocationはUTCを意味します。全てのUTCタイムはloc==nilで表され、決してloc==&utcLocではありません。

へぇ。location == nilUTC と…
 
そもそも time.ParseInLocation で jst 設定したときはなぜ変わらなかった??

  jst, _ := time.LoadLocation("Asia/Tokyo")
  r, _ := time.ParseInLocation(ISO8601Format, value, jst)

 

time.ParseInLocation は何をしているのか

// ParseInLocation is like Parse but differs in two important ways.
// First, in the absence of time zone information, Parse interprets a time as UTC;
// ParseInLocation interprets the time as in the given location.
// Second, when given a zone offset or abbreviation, Parse tries to match it
// against the Local location; ParseInLocation uses the given location.
func ParseInLocation(layout, value string, loc *Location) (Time, error) {
    return parse(layout, value, loc, loc)
}

実質 time.parse している  
 

time.parse は何をしているのか

pase 関数は長すぎて載せないが、今回の例だとここ

return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil

defaultLocation は

  jst, _ := time.LoadLocation("Asia/Tokyo")

なので、jst がセットされている。なのになぜ変わらないのだろう。
f:id:pigggg:20210811205327p:plain
Date() のなかでも loc は入っている。
 
そして Date() のなかで setLoc() もしている

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {
  if loc == nil {
    panic("time: missing Location in call to Date")
  }
  ...
  t := unixTime(unix, int32(nsec))
  t.setLoc(loc)
  return t
}

   
腑に落ちない…