【LIVE】全国の日の出日の入り時刻を毎日配信してみた

# Motive

気がついたら家にずっといて夕方になることがよくあるので、明るい時に行動したいっすよね。
てことなので冬季特有のうつ病予防対策として、全国の日の出日の入りの時刻を毎日更新して表示させてみます。

# Method 

日の出日の入りですが, [おはこん番地は!?API](http://labs.bitmeister.jp/ohakon/api/)を使っています。

スクリプトですが、、、

- Qiita記事を作成する

- Qiita記事をapiを使って更新する

の二つに分割し、`cron` を使ってだいたい午前12時くらいにrunさせる設定にします。

# Development

### Qiita記事を作成するスクリプト

```Python
import requests
import datetime
from bs4 import BeautifulSoup
from collections import namedtuple
import asyncio
import os

State = namedtuple('State', ['name', 'id', 'lat', 'lng'])

#dataset
states = [
	State("札幌市",    1, 43.06417,141.34694),
	State("青森市",    2, 40.82444,140.74),
	State("盛岡市",    3, 39.70361,141.1525),
	State("仙台市",    4, 38.26889,140.87194),
	State("秋田市",    5, 39.71861,140.1025),
	State("山形市",    6, 38.24056,140.36333),
	State("福島市",    7, 37.75,140.46778),
	State("水戸市",    8, 36.34139,140.44667),
	State("宇都宮市",   9, 36.56583,139.88361),
	State("前橋市",    10, 36.39111,139.06083),
	State("さいたま市", 11, 35.85694,139.64889),
	State("千葉市",    12, 35.60472,140.12333),
	State("新宿区",    13, 35.68944,139.69167),
	State("横浜市",    14, 35.44778,139.6425),
	State("新潟市",    15, 37.90222,139.02361),
	State("富山市",    16, 36.69528,137.21139),
	State("金沢市",    17, 36.59444,136.62556),
	State("福井市",    18, 36.06528,136.22194),
	State("甲府市",    19, 35.66389,138.56833),
	State("長野市",    20, 36.65139,138.18111),
	State("岐阜市",    21, 35.39111,136.72222),
	State("静岡市",    22, 34.97694,138.38306),
	State("名古屋市",  23, 35.18028,136.90667),
	State("津市",     24, 34.73028,136.50861),
	State("大津市",   25, 35.00444,135.86833),
	State("京都市",   26,35.02139,135.75556),
	State("大阪市",   27,34.68639,135.52),
	State("神戸市",   28,34.69139,135.18306),
	State("奈良市",   29,34.68528,135.83278),
	State("和歌山市",  30, 34.22611,135.1675),
	State("鳥取市",   31,35.50361,134.23833),
	State("松江市",   32,35.47222,133.05056),
	State("岡山市",   33,34.66167,133.935),
	State("広島市",   34,34.39639,132.45944),
	State("山口市",   35,34.18583,131.47139),
	State("徳島市",   36,34.06583,134.55944),
	State("高松市",   37,34.34028,134.04333),
	State("松山市",   38,33.84167,132.76611),
	State("高知市",   39,33.55972,133.53111),
	State("福岡市",   40,33.60639,130.41806),
	State("佐賀市",   41,33.24944,130.29889),
	State("長崎市",   42,32.74472,129.87361),
	State("熊本市",   43,32.78972,130.74167),
	State("大分市",   44,33.23806,131.6125),
	State("宮崎市",   45,31.91111,131.42389),
	State("鹿児島市", 46,31.56028,130.55806),
	State("那覇市",   47,26.2125,127.68111)
]

async def get_time_sun_rise_set(year, month, day, state):
    url = 'http://labs.bitmeister.jp/ohakon/api/?'
    payload = {'mode':'sun_moon_rise_set', 'year':year, 'month':month, 'day':day, 'lat':state.lat, 'lng':state.lng}
    response = requests.get(url, params=payload)
    soup = BeautifulSoup(response.content, "html.parser")
    await asyncio.sleep(1)

    sunrise = datetime.datetime.strptime(soup.sunrise_hm.text,'%H:%M')
    sunset = datetime.datetime.strptime(soup.sunset_hm.text,'%H:%M')

    diff_seconds = (sunset - sunrise).total_seconds()
    diff_hours = int(diff_seconds // (60 * 60))
    diff_minutes = int((diff_seconds - diff_hours * (60 * 60)) / 60)

    return state.name, soup.sunrise_hm.text, soup.sunset_hm.text, "{:02d}:{:02d}".format(diff_hours, diff_minutes)

def write_session(foutobj, path):
	with open(path, "r", encoding='utf-8') as fin:
		foutobj.write(fin.read())	


if __name__ == "__main__":
	now = datetime.datetime.now()
	output = "./article.md"
	base_folder = "./templates/"

	loop = asyncio.get_event_loop()
	cors = [get_time_sun_rise_set(now.year, now.month, now.day, s) for s in states]
	result = loop.run_until_complete(
			asyncio.gather(*cors)
		)
	build_parts = ["motive.md", "method.md", "development.md", "contents.md", "future.md", "reference.md"]

	with open("./templates/contents.md", "w", encoding='utf-8') as fout:
		fout.write("# Sunrize/Sunset\n\n")
		fout.write("{}\n\n".format(datetime.datetime.now().strftime("【%Y/%m/%d %H:%M時更新】")))
		fout.write("|city|sunrize|sunset|hours of sunlight|\n")
		fout.write("|:--|:--|:--|:--|\n")
		for r in result:
			fout.write("|{}|{}|{}|{}|\n".format(*r))
		fout.write("\n\n")

	with open(output, "w", encoding='utf-8') as fout:
		for p in build_parts:
			write_session(fout, os.path.join(base_folder, p))

```

`get_time_sun_rise_set()` でAPIを使って日の出日の入りの時間を出力しています。リクエストを設定するときは `payload = {'mode':'sun_moon_rise_set', 'year':year, 'month':month, 'day':day, 'lat':state.lat, 'lng':state.lng}` の通り日付と緯度経度のパラメータを用います。

あと、それぞれのapi取得を安定的に取得するために念のため 非同期処理 `asyncio` を使っています。

```Python
	loop = asyncio.get_event_loop()
	cors = [get_time_sun_rise_set(now.year, now.month, now.day, s) for s in states]
	result = loop.run_until_complete(
			asyncio.gather(*cors)
		)
```

また、あとあとで編集しやすいために各セクションの文章を分割して、最終的に集約させています。
単純にファイルを読み書きするときは `open(path, 'r')` でよかったのですが `cron` で実行する場合は文字コードでエラーになります。
それで 引数に明示的に `encoding='utf-8'` にする必要があります。([ファイルI/O由来でUnicodeDecodeErrorが出た時の対処法](https://qiita.com/satsukiya/items/db8f1c35f86fc26f3fb4) を参照)


```Python
	build_parts = ["motive.md", "method.md", "development.md", "contents.md", "future.md", "reference.md"]
	with open(output, "w", encoding='utf-8') as fout:
		for p in build_parts:
			write_session(fout, os.path.join(base_folder, p))
```




### Qiita記事をapiを使って更新する

```Python
import json
import requests

if __name__ == "__main__":
	BASE_URL = "https://qiita.com/api/v2/items/"
	TOKEN = "--アクセストークン--"
	headers = {"Authorization": f"Bearer {TOKEN}", "Content-Type":"application/json"}

	sentence = ""
	with open("article.md", "r", encoding='utf-8') as fin:
		sentence = fin.read()

	contents = {"title": "【LIVE】全国の日の出日の入り時刻を毎日配信してみた",
				"tags": [{"name": "Python"},
						{"name":"cron"},
						{"name":"asyncio"},
						{"name":"QiitaAPI"},
						{"name":"api"}],
				"body": sentence,
				"id":"ada82beb3b747b99a05e"
				}

	res = requests.patch(BASE_URL + contents["id"], headers=headers, json=contents)
	print(res)
```

QiitaAPIを使うには[アクセストークンの発行](https://qiita.com/settings/tokens/new)が必要で、`requests`でAPIをリクエストするときに必要です。
`requests.patch({コンテンツのURL}, {ヘッダー}, {コンテンツの内容})` で内容を更新することができます。


# Sunrize/Sunset

【2023/10/01 01:00時更新】

|city|sunrize|sunset|hours of sunlight|
|:--|:--|:--|:--|
|札幌市|5:30|17:17|11:47|
|青森市|5:32|17:20|11:48|
|盛岡市|5:30|17:19|11:49|
|仙台市|5:31|17:21|11:50|
|秋田市|5:34|17:23|11:49|
|山形市|5:33|17:23|11:50|
|福島市|5:32|17:23|11:51|
|水戸市|5:32|17:23|11:51|
|宇都宮市|5:34|17:25|11:51|
|前橋市|5:38|17:29|11:51|
|さいたま市|5:35|17:26|11:51|
|千葉市|5:33|17:24|11:51|
|新宿区|5:35|17:26|11:51|
|横浜市|5:35|17:26|11:51|
|新潟市|5:38|17:28|11:50|
|富山市|5:45|17:36|11:51|
|金沢市|5:47|17:38|11:51|
|福井市|5:49|17:40|11:51|
|甲府市|5:39|17:31|11:52|
|長野市|5:41|17:32|11:51|
|岐阜市|5:47|17:38|11:51|
|静岡市|5:40|17:32|11:52|
|名古屋市|5:46|17:37|11:51|
|津市|5:47|17:39|11:52|
|大津市|5:50|17:42|11:52|
|京都市|5:51|17:42|11:51|
|大阪市|5:51|17:43|11:52|
|神戸市|5:53|17:44|11:51|
|奈良市|5:50|17:42|11:52|
|和歌山市|5:53|17:45|11:52|
|鳥取市|5:57|17:48|11:51|
|松江市|6:01|17:53|11:52|
|岡山市|5:58|17:49|11:51|
|広島市|6:04|17:55|11:51|
|山口市|6:07|17:59|11:52|
|徳島市|5:55|17:47|11:52|
|高松市|5:57|17:49|11:52|
|松山市|6:02|17:54|11:52|
|高知市|5:59|17:51|11:52|
|福岡市|6:12|18:04|11:52|
|佐賀市|6:12|18:04|11:52|
|長崎市|6:13|18:06|11:53|
|熊本市|6:10|18:03|11:53|
|大分市|6:07|17:59|11:52|
|宮崎市|6:07|18:00|11:53|
|鹿児島市|6:10|18:04|11:54|
|那覇市|6:21|18:17|11:56|


# Future

全国の日の出日の入り時間を出力してみると時間差が約30分から約60分くらいあり、日照時間は約30分から60分くらいの差があることがわかります。

# Reference

- [おはこん番地は!?API](http://labs.bitmeister.jp/ohakon/index.cgi)
- [QiitaAPI - 投稿](https://qiita.com/api/v2/docs#%E6%8A%95%E7%A8%BF)
- [Pythonでcurlコマンドと同等の処理を実行する方法](https://intellectual-curiosity.tokyo/2019/08/31/python%E3%81%A7curl%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%A8%E5%90%8C%E7%AD%89%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)
- [Qiita API で投稿を自動化する](https://qiita.com/tomowarkar/items/11488231c6d22d960323)