Minecraft: Java Edition で現実世界の日付と時刻を取得する

## 始める前に

この記事では、[プレイヤーの頭](https://minecraft.wiki/w/Player_Head)を使用して Minecraft: Java Edition で現実世界の日付と時刻を取得する関数を作成します。取得した結果は整数としてストレージに保存されます。

検証済みバージョンは 1.21.4 です。

### 前提条件

- 基本的な数学の知識を理解していること。
- `data`、`scoreboard`、マクロなど、コマンドおよび関数に精通していること。
- 「[Base64](https://w.wiki/5Nqn) 形式にエンコードされた文字列」をテキストにデコードする方法を熟知していること。

## プレイヤーの頭から UNIX 時刻を取得する

コマンド `give @s minecraft:player_head[minecraft:profile= "MHF_Question"] 1` を使用して「プレイヤーの頭」を入手すると、次のようなアイテム データが保存されます。

![テキストには、「{"minecraft:profile": {name: "MHF_Question", properties: \[{name: "textures", signature: "g99WXvXS5ozDNzzfdRDtSZ9e4Y4gv3rUgOhsInIF+tJDbM7vv1d3u/kph8pUOgedSPeLW8pk+663zEsUljHUx8UcUbK6xALH98tyu7CDDKnVmRyd3+wmjxD4b4JSMl4jiAVlPovK2aZLx6vpRe5/3kKBtriRKIFeUE15FMURvOAmeuX8Tq4ZkIUbhZRvTuH8+HZz5tcqAidlnBsaPym4G4sGfskv81UBWrx0jdP3VamlGZKhGKTl/ukQ5efVJMbaBlVtwWwMgl9xC5ELcCUCNewgucrMBxV+vtLxcrpZQl64OTJAAEabVCfgFibCwtOzfOOGf08UJdHQY86sNERdsp6ilVqoNSI+lV4DIPWb1zc7yJMYib5tgpU6xhMhuaDVEiJfOMfdqUmnBdsbbfD5KejAZx7r++M6U0fyU26/5V7QxOUodrRDOzJyqSSctBjUaanxqgMBDHEei7IY55g1WqLAZkiHb95kj0Jg85jJ/+iBRPkDP+jf6ULzw1loaWFioC8AjjVlboZLCQkFGqW1hiL8bDvbt04HLBwRNlD/bHc4hx4JXtGZ1yMAwRw/DgzmfSA7i0GNhaeYuN1E/e/4luHXMyyRah2oIJr4LPGRgTAafSrfSakcrnxshV5I+aIeyI0qAMsmcUbI79ikF3atNsv+GfcA3wnJAbYW0PDjR28=", value: "ewogICJ0aW1lc3RhbXAiIDogMTQzMjk4NDk4MjAwMCwKICAicHJvZmlsZUlkIiA6ICI2MDZlMmZmMGVkNzc0ODQyOWQ2Y2UxZDMzMjFjNzgzOCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNSEZfUXVlc3Rpb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDM0ZTA2M2NhZmI0NjdhNWM4ZGU0M2VjNzg2MTkzOTlmMzY5ZjRhNTI0MzRkYTgwMTdhOTgzY2RkOTI1MTZhMCIKICAgIH0KICB9Cn0="}\], id: \[I; 1617833968, -310949822, -1653808685, 840726584\]}}」と書かれています。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/00b79221-b6e4-5072-0318-1da6479b05d8.png)

このアイテム データの `value` には、スキンのテクスチャ データが「Base64 形式にエンコードされた文字列」として保存されます。

---

この「エンコードされた文字列」をデコードすると、次の文字列を取得できます。

![テキストには、「{"timestamp" : 1432984982000, "profileId" : "606e2ff0ed7748429d6ce1d3321c7838", "profileName" : "MHF_Question", "textures" : {"SKIN" : {"url" : "http://textures.minecraft.net/texture/d34e063cafb467a5c8de43ec78619399f369f4a52434da8017a983cdd92516a0"}}}」と書かれています。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/1185f2f7-be35-83ee-f2e1-214cc89e2608.png)

`timestamp` には、「テクスチャ データを取得した日時の UNIX 時刻」が保存されます。

UNIX 時刻とは、1970年1月1日 00:00:00（UTC）からの経過秒数です。つまり、この値を取得してしまえば、現実世界の日時を算出することができます。

### プレイヤーの頭から「エンコードされた文字列」を取得する

プレイヤーの頭をアイテムとして召喚して、「エンコードされた文字列」を取得します。

まずはプレイヤーの頭をアイテムとして召喚します。

<details>
<summary><b>bring.mcfunction</b></summary>

```coffee:bring.mcfunction
# イニシャライズする
data remove storage current_time_bringer:current_time current_time
data remove storage current_time_bringer:current_time data
data modify storage current_time_bringer:current_time data.binary set value ""
data modify storage current_time_bringer:current_time data.unix set value ""
# アイテムを召喚する
summon minecraft:item ~ ~ ~ {\
  Item: {count: 1, components: { "minecraft:profile": {"name": "MHF_Question"}}, id: "minecraft:player_head"},\
  PickupDelay: 32767, Tags: ["tag.current_time_bringer.item"]\
}
# 次の処理を開始する
function current_time_bringer:fetch_unix/repeat_detection
```

</details>

---

「エンコードされた文字列」が保存されるのは約 1 秒後です。そのため、「エンコードされた文字列」が保存されるまで再帰処理を行う必要があります。

![日時を取得するプロセスを示すフローチャート。チャートはアクション「プレイヤーの頭をアイテムとして召喚」から開始します。次に、「プレイヤーの頭に『エンコードされた文字列』が保存されているか」と質問します。質問の回答が「はい」の場合は、プロセスは下に進みます。質問の回答が「いいえ」の場合は、1 ティック後に 1 つ前の質問にループバックします。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/47e011db-6644-c408-aa43-c7932293fe0f.png)

「エンコードされた文字列」が保存されていない場合は、`components."minecraft:profile".id` というデータがありません。そのデータがない場合は 1 ティック後に同じ関数を実行し、そのデータがある場合は次の処理を開始します。

<details>
<summary><b>fetch_unix/repeat_detection.mcfunction</b></summary>

```coffee:fetch_unix/repeat_detection.mcfunction
# 「プレイヤーの頭の情報」が保存されている場合は次の処理を開始する
execute if entity @n[tag= tag.current_time_bringer.item] if data entity @n[tag= tag.current_time_bringer.item] Item.components."minecraft:profile".id if data entity @n[tag= tag.current_time_bringer.item] Item.components."minecraft:profile".properties[].value run function current_time_bringer:fetch_unix/run
# 「プレイヤーの頭の情報」が保存されていない場合は 1 ティック後に同じ関数を実行する
execute if entity @n[tag= tag.current_time_bringer.item] unless data entity @n[tag= tag.current_time_bringer.item] Item.components."minecraft:profile".id run schedule function current_time_bringer:fetch_unix/repeat_detection 1t append
```

</details>

次に、アイテムから「エンコードされた文字列」を 14 文字に切り取って取得し、アイテムをキルします。

<details>
<summary><b>fetch_unix/run.mcfunction</b></summary>

```coffee:fetch_unix/run.mcfunction
# 「エンコードされた文字列」を 14 文字に切り取る
data modify storage current_time_bringer:current_time data.raw set from entity @n[tag= tag.current_time_bringer.item] Item.components."minecraft:profile".properties[].value
data modify storage current_time_bringer:current_time data.raw set string storage current_time_bringer:current_time data.raw 24 38
# アイテムをキルする
kill @e[tag= tag.current_time_bringer.item]
# 次の処理を開始する
function current_time_bringer:fetch_unix/repeat_raw_separation
```

</details>

ここで「エンコードされた文字列」を 14 文字に切り取っているのは、こっちの方が効率が良さそうだと思ったからです。

![テキストには、「『エンコードされた文字列』の全部: ewogICJ0aW1lc3RhbXAiIDogMTQzMjk4NDk4MjAwMCwKICAicHJvZmlsZUlkIiA6ICI2MDZlMmZmMGVkNzc0ODQyOWQ2Y2UxZDMzMjFjNzgzOCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNSEZfUXVlc3Rpb24iLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDM0ZTA2M2NhZmI0NjdhNWM4ZGU0M2VjNzg2MTkzOTlmMzY5ZjRhNTI0MzRkYTgwMTdhOTgzY2RkOTI1MTZhMCIKICAgIH0KICB9Cn0=; 『エンコードされた文字列』の一部: MTQzMjk4NDk4Mj; 全部をデコードした文字列: {"timestamp" : 1432984982000, "profileId" : "606e2ff0ed7748429d6ce1d3321c7838", "profileName" : "MHF_Question", "textures" : {"SKIN" : {"url" : "http://textures.minecraft.net/texture/d34e063cafb467a5c8de43ec78619399f369f4a52434da8017a983cdd92516a0"}}}; 『000』はミリ秒。」と書かれています。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/6d4d6ec9-3507-dc19-fa8a-0f9c244ae232.png)

### 「エンコードされた文字列」をデコードする

取得した「エンコードされた文字列」をデコードします。

まずは、Base64 アルファベットに基づいて、文字をバイナリに変換します。

![Base64 形式の文字列をバイナリに変換するプロセスを示すフローチャート。チャートは「『M』TQzMjk4NDk4Mj」から開始します。次に、「TQzMjk4NDk4Mj > 001101」と 1 文字がバイナリに変換されます。次に、「『T』QzMjk4NDk4Mj > 001101」と 1 文字が選択されます。次に、「QzMjk4NDk4Mj > 001101010011」と 1 文字がバイナリに変換されます。次に、「『Q』zMjk4NDk4Mj > 001101010011」と 1 文字が選択されます。次に、「zMjk4NDk4Mj > 001101010011010000」と 1 文字がバイナリに変換され、プロセスは下に進みます。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/e10b7c29-5fc2-5fe3-8ead-744927cb5dfe.png)

<details>
<summary><b>fetch_unix/repeat_raw_separation.mcfunction</b></summary>

```coffee:fetch_unix/repeat_raw_separation.mcfunction
# 「エンコードされた文字列」から 1 文字を切り離す
data modify storage current_time_bringer:current_time data.separated_raw set string storage current_time_bringer:current_time data.raw 0 1
data modify storage current_time_bringer:current_time data.raw set string storage current_time_bringer:current_time data.raw 1
# 切り離した文字をバイナリに変換する関数を実行する
function current_time_bringer:fetch_unix/convert_to_binary
# バイナリを結合する関数を実行する
function current_time_bringer:fetch_unix/concatenate_strings_to_binary with storage current_time_bringer:current_time data
# 「エンコードされた文字列」が残っていない場合は次の処理を開始する
execute if data storage current_time_bringer:current_time data{raw: ""} run function current_time_bringer:fetch_unix/process_before_converting_to_ascii
# 「エンコードされた文字列」が残っている場合は同じ関数を繰り返す
execute unless data storage current_time_bringer:current_time data{raw: ""} if data storage current_time_bringer:current_time data.raw run function current_time_bringer:fetch_unix/repeat_raw_separation
```

</details>

<details>
<summary><b>fetch_unix/convert_to_binary.mcfunction</b></summary>

```coffee:fetch_unix/convert_to_binary.mcfunction
# 切り離した文字が「A」の場合はバイナリ「000000」を返戻する
execute if data storage current_time_bringer:current_time data{separated_raw: "A"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "000000"
# 切り離した文字が「B」の場合はバイナリ「000001」を返戻する
execute if data storage current_time_bringer:current_time data{separated_raw: "B"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "000001"

...

# 切り離した文字が「/」の場合はバイナリ「111111」を返戻する
execute if data storage current_time_bringer:current_time data{separated_raw: "/"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "111111"
# 切り離した文字が「=」の場合はバイナリを返戻しない
execute if data storage current_time_bringer:current_time data{separated_raw: "="} run data modify storage current_time_bringer:current_time data.string_for_appending set value ""

data remove storage current_time_bringer:current_time data.separated_raw
```

</details>

<details>
<summary><b>fetch_unix/concatenate_strings_to_binary.mcfunction</b></summary>

```coffee:fetch_unix/concatenate_strings_to_binary.mcfunction
# バイナリを結合する
$data modify storage current_time_bringer:current_time data.binary set value "$(binary)$(string_for_appending)"

data remove storage current_time_bringer:current_time data.string_for_appending
```

</details>

<details>
<summary><b>fetch_unix/process_before_converting_to_ascii.mcfunction</b></summary>

```coffee:fetch_unix/process_before_converting_to_ascii.mcfunction
data remove storage current_time_bringer:current_time data.raw
# 次の処理を開始する
function current_time_bringer:fetch_unix/repeat_binary_separation
```

</details>

---

次に、バイナリを ASCII に変換します。

![バイナリを ASCII に変換するプロセスを示すフローチャート。チャートは「『00110001』001101000011001100110010000110001...」から開始します。次に、「001101000011001100110010000110001... > 1」と 8 文字が ASCII に変換されます。次に、「『00110100』0011001100110010000110001... > 1」と 8 文字が選択されます。次に、「0011001100110010000110001... > 14」と 8 文字が ASCII に変換されます。次に、「『00110011』00110010000110001... > 14」と 8 文字が選択されます。次に、「00110010000110001... > 143」と 8 文字が ASCII に変換され、プロセスは下に進みます。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/e20b404e-5f03-60fe-e47b-561eccaf4b8b.png)

<details>
<summary><b>fetch_unix/repeat_binary_separation.mcfunction</b></summary>

```coffee:fetch_unix/repeat_binary_separation.mcfunction
# バイナリから 8 文字を切り離す
data modify storage current_time_bringer:current_time data.separated_binary set string storage current_time_bringer:current_time data.binary 0 8
data modify storage current_time_bringer:current_time data.binary set string storage current_time_bringer:current_time data.binary 4
data modify storage current_time_bringer:current_time data.binary set string storage current_time_bringer:current_time data.binary 4
# バイナリを ASCII に変換する関数を実行する
function current_time_bringer:fetch_unix/convert_to_ascii
# ASCII を結合する関数を実行する
function current_time_bringer:fetch_unix/concatenate_strings_to_unix with storage current_time_bringer:current_time data
# バイナリが残っていない場合は次の処理を開始する
execute if data storage current_time_bringer:current_time data{binary: ""} run function current_time_bringer:fetch_unix/process_before_converting_to_date_time_components
# バイナリが残っている場合は同じ関数を繰り返す
execute unless data storage current_time_bringer:current_time data{binary: ""} if data storage current_time_bringer:current_time data.binary run function current_time_bringer:fetch_unix/repeat_binary_separation
```

</details>

<details>
<summary><b>fetch_unix/convert_to_ascii.mcfunction</b></summary>

```coffee:fetch_unix/convert_to_ascii.mcfunction
# バイナリが「00101101」の場合は ASCII「-」を返戻する
execute if data storage current_time_bringer:current_time data{separated_binary: "00101101"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "-"
# バイナリが「00110000」の場合は ASCII「0」を返戻する
execute if data storage current_time_bringer:current_time data{separated_binary: "00110000"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "0"

...

# バイナリが「00111000」の場合は ASCII「8」を返戻する
execute if data storage current_time_bringer:current_time data{separated_binary: "00111000"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "8"
# バイナリが「00111001」の場合は ASCII「9」を返戻する
execute if data storage current_time_bringer:current_time data{separated_binary: "00111001"} run data modify storage current_time_bringer:current_time data.string_for_appending set value "9"

data remove storage current_time_bringer:current_time data.separated_binary
```

</details>

<details>
<summary><b>fetch_unix/concatenate_strings_to_unix.mcfunction</b></summary>

```coffee:fetch_unix/concatenate_strings_to_unix.mcfunction
# ASCII を結合する
$data modify storage current_time_bringer:current_time data.unix set value "$(unix)$(string_for_appending)"

data remove storage current_time_bringer:current_time data.string_for_appending
```

</details>

<details>
<summary><b>fetch_unix/process_before_converting_to_date_time_components.mcfunction</b></summary>

```coffee:fetch_unix/process_before_converting_to_date_time_components.mcfunction
data remove storage current_time_bringer:current_time data.binary
# スコアボード オブジェクトを作成する
scoreboard objectives add objective.current_time_bringer.temporary_value dummy {"fallback": "Temporary Value", "translate": "objective.current_time_bringer.temporary_value", "type": "translatable"}
# 次の処理を開始する
function current_time_bringer:convert_to_date_time_components/tick with storage current_time_bringer:current_time data
# スコアボード オブジェクトを削除する
scoreboard objectives remove objective.current_time_bringer.temporary_value
```

</details>

---

UNIX 時刻が文字列としてストレージ `current_time_bringer:current_time` の `data.unix` に保存されます。

![テキストには、「{data: {unix: "1432984982"}}」と書かれています。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/82fc9ed1-7c79-d2e4-e7d4-926cab245653.png)

## UNIX 時刻を日時の構成要素に変換する

UNIX 時刻を日時の構成要素に変換するには、次のプロセスを使用します。

![UNIX 時刻を日時の構成要素に変換するプロセスを示すフローチャート。チャートは変数「days」を「UNIX 時刻 / (60 (秒) * 60 (分) * 24 (時間))」に、変数「remaining_seconds」を「UNIX 時刻 % (60 (秒) * 60 (分) * 24 (時間))」に、変数「year」を「1970」に定義するアクションから開始します。次に、「days が 365 以上であるか」と 1 件目の質問をします。1 件目の質問の回答が「はい」の場合は、「year が 4 の倍数であり 100 の倍数でないか、または 400 の倍数であるか」と 2 件目の質問をします。2 件目の質問の回答が「はい」の場合は、「days が 366 以上であるか」と 3 件目の質問をします。3 件目の質問の回答が「はい」の場合は、変数「days」を「days - 366」に、変数「year」を「year + 1」に定義し、1 件目の質問にループバックします。2 件目の質問の回答が「いいえ」の場合は、変数「days」を「days - 365」に、変数「year」を「year + 1」に定義し、1 件目の質問にループバックします。1 件目と 3 件目の質問の回答が「いいえ」の場合は、変数「month」を「1」に、変数「days_in_month」を「\[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31\]」に定義します。次に、「year が 4 の倍数であり 100 の倍数でないか、または 400 の倍数であるか」と 4 件目の質問をします。4 件目の質問の回答が「はい」の場合は、変数「days_in_month\[1\]」を「days_in_month\[1\] + 1」に定義します。4 件目の質問の回答が「いいえ」の場合は、次のステップに進みます。次に、「days が days_in_month\[month -1\] 以上であるか」と 5 件目の質問をします。5 件目の質問の回答が「はい」の場合は、変数「days」を「days - days_in_month\[month - 1\]」に、変数「month」を「month + 1」に定義し、5 件目の質問にループバックします。5 件目の質問の回答が「いいえ」の場合は、変数「days」を「days + 1」に、変数「hour」を「remaining_seconds / (60 (秒) * 60 (分))」に、変数「remaining_seconds」を「remaining_seconds % (60 (秒) * 60 (分))」に、変数「minute」を「remaining_seconds / 60 (秒)」に、変数「second」を「remaining_seconds % 60 (秒)」に定義します。最後に、「year 年 month 月 day 日 hour 時 minute 分 second 秒」と出力します。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768584/0b0d09b9-f8c7-9bb3-85c3-9777554989e1.png)

<details>
<summary><b>convert_to_date_time_components/tick.mcfunction</b></summary>

```coffee:convert_to_date_time_components/tick.mcfunction
# UNIX 時刻を代入する
$scoreboard players set target.current_time_bringer.unix objective.current_time_bringer.temporary_value $(unix)
# 変数を定義する
function current_time_bringer:convert_to_date_time_components/define/days_in_month
scoreboard players set target.current_time_bringer.seconds_in_day objective.current_time_bringer.temporary_value 86400
# UNIX 時刻から日数と残りの秒数を算出する関数を実行する
function current_time_bringer:convert_to_date_time_components/define/days_and_remaining_seconds
# 日数から現在の年を算出する
scoreboard players set target.current_time_bringer.year objective.current_time_bringer.temporary_value 1970
scoreboard players set target.current_time_bringer.days_in_year objective.current_time_bringer.temporary_value 365
scoreboard players set target.current_time_bringer.leap_year_days objective.current_time_bringer.temporary_value 366
execute if score target.current_time_bringer.days objective.current_time_bringer.temporary_value >= target.current_time_bringer.days_in_year objective.current_time_bringer.temporary_value run function current_time_bringer:convert_to_date_time_components/convert_days_to_year_and_month/tick
# 月の日数を考慮して現在の月を算出する
scoreboard players set target.current_time_bringer.month objective.current_time_bringer.temporary_value 1
function current_time_bringer:convert_to_date_time_components/define/is_leap_year
scoreboard players operation target.current_time_bringer.days_in_month.2 objective.current_time_bringer.temporary_value += target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value
execute store result storage current_time_bringer:current_time convert_to_date_time_components.month int 1 run scoreboard players get target.current_time_bringer.month objective.current_time_bringer.temporary_value
function current_time_bringer:convert_to_date_time_components/calculate_taking_into_account_days_in_month/tick with storage current_time_bringer:current_time convert_to_date_time_components
data remove storage current_time_bringer:current_time convert_to_date_time_components
# 現在の日を算出する
scoreboard players operation target.current_time_bringer.day objective.current_time_bringer.temporary_value = target.current_time_bringer.days objective.current_time_bringer.temporary_value
scoreboard players add target.current_time_bringer.day objective.current_time_bringer.temporary_value 1
# 残りの秒数から現在の時、分、秒を算出する
scoreboard players set target.current_time_bringer.seconds_in_hour objective.current_time_bringer.temporary_value 3600
scoreboard players operation target.current_time_bringer.hour objective.current_time_bringer.temporary_value = target.current_time_bringer.remaining_seconds objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.hour objective.current_time_bringer.temporary_value /= target.current_time_bringer.seconds_in_hour objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.remaining_seconds objective.current_time_bringer.temporary_value %= target.current_time_bringer.seconds_in_hour objective.current_time_bringer.temporary_value
scoreboard players set target.current_time_bringer.seconds_in_minute objective.current_time_bringer.temporary_value 60
scoreboard players operation target.current_time_bringer.minute objective.current_time_bringer.temporary_value = target.current_time_bringer.remaining_seconds objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.minute objective.current_time_bringer.temporary_value /= target.current_time_bringer.seconds_in_minute objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.second objective.current_time_bringer.temporary_value = target.current_time_bringer.remaining_seconds objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.second objective.current_time_bringer.temporary_value %= target.current_time_bringer.seconds_in_minute objective.current_time_bringer.temporary_value
# 算出した現在の年、月、日、時、分、秒をストレージに保存する
execute store result storage current_time_bringer:current_time current_time.year int 1 run scoreboard players get target.current_time_bringer.year objective.current_time_bringer.temporary_value
execute store result storage current_time_bringer:current_time current_time.month int 1 run scoreboard players get target.current_time_bringer.month objective.current_time_bringer.temporary_value
execute store result storage current_time_bringer:current_time current_time.day int 1 run scoreboard players get target.current_time_bringer.day objective.current_time_bringer.temporary_value
execute store result storage current_time_bringer:current_time current_time.hour int 1 run scoreboard players get target.current_time_bringer.hour objective.current_time_bringer.temporary_value
execute store result storage current_time_bringer:current_time current_time.minute int 1 run scoreboard players get target.current_time_bringer.minute objective.current_time_bringer.temporary_value
execute store result storage current_time_bringer:current_time current_time.second int 1 run scoreboard players get target.current_time_bringer.second objective.current_time_bringer.temporary_value

data remove storage current_time_bringer:current_time data
```

</details>

<details>
<summary><b>convert_to_date_time_components/define/days_in_month.mcfunction</b></summary>

```coffee:convert_to_date_time_components/define/days_in_month.mcfunction
# 1 月の日数を定義する
scoreboard players set target.current_time_bringer.days_in_month.1 objective.current_time_bringer.temporary_value 31
# 2 月の日数を定義する
scoreboard players set target.current_time_bringer.days_in_month.2 objective.current_time_bringer.temporary_value 28

...

# 11 月の日数を定義する
scoreboard players set target.current_time_bringer.days_in_month.11 objective.current_time_bringer.temporary_value 30
# 12 月の日数を定義する
scoreboard players set target.current_time_bringer.days_in_month.12 objective.current_time_bringer.temporary_value 31
```

</details>

<details>
<summary><b>convert_to_date_time_components/define/days_and_remaining_seconds.mcfunction</b></summary>

```coffee:convert_to_date_time_components/define/days_and_remaining_seconds.mcfunction
# 日数と残りの秒数を算出する
scoreboard players operation target.current_time_bringer.days objective.current_time_bringer.temporary_value = target.current_time_bringer.unix objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.remaining_seconds objective.current_time_bringer.temporary_value = target.current_time_bringer.unix objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.days objective.current_time_bringer.temporary_value /= target.current_time_bringer.seconds_in_day objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.remaining_seconds objective.current_time_bringer.temporary_value %= target.current_time_bringer.seconds_in_day objective.current_time_bringer.temporary_value
```

</details>

<details>
<summary><b>convert_to_date_time_components/define/is_leap_year.mcfunction</b></summary>

```coffee:convert_to_date_time_components/define/is_leap_year.mcfunction
# うるう年であるか確認する

scoreboard players set target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value 0

scoreboard players operation target.current_time_bringer.year_00 objective.current_time_bringer.temporary_value = target.current_time_bringer.year objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.year_01 objective.current_time_bringer.temporary_value = target.current_time_bringer.year objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.year_02 objective.current_time_bringer.temporary_value = target.current_time_bringer.year objective.current_time_bringer.temporary_value

scoreboard players set target.current_time_bringer.4 objective.current_time_bringer.temporary_value 4
scoreboard players set target.current_time_bringer.100 objective.current_time_bringer.temporary_value 100
scoreboard players set target.current_time_bringer.400 objective.current_time_bringer.temporary_value 400

scoreboard players operation target.current_time_bringer.year_00 objective.current_time_bringer.temporary_value %= target.current_time_bringer.4 objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.year_01 objective.current_time_bringer.temporary_value %= target.current_time_bringer.100 objective.current_time_bringer.temporary_value
scoreboard players operation target.current_time_bringer.year_02 objective.current_time_bringer.temporary_value %= target.current_time_bringer.400 objective.current_time_bringer.temporary_value

execute if score target.current_time_bringer.year_00 objective.current_time_bringer.temporary_value matches 0 unless score target.current_time_bringer.year_01 objective.current_time_bringer.temporary_value matches 0 run scoreboard players set target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value 1
execute unless score target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value matches 1 if score target.current_time_bringer.year_02 objective.current_time_bringer.temporary_value matches 0 run scoreboard players set target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value 1
```

</details>

<details>
<summary><b>convert_to_date_time_components/convert_days_to_year_and_month/tick.mcfunction</b></summary>

```coffee:convert_to_date_time_components/convert_days_to_year_and_month/tick.mcfunction
# うるう年であるか確認する
function current_time_bringer:convert_to_date_time_components/define/is_leap_year
# うるう年であり日数が 366 以上である場合は次の関数を実行する
execute if score target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value matches 1 if score target.current_time_bringer.days objective.current_time_bringer.temporary_value >= target.current_time_bringer.leap_year_days objective.current_time_bringer.temporary_value run function current_time_bringer:convert_to_date_time_components/convert_days_to_year_and_month/is_leap_year
# うるう年であり日数が 366 以上ではない場合はループを終了する
execute if score target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value matches 1 unless score target.current_time_bringer.days objective.current_time_bringer.temporary_value >= target.current_time_bringer.leap_year_days objective.current_time_bringer.temporary_value run scoreboard players set target.current_time_bringer.stop_loop objective.current_time_bringer.temporary_value 1
# うるう年ではない場合は次の関数を実行する
execute unless score target.current_time_bringer.is_leap_year objective.current_time_bringer.temporary_value matches 1 run function current_time_bringer:convert_to_date_time_components/convert_days_to_year_and_month/is_not_leap_year
# 日数が 365 以上である場合は同じ関数を繰り返す
execute unless score target.current_time_bringer.stop_loop objective.current_time_bringer.temporary_value matches 1 if score target.current_time_bringer.days objective.current_time_bringer.temporary_value >= target.current_time_bringer.days_in_year objective.current_time_bringer.temporary_value run function current_time_bringer:convert_to_date_time_components/convert_days_to_year_and_month/tick
```

</details>

<details>
<summary><b>convert_to_date_time_components/convert_days_to_year_and_month/is_leap_year.mcfunction</b></summary>

```coffee:convert_to_date_time_components/convert_days_to_year_and_month/is_leap_year.mcfunction
# 日数から 366 を減算する
scoreboard players remove target.current_time_bringer.days objective.current_time_bringer.temporary_value 366
# 年に 1 を加算する
scoreboard players add target.current_time_bringer.year objective.current_time_bringer.temporary_value 1
```

</details>

<details>
<summary><b>convert_to_date_time_components/convert_days_to_year_and_month/is_not_leap_year.mcfunction</b></summary>

```coffee:convert_to_date_time_components/convert_days_to_year_and_month/is_not_leap_year.mcfunction
# 日数から 365 を減算する
scoreboard players remove target.current_time_bringer.days objective.current_time_bringer.temporary_value 365
# 年に 1 を加算する
scoreboard players add target.current_time_bringer.year objective.current_time_bringer.temporary_value 1
```

</details>

<details>
<summary><b>convert_to_date_time_components/calculate_taking_into_account_days_in_month/tick.mcfunction</b></summary>

```coffee:convert_to_date_time_components/calculate_taking_into_account_days_in_month/tick.mcfunction
# 日数が月の日数以上である場合は次の関数を実行する
$execute if score target.current_time_bringer.days objective.current_time_bringer.temporary_value >= target.current_time_bringer.days_in_month.$(month) objective.current_time_bringer.temporary_value run function current_time_bringer:convert_to_date_time_components/calculate_taking_into_account_days_in_month/days_is_greater_than_days_in_month with storage current_time_bringer:current_time convert_to_date_time_components
```

</details>

<details>
<summary><b>convert_to_date_time_components/calculate_taking_into_account_days_in_month/days_is_greater_than_days_in_month.mcfunction</b></summary>

```coffee:convert_to_date_time_components/calculate_taking_into_account_days_in_month/days_is_greater_than_days_in_month.mcfunction
# 日数から月の日数を減算する
$scoreboard players operation target.current_time_bringer.days objective.current_time_bringer.temporary_value -= target.current_time_bringer.days_in_month.$(month) objective.current_time_bringer.temporary_value
# 月に 1 を加算する
scoreboard players add target.current_time_bringer.month objective.current_time_bringer.temporary_value 1
# 日数が月の日数以上であるか確認する
execute store result storage current_time_bringer:current_time convert_to_date_time_components.month int 1 run scoreboard players get target.current_time_bringer.month objective.current_time_bringer.temporary_value
function current_time_bringer:convert_to_date_time_components/calculate_taking_into_account_days_in_month/tick with storage current_time_bringer:current_time convert_to_date_time_components
```

</details>

## 完成したコード

この記事で完成したコードは、[GitHub で確認](https://github.com/testl0/current-time-bringer/tree/main)することができます。

## おわりに

プレイヤーの頭を使用して現実世界の日付と時刻を取得する関数を作成しました。

### 概要

- プレイヤーの頭から `components."minecraft:profile".properties[].value` を取得する。
- 取得した文字列の 25 文字目 ～ 38 文字目を Base64 形式から UNIX 時刻にデコードする。
- UNIX 時刻を日時の構成要素に変換する。

### 修正が必要な問題

#### 1 つのセッションで一度しか日時を取得できない

これはゴリ押してしまえば解決できるかもしれませんが、実装が面倒だったために省略しています。

#### 2038年1月18日 13:14:08（UTC）以降は利用できない

`scoreboard` コマンドで UNIX 時刻から日時を算出していますが、スコアボード オブジェクトの最大値は 2,147,483,647 です。そのため、UNIX 時刻が 2,147,483,648-50,400=2,147,433,247 以上である 2038年1月18日 13:14:08（UTC）以降になると、正常に算出できなくなります。これはゴリ押してしまえば解決できるかもしれませんが、実装が面倒だったために省略しています。

#### 2286年11月20日 03:46:40（UTC）以降は利用できない

「エンコードされた文字列」を 14 文字に切り取る操作は、UNIX 時刻が 10 桁であることを前提としています。そのため、UNIX 時刻が 10,000,000,000-50,400=9,999,949,600 以上である 2286年11月20日 03:46:40（UTC）以降になると、正常に算出できなくなります。そのときは私は生きていないため、生涯にわたって修正しません。