正規化UNIX時間による季節判定
UNIX時間とは
UNIX時間(UNIX time)とは1970年1月1日0時0分0秒(UNIX epochと呼ばれる時刻)から刻み続けられている“秒数”を単位とした正の整数である。仕様として閏年はカウントするが、閏秒はカウントせず1日は必ず86,400秒(60秒×60分×24時間)となるよう工夫されている。そのため閏秒の瞬間は閏秒1秒前と同じUNIX時間となることもある、そんなおもしろ時計だ。
季節判定する上でのサンプル範囲を設定
四季(春夏秋冬)を判別する用途においてならばUNIX時間の閏秒の扱いは無視できる。よって閏年だけを考慮すれば良いこととなる。ここでは1970年(UNIX epoch)から2038年(32bitだとUNIX時間がオーバーフローする年)までの69年間をサンプル範囲として季節判定用の定数を探って行くこととする。ここで2038年もまるっと1年分として計算して行く。
判定方法
先述のとおりUNIX時間とはUNIX epochから閏秒を無視した経過秒数なので、1年毎の年間平均秒数($ Y_{avg} $)で割った値を正規化することで、小数点以下が月を示すような数値となることが期待できる。ここでは便宜上その数値を「正規化UNIX時間」($ U_{norm} $)と呼ぶ。ならば、判定したいUNIX時間を$ U_{time} $とすると次の式で正規化UNIX時間を求めることができる。
$$ \begin{aligned} Y_{f} &= \frac{U_{time}}{Y_{avg}} \\[1.5em] U_{norm} &= Y_{f} - \lfloor Y_{f} \rfloor \end{aligned} $$
2つ目の式はノコギリ波を生成することで有名。ノコギリの波ひとつひとつが1年を表し、その高さが時の経過とUNIX時間との比例関係を意味する。さて、この正規化UNIX時間において1月のはじめは0.0、12月のおわりは1.0になるはずなので、この間、すなわち各月のはじめとおわりに対応する数値を算出すれば、それなりに季節判定できると考えた。
まとめると、これからやることは2つ。ひとつは年間平均秒数$ Y_{avg} $を算出すること。そしてもうひとつは正規化UNIX時間における各月のはじめとおわりに対応する数値を算出することである。
年間平均秒数の算出
通常1年は365日なので1日=86,400秒に365を掛ければ1年あたりの秒数が計算できる。
$$ Y_{c} = 365days \times 24hours \times 60minutes \times 60seconds = 31,536,000 $$
問題は閏年だ。閏年は2月29日の存在する年。秒数にして平年より1日分多い数字となる。
$$ Y_{l} = 366days \times 24hours \times 60minutes \times 60seconds = 31,622,400 $$
この閏年は4年に1度訪れる。ただし、“西暦年数が100で割り切れかつ400で割り切れない年は平年とする”という例外が存在する。
幸いなことに1970年から2038年の間ならば上手いこと4年に1度の間隔で閏年が訪れる期間なので、単純に考えると365.25日(365+1/4日)を平均UNIX時間とすれば良い。つまり31,557,600秒だ。しかし、1970年から2038年までの69年間で閏年は17年。1/4と考えるには0.25年(あと1年分)閏年が少ないのだ。そこできちんと計算をして、正確な平均値を算出することにした(ぴったり1/4となるよう2041年までサンプル範囲を延長すれば良いだけの話なのだがここだけは律儀に2038年へ執着してみた)。
計算は次の通り。サンプル年数69の内52年が平年で17年が閏年である。
$$ Y_{avg} = \frac{ 52 \times Y_{c} + 17 \times Y_{l} }{ 69 } = 31557286.9565217 $$
ひとつ定数が出てきた。1970年から2038年までの1年毎の平均秒数($ Y_{avg} $)だ。
正規化UNIX時間と月ごとの対応値の算出
さて、ここから計算量はぐぐっと増えるのでコンピュータのチカラを借りる。正規化UNIX時間と、1970年から2038年までの1月から12月までのはじめ(0時0分0秒000)とおわり(23時59分59秒999)に対応する数値の平均値を算出するのだ。JavaScriptでプログラムをゴリゴリと書いて、nodeで実行。結果は2次元配列で表示されるようにした。親配列の要素12個はそれぞれが1月から12月に対応し、小配列にある2つの値はその月の“はじめ”と“おわり”の平均数値である。
計算式はもちろん次のもの。$ U_{time} $に各年各月のはじめとおわりのUNIX時間を入れ、69年分の数値を算出(計1,656データ)、そこから各月のはじめとおわりの平均値を算出している。
$$ \begin{aligned} Y_{f} &= \frac{U_{time}}{Y_{avg}} \\[1.5em] U_{norm} &= Y_{f} - \lfloor Y_{f} \rfloor \end{aligned} $$
そして結果は次の通り。季節は放送用語を元に区分した。
1月 冬まっただなか
- はじめ: 0.00000000000004131
- おわり: 0.08487253133573748
2月 冬おわり
- はじめ: 0.08487256302413952
- おわり: 0.16220601016016947
3月 春はじめ
- はじめ: 0.1622060418485715
- おわり: 0.24702732063817395
4月 春まっさかり
- はじめ: 0.24702735232657608
- おわり: 0.3291057932461683
5月 春おわり
- はじめ: 0.3291058249345702
- おわり: 0.4139800095782059
6月 夏はじめ
- はじめ: 0.41398004126660787
- おわり: 0.4961163479640482
7月 夏まっさかり
- はじめ: 0.49611637965245037
- おわり: 0.5809905642960851
8月 夏おわり
- はじめ: 0.5809905959844873
- おわり: 0.6658647806281227
9月 秋はじめ
- はじめ: 0.6658648123165247
- おわり: 0.748001119013965
10月 秋まっさかり
- はじめ: 0.7480011507023674
- おわり: 0.8329365077397282
11月 秋おわり
- はじめ: 0.8329365394281301
- おわり: 0.915125751979603
12月 冬はじめ
- はじめ: 0.9151257836680052
- おわり: 0.9999999683116402
ご覧のように閏年の影響を受け1月のはじめと12月のおわりは0.0や1.0ぴったりではない(依って“正規化”という言葉も便宜上のものであることがお分かりいただけるだろう)。しかし、かなり近い数値を出力していることは驚くべきだ。
季節別
以下に季節別の数値も記す。ここは例えば春のはじめならば2月おわり($ M_1 $)と3月はじめ($ M_2 $)の平均値を計算することで、それを春はじめとしている。平均値( $ S_{avg} $ )の計算は次の通りである。
$$ S_{avg} = \frac{ M_2 - M_1 }{2} + M_1 $$
春 (3月から5月)
- はじめ: 0.16220602600437 (2月おわりと3月はじめの平均値)
- おわり: 0.413980025422406 (5月おわりと6月はじめの平均値)
夏 (6月から8月)
- はじめ: 0.413980025422406 (5月おわりと6月はじめの平均値)
- おわり: 0.665864796472323 (8月おわりと9月はじめの平均値)
秋 (9月から11月)
- はじめ: 0.665864796472323 (8月おわりと9月はじめの平均値)
- おわり: 0.915125767823804 (11月おわりと12月はじめの平均値)
冬 (12月から翌年2月)
- はじめ: 0.915125767823804 (11月おわりと12月はじめの平均値)
- おわり: 0.16220602600437 (2月おわりと3月はじめの平均値)
double型の有効桁数を小数点以下15桁までとし、そこまで表示してみた。
UNIX時間だけを使った季節の判定
さて、必要な定数は全て揃ったので実践。例えば次のJavaScriptコードで四季を判別できる。
// let time = new Date( 2022, 4 - 1, 30, 12, 25, 30, 0 ).getTime() / 1000
let time = 1651289130 // 2022年4月30日12時25分30秒000
let frac = time / 31557286.9565217
let normTime = frac - Math.floor( frac )
if ( 0.915125767823804 < normTime || normTime <= 0.16220602600437 ) { // 最初に冬を判定しよう!
console.log( '冬' )
}
else if ( 0.16220602600437 < normTime && normTime <= 0.413980025422406 ) {
console.log( '春' )
}
else if ( 0.413980025422406 < normTime && normTime <= 0.665864796472323 ) {
console.log( '夏' )
}
else if ( 0.665864796472323 < normTime && normTime <= 0.915125767823804 ) {
console.log( '秋' )
}
else {
console.log( '摩訶不四季' )
}
See the Pen Untitled by Hideyuki Tabata (@seeker5084) on CodePen.
年をまたぐ都合上、冬をORで最初に判定する点が肝である。あとはANDで春夏秋を判定できる。月ごとの平均値もあるため、それを使えば、UNIX時間だけを使ってある程度の精度で何月かを判定することもできる。
以上がわたしの導き出した結果である。もっと良い考え方もきっとあるはずなので、何か思いのある方はじゃんじゃんコメントを送ってほしい。