Golang で日付の繰り返し処理を扱う

# Golang で日付の繰り返し処理を扱う

こんにちは、今回はGolangで暦を扱う機会があったので、メモとして残しておきます。
Golangで暦を自在に操ってサクッと占いアプリでも作ってみたいですね。

# Golangにおける時間

Golangには時間を扱うことができる標準ライブラリがあります、また時間型をサポートしています。
例えば、現在の時刻は次のように記述して取得することができます。

```Go
package main

import (
	"fmt"
	"time"
)

func main() {
    fmt.Println(time.Now())
}
```

https://play.golang.org/p/CzmUKIlJCRU

ではここで取得した現在時刻を起点として一定の繰り返しで日付を取得する際どうすればよいのでしょうか。

# RRULEについて
実は、繰り返し設定をデータとして保存するのに有用な形式がRFC2445として定められています。

Internet Calendaring and Scheduling Core Object Specification
http://www.ietf.org/rfc/rfc2445.txt

今回はこのRRULEを用いて繰り返しの日付を取得してみましょう。
GolangにはいくつかRRULEを取得できるライブラリが存在します。
今回は比較的Githubのスター数の多い`rrule-go`を使ってみたいと思います。

https://github.com/teambition/rrule-go

例えば現在の日時から繰り返し1週間の日時を５つ取得したい場合はRRULE的に次のように定義できます。

"DTSTART:現在の日時\nFREQ=WEEKLY;INTERVAL=1;COUNT=5"

また`rrule-go`にはこのRRULEを文字列として渡してあげるとデータを取得できます。

```Go
package main

import (
	"fmt"
	"time"

	"github.com/teambition/rrule-go"
)

func main() {
	var timesetter string
	timesetter = time.Now().Format("20060102T150405Z")
	rruleInstance := "DTSTART:" + timesetter + "\n" + "FREQ=WEEKLY;INTERVAL=1;COUNT=5"
	s, err := rrule.StrToRRule(rruleInstance)
	if err != nil {
		fmt.Println("Rrule error",err)
	}
	fmt.Println(s.All())
}
```

https://play.golang.org/p/1jfzbKJrih2

このように非常にコンパクトに記述することができます。
RRULEの表現方法はたくさんあって追いきれないのでここでは上述の例に留めますが、実際に利用する際にはその用途に応じたRRULE文字列を作る必要があるので、文字列を作る関数を別途定義してあげると便利になると思います。

例えば、RRULEにおけるUNTILを抽出したい場合だと以下のような感じになると思います。

```Go
package main

import (
	"fmt"
	"time"

	"github.com/teambition/rrule-go"
)

func GetUntil(startData time.Time, endData time.Time, rruleData *string, exdateData *string) (time.Time, error) {
	var rruleInstance string
	var err error
	timeSetter := startData.Format("20060102T150405Z")
	if rruleData == nil {
		return endTimeData, err
	}
	rruleInstance = fmt.Sprintf("DTSTART:%s\nRRULE:%s", timeSetter, *rruleData)
	if exdateData != nil {
		rruleInstance = fmt.Sprintf("%s\nEXDATE:%s", rruleInstance, *exdateData)
	}
	RRule, err := rrule.StrToRRuleSet(rruleInstance)
	if err != nil {
		return endData, err
	}
	Until := RRule.All()[len(RRule.All())-1]
	return Until, err
}
```

# 実際に何か作ってみよう
せっかくなので、簡単な具体例として人間のバイオリズムでも算出してみましょう。
RRULEで周期は簡単に算出できますね。

> 1. 身体(Physical)     23日周期：体力・耐久力・抵抗力・スタミナ・エネルギー・勇気など
> 2. 感情(Sensitivity)  28日周期：感情・気分・神経・直感・ムード・感受性・反射力・創造力など
> 3. 知性(Intellectual) 33日周期：知力・思考力・記憶力・分析力・判断力・集中力・構成力など

$$\sin(\frac{2 \pi t}{T})$$

出典：「バイオリズムの計算」CASIO 
https://keisan.casio.jp/exec/system/1231994137

`T`に関してはそれぞれの周期を指しています。

また、`t`に関しては次のように定義されています、
$$t = baseday - birthday$$

今回は実際に日付の差分を計算する際にUnixtimeを使いますが、
Golangの標準のUnixtimeは秒になっているので、

$$24 \times 60 \times 60 = 86400$$

この86400を次のようにUnixtimeから割ってあげると良いです。

```Go
t := float64(baseday.Unix()/86400 - birthday.Unix()/86400)
```

実装すると次のようになります。

```Go
package main

import (
	"fmt"
	"math"
	"time"

	"github.com/teambition/rrule-go"
)

func biorhythm(startData time.Time, count int32, t float64) {
	timeSetter := startData.Format("20060102T150405Z")
	rrulePhysical := fmt.Sprintf("DTSTART:%s\nFREQ=DAILY;INTERVAL=23;COUNT=%d", timeSetter, count)
	rruleSensitivity := fmt.Sprintf("DTSTART:%s\nFREQ=DAILY;INTERVAL=28;COUNT=%d", timeSetter, count)
	rruleIntellectual := fmt.Sprintf("DTSTART:%s\nFREQ=DAILY;INTERVAL=33;COUNT=%d", timeSetter, count)
	P, _ := rrule.StrToRRule(rrulePhysical)
	S, _ := rrule.StrToRRule(rruleSensitivity)
	I, _ := rrule.StrToRRule(rruleIntellectual)

	fmt.Printf("Physical:     ")
	fmt.Printf("sin(2πt/T)=%f", math.Sin((t*2*math.Pi)/23))
	fmt.Println(P.All())

	fmt.Printf("Sensitivity:  ")
	fmt.Printf("sin(2πt/T)=%f", math.Sin((t*2*math.Pi)/28))
	fmt.Println(S.All())

	fmt.Printf("Intellectual: ")
	fmt.Printf("sin(2πt/T)=%f", math.Sin((t*2*math.Pi)/33))
	fmt.Println(I.All())
}

func main() {
	birthday := // ? = time.Time型の誕生日をセット
	baseday :=  // ? = time.Time型の基準日をセット
	fmt.Println(birthday,baseday)
	t := float64(baseday.Unix()/86400 - birthday.Unix()/86400)
	biorhythm(birthday, 10, t)
}
```

https://play.golang.org/p/VOQl20-x9Cc

playgroundでは試しに
2020-11-07 23:00:00 +0000 UTC m=+346896000.000000001
2020-11-14 23:00:00 +0000 UTC m=+347500800.000000001
をセットしました。

このような形でRRULEを用いると、しっかりと言語化されるので、シンプルな表現で周期を算出できますね。

# 最後に
バイオリズムに関しては統計学的に有意なデータが得られていないので注意しましょう。
ですが、仮説であったとしても、よくエンターテイメントの世界で登場しますよね。
Golangにおけるライブラリ等に関してもきちんと特性、用法を知った上でそのツールをどう使っていくかというのは使い手次第ですね。

### 参考
「Internet Calendaring and Scheduling Core Object Specification」 http://www.ietf.org/rfc/rfc2445.txt
「rrule-go」https://github.com/teambition/rrule-go
「バイオリズム」 https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%82%AA%E3%83%AA%E3%82%BA%E3%83%A0
「バイオリズムの計算」https://keisan.casio.jp/exec/system/1231994137