Windowsのタイムゾーンをコンテナに引き継ぐ
コンテナを起動するスクリプトで、Dockerホストのタイムゾーンをコンテナに引き継がせたい場合がある。たとえば、コンテナが吐くファイルに、ローカルなタイムゾーンで生成日時を書き込みたいときとか。
世界中の誰もが同じスクリプトを使いまわせるようにしたければ、リテラルでタイムゾーンを書くことはできないので、何らかの手段でDockerホストのタイムゾーンを取得し、それをコンテナに引き回す必要がある。
Linuxの場合
DockerホストがLinuxの場合は簡単だ。以下はDockerホストがUbuntuの例。
コンテナのタイムゾーンは、デフォルトではUTCになっている。以下はDebianコンテナの例。
$ docker run -it --rm debian date
Thu May 4 23:17:36 UTC 2023
環境変数TZ
に反応してくれるコンテナなら、起動時にTZ
を渡せばタイムゾーンが変わる。Dockerホスト側のタイムゾーンは、たとえばtimedatectl
で取り出す。
$ timedatectl --property Timezone --value show
Asia/Tokyo
$ docker run -it --rm --env TZ=$(timedatectl --property Timezone --value show) debian date
Fri May 5 08:17:57 JST 2023
あるいは、コンテナの/etc/localtime
を目的のzoneinfoへのシンボリックリンクとして張りなおす手もある。
$ docker run -it --rm debian sh -c "ln -sf /usr/share/zoneinfo/$(timedatectl --property Timezone --value show) /etc/localtime && date"
Fri May 5 08:18:00 JST 2023
ほかに、Dockerホストの/etc/localtime
をコンテナにマウントして引き継がせる手もあるが、この場合は少し注意が必要だ。
$ ls -l /etc/localtime
lrwxrwxrwx 1 root root 30 May 4 06:49 /etc/localtime -> /usr/share/zoneinfo/Asia/Tokyo
$ docker run -it --rm -v /etc/localtime:/etc/localtime:ro debian date
Fri May 5 08:18:22 JST 2023
この方法では、コンテナの/etc/localtime
が/usr/share/zoneinfo/Etc/UTC
へのシンボリックリンクだった場合、コンテナの/usr/share/zoneinfo/Etc/UTC
の中身がDockerホストの/usr/share/zoneinfo/Asia/Tokyo
の中身に置き換わってしまう(Debianコンテナはこのケースに該当する)。
$ docker run -it --rm -v /etc/localtime:/etc/localtime:ro debian
root@34202b683b78:/# ls -l /etc/localtime
lrwxrwxrwx 1 root root 27 May 2 09:00 /etc/localtime -> /usr/share/zoneinfo/Etc/UTC
root@34202b683b78:/# tail --lines 1 /usr/share/zoneinfo/Etc/UTC
JST-9
万一コンテナ内で/usr/share/zoneinfo/Etc/UTC
を直接参照しているプログラムがいたら、時間がずれてしまうおそれがあるので、できれば避けた方が良いかもしれない。
Windowsの場合
問題は、DockerホストがWindowsの場合だ。
PowerShellだと、タイムゾーンはGet-TimeZone
コマンドレットが返すSystem.TimeZoneInfo
のId
プロパティで取れるのだが、この値はIANAが定めるAsia/Tokyo
のような値ではなく、Windows独自のTokyo Standard Time
のような形式なため、そのままではLinuxのコンテナに渡せない。
PS C:\> Get-TimeZone | Select-Object -ExpandProperty Id
Tokyo Standard Time
.NET 5までは、このWindows独自のタイムゾーン名をIANAの名前に変換する公式な手段がなく、TimeZoneConverterをNuGetして使うなどの方法しかなかった。
しかし.NET 6で、System.TimeZoneInfo
クラスにTryConvertWindowsIdToIanaId
メソッドが追加され、これで名前の変換ができるようになった。このあたりの事情は、「Date, Time, and Time Zone Enhancements in .NET 6」に詳しい。
TryConvertWindowsIdToIanaId
メソッドは2つの引数を取る。1つ目は入力となるWindowsのタイムゾーン名の文字列で、2つ目は出力となるIANAの名前を格納する変数への参照だ。戻り値は、そのWindowsタイムゾーン名が存在したかどうかを示すブール値になる。
これをPowerShellから呼び出す場合はこんな感じになるだろう。
PS C:\> $win = Get-TimeZone | Select-Object -ExpandProperty Id
PS C:\> $iana = ""
PS C:\> [System.TimeZoneInfo]::TryConvertWindowsIdToIanaId($win, [ref]$iana)
True
PS C:\> $iana
Asia/Tokyo
これで得られた$iana
をコンテナ起動時のTZ
環境変数に設定してやれば、Windowsからでもタイムゾーンが引き継げる。
PS C:\> docker run -it --rm --env TZ=$iana debian date
Fri May 5 08:19:27 JST 2023
TZ
に対応していないコンテナの場合は、コマンド実行前に/etc/localtime
を設定してやればよいだろう。
PS C:\> docker run -it --rm debian sh -c "ln -sf /usr/share/zoneinfo/$iana /etc/localtime && date"
Fri May 5 08:19:41 JST 2023
めでたし、めでたし。
コンテナがAlpineの場合
なお、コンテナがAlpine Linuxベースの場合、デフォルトではzoneinfoが入っていないので、先にapk add tzdata
してやる必要がある。
素のままでは、TZ
を渡しても、/etc/localtime
を設定しても、UTCのまま。
PS C:\> docker run -it --rm --env TZ=$iana alpine date
Thu May 4 23:19:57 UTC 2023
PS C:\> docker run -it --rm alpine sh -c "ln -sf /usr/share/zoneinfo/$iana /etc/localtime && date"
Thu May 4 23:20:01 UTC 2023
apk add tzdata
してからなら、タイムゾーン指定が有効になる。
PS C:\> docker run -it --rm --env TZ=$iana alpine sh -c "apk --update-cache add tzdata && date"
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2023c-r0)
OK: 10 MiB in 16 packages
Fri May 5 08:20:17 JST 2023
PS C:\> docker run -it --rm alpine sh -c "apk --update-cache add tzdata && ln -sf /usr/share/zoneinfo/$iana /etc/localtime && date"
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2023c-r0)
OK: 10 MiB in 16 packages
Fri May 5 08:20:23 JST 2023
※バージョンメモ
- Docker Desktop Engine 20.10.24
- Ubuntu 22.04.2 (on WSL 1.2.5.0)
- PowerShell 7.3.4
- debian:latest = debian:11.7
- alpine:latest = alpine:3.17.3