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
 alpha3166
alpha3166