Linuxのタイムゾーンまわり CentOS7編

最近会社で聞かれて答えたことをまとめてみました。

Linuxでコマンドやログから出力される日時の情報、とくにタイムゾーンまわりを説明します。

CentOS6とCentOS7の実験結果を含めて書くつもりでした。しかしCentOS6の結果をうまく纏めることができませんでした。そのためCentOS7編として説明します。

# 関連するファイル群との関連性 (CentOS7)
まず、Linuxが管理している時計とその周辺の主要部分を簡単に図示してみます。その後、それぞれの部分を説明していきます。

![Linuxの日時とその周辺.png](https://qiita-image-store.s3.amazonaws.com/0/381550/f7ab8607-b4be-e4c4-decd-3fa4e2758fef.png)

# ハードウェアクロック
ハードウェアクロックは、OSとは無関係に刻まれる、パソコン内部の時計です。

パソコンの電源を停止させて、1時間後にパソコンを起動すると、時計はちゃんと1時間経過した時刻を指します。パソコンの電源を停止させた状態でも、コンセントを抜いてあっても、ネットワークに接続されていない状態でも、パソコンの内部では時計を刻んでいます。

このように、OSとは無関係に刻まれる時計が、ハードウェアクロックです。マザーボードに取り付けられたボタン電池などから電源供給を受けて、外部電源やバッテリーなどとは独立して、時計を刻みます。

ハードウェアクロックそのものには、タイムゾーンの考えはありません。

以降の説明では、RTC(リアルタイムクロック)と略記します。

# システムクロック
システムクロックは、OSが内部保持する時計です。

システムクロックは、OSが起動する際にRTCからセットされます。そして、OS起動中はマザーボードのインターバルタイマーからの割り込みによって、クロックを刻んでいきます。そして、何らかのタイミングでRTCへの書き戻しが行われます。書き戻しのタイミングはOSや設定によって異なります。一例をあげると、一部のLinuxには、NTPを使用して時刻同期状態になると、11分周期でRTCへの書き戻しを行うものがあります("man hwclock")。
![ハードウェアクロックとシステムクロックの相互反映.png](https://qiita-image-store.s3.amazonaws.com/0/381550/7aa01f18-81d7-1af9-4265-dced65234f30.png)

OS起動時にRTCからシステムクロックをセットする際、RTCをUTCとして扱うかローカルタイムとして扱うかによって、システムクロックへのセット方法が変わることになります。Linuxでは、システムクロックをUTCで管理しています。RTCをUTCとして扱うなら、RTCをそのままシステムクロックにセットすることができます。RTCをローカルタイムとして扱うなら、ローカルタイムからUTCへの変換を行った後にシステムクロックにセットする必要があります。

MS-DOSやWindowsでは、RTCをローカルタイムとして扱っています。RTCが12:00を指し示している場合、OSも現地時間で12:00だという扱い方です。シングルユーザーユース、デスクトップ利用をベースとして育ってきたOSだからだと思います。

旧来のUnix系OSでは、RTCをUTCとして扱っていました。RTCが12:00なら、OSはUTCで12:00、そのとき日本時間だと21:00という扱いです。マルチユーザーユース、遠隔サーバー利用をベースとして育ってきたOSだからだと思います。一人のユーザーが日本時間で使っている裏で、他のユーザーが他のタイムゾーンを使っているかもしれない、だから特定のタイムゾーンに決められないというような理由があったのだろうと思います。

しかし、シングルユーザーで利用し、Windowsとデュアルブートで共存させることもあるPC-UNIXが登場し、RTCをローカルタイムとして扱う必要が出てきました。このような背景もあってか、LinuxではRTCをUTCとして扱うこともローカルタイムとして扱うこともできるようになっています。

# ローカルタイム出力
日時情報を画面やファイルに出力するときは、ローカルタイムで出力することが多いでしょう。システムクロックを出力する場合も、ファイルのタイムスタンプを出力する場合も同じです。ローカルタイムで出力するためには、ローカルタイムのタイムゾーンが必要となります。

Linuxでは、複数の情報に基づいてタイムゾーンを決定します。環境変数TZ、オプション引数や設定ファイル、それらの指定がない場合に使われる全体設定 "/etc/localtime" などが、タイムゾーンに関与します。

# 実験

上に書かれた情報を実際に実験してみることにします。

## RTCからシステムクロックを決定する仕組み
RTCからシステムクロックを算出する際には"/etc/adjtime"や"/etc/localtime"が使用されます。CentOS6の頃には"/etc/sysconfig/clock"というファイルもありました。しかしCentOS7では排除されたようです。

実験環境は、CentOS(7.6.1810) on VirtualBox(6.0.4) on Windows 10です。CentOSは[OSBoxes](https://www.osboxes.org/centos/)から入手したVirtualBoxイメージを使用しています。

Windows側のタイムゾーンの影響を排するため、Windowsの時刻同期をOFFして、タイムゾーン設定をUTCに変更します。実験するたびに、Windowsの時計を30日9時過ぎに更新しています。

![Windows側の時刻設定.png](https://qiita-image-store.s3.amazonaws.com/0/381550/62426701-7a54-3151-8a83-7a2118469a49.png)

ゲストOSもインターネットへの接続を無効(ホストオンリーアダプター)にすることで、時刻同期をOFFしてあります。

### CentOS7 RTC=UTC の場合

ホストOSのWindowsが30日の9時過ぎを指し示しているとき、ゲストOSのCentOSは以下のようになります。なお、理解しやすくするために先頭でタイムゾーンをAsia/Tokyoに変えています。

```
[root@osboxes ~]# timedatectl set-timezone Asia/Tokyo
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 18:12:00 JST
  Universal time: Sat 2019-03-30 09:12:00 UTC
        RTC time: Sat 2019-03-30 09:12:01
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: no
      DST active: n/a
[root@osboxes ~]#
[root@osboxes ~]# cat /etc/adjtime
0.0 0 0.0
0
UTC
[root@osboxes ~]#
```

"RTC in local TZ"がnoとなっています。これがRTC=UTCを表しています。"/etc/adjtime"の3行目でもRTC=UTCとなっていることが確認できます。

"RTC time"がゲストOS視点のRTCです。ホストOSの時刻と一致しています。

"Universal time"がシステムクロックです。RTCがそのまま取り込まれているのが分かります。

"Local time"はシステムクロックをシステム全体設定のタイムゾーンで表したものです。日本時間なので+9時間されていることが分かります。

### CentOS7 RTC=ローカルタイム
RTCをJSTとして扱うように切り替えてみます。

```
[root@osboxes ~]# timedatectl --adjust-system-clock set-local-rtc 1
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 09:13:42 JST
  Universal time: Sat 2019-03-30 00:13:42 UTC
        RTC time: Sat 2019-03-30 09:13:42
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: yes
      DST active: n/a

Warning: The system is configured to read the RTC time in the local time zone.
         This mode can not be fully supported. It will create various problems
         with time zone changes and daylight saving time adjustments. The RTC
         time is never updated, it relies on external facilities to maintain it.
         If at all possible, use RTC in UTC by calling
         'timedatectl set-local-rtc 0'.
[root@osboxes ~]# cat /etc/adjtime
0.0 0 0.0
0
LOCAL
[root@osboxes ~]#
```

"RTC in local TZ"がyesとなっています。これがRTC=ローカルタイムを表しています。この変更が"/etc/adjtime"の3行目に反映されていることも分かります。なお、この設定にすると上記のように"timedatectl status"実行時に警告が表示されます。

"RTC time"は変わっていません。これは、"timedatectl set-local-rtc 1"を実行するときのオプション"--adjust-system-clock"による効果です。この部分は後述します。

"Local time"がローカルタイムです。ローカルタイムがRTCと一致していることが分かります。

"Universal time"がシステムクロックです。"Local time"がRTCと一致するための辻褄合わせで、RTCが-9時間されていることが確認できます。

"timedatectl set-local-rtc 1"を実行するときに"--adjust-system-clock"を付けました。これをつけないとどうなるでしょうか。

|項目           | 実行前 |オプションありで実行 |オプションなしで実行 |
|:--------------|:-----|:--------|:--------|
| Local time    |19:00 |10:00(*) |19:00    |
| Universal time|10:00 |01:00(*) |10:00    |
| RTC time      |10:00 |10:00    |19:00(*) |

実行前後で変化した項目に(*)印をつけてあります。オプションありの場合は、RTCを変えずにシステムクロックで時刻調整しています。オプションなしの場合は、システムクロックを変えずにRTCで時刻調整しています。オプションの名前"--adjust-system-clock"が、システムクロックを補正する形で時刻調整することを物語っています。

今回は、RTCを変更したくないケースとして"--adjust-system-clock"付きで実行しています。Windowsとのデュアルブートのような場合は、このような時刻調整になるでしょう。

## UTCで保持されているシステムクロックからローカルタイムを算出する仕組み

まず前提環境を作ります。上に書いたRTC＝ローカルタイムの環境です。

```
[root@osboxes ~]# timedatectl set-timezone Asia/Tokyo
[root@osboxes ~]# timedatectl --adjust-system-clock set-local-rtc 1
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 09:13:03 JST
  Universal time: Sat 2019-03-30 00:13:03 UTC
        RTC time: Sat 2019-03-30 09:13:03
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: yes
      DST active: n/a
    : 警告メッセージ略
[root@osboxes ~]#
```

### 全体設定"/etc/localtime"

"/etc/localtime"は、システム全体のタイムゾーンを設定するファイルです。このファイルはタイムゾーン情報を含んだファイルで、このファイルを差し替えることによりシステム全体のタイムゾーンを切り替えることができます。

このファイルは通常、"/usr/share/zoneinfo"の下にあるファイルのコピーあるいはリンク(シンボリックリンクorハードリンク)となっています。

```
[root@osboxes ~]# ls -l /etc/localtime
lrwxrwxrwx. 1 root root 32 Mar 30  2019 /etc/localtime -> ../usr/share/zoneinfo/Asia/Tokyo
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 09:15:10 JST
  Universal time: Sat 2019-03-30 00:15:10 UTC
        RTC time: Sat 2019-03-30 09:15:10
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: yes
      DST active: n/a

   : 警告メッセージ省略
[root@osboxes ~]# unset TZ; date
Sat Mar 30 09:19:11 JST 2019
[root@osboxes ~]#
```

timedatectlコマンドで設定されたタイムゾーン情報が"/etc/localtime"に反映されていること、環境変数TZが設定されていない場合には"/etc/localtime"の情報が用いられることが分かります。

タイムゾーンを変えて試してみます。

```
[root@osboxes ~]# timedatectl set-timezone Asia/Ho_Chi_Minh
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 07:18:15 +07
  Universal time: Sat 2019-03-30 00:18:15 UTC
        RTC time: Sat 2019-03-30 07:18:16
       Time zone: Asia/Ho_Chi_Minh (+07, +0700)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: yes
      DST active: n/a

   : 警告メッセージ省略
[root@osboxes ~]#
```

この動きはマニュアルに書いてあります("man timedatectl")。RTCをローカルタイムとして扱っている場合、システムクロックを変更しない形でタイムゾーンを変更します。その結果、辻褄が合うようにRTCが変更されます。

RTCを変更したくない場合は、CentOS6と同様に手動で変更するしかないかもしれません。戻して手動変更をしてみます。

```
[root@osboxes ~]# timedatectl set-timezone Asia/Tokyo
[root@osboxes ~]# \rm /etc/localtime
[root@osboxes ~]# ln -s /usr/share/zoneinfo/Asia/Ho_Chi_Minh /etc/localtime
[root@osboxes ~]# ls -l /etc/localtime
lrwxrwxrwx. 1 root root 36 Mar 30 07:35 /etc/localtime -> /usr/share/zoneinfo/Asia/Ho_Chi_Minh
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 07:36:37 +07
  Universal time: Sat 2019-03-30 00:36:37 UTC
        RTC time: Sat 2019-03-30 09:36:38
       Time zone: Asia/Ho_Chi_Minh (+07, +0700)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: yes
      DST active: n/a

   : 警告メッセージ省略
[root@osboxes ~]# hwclock --hctosys
[root@osboxes ~]# timedatectl status
      Local time: Sat 2019-03-30 09:40:08 +07
  Universal time: Sat 2019-03-30 02:40:08 UTC
        RTC time: Sat 2019-03-30 09:40:08
       Time zone: Asia/Ho_Chi_Minh (+07, +0700)
     NTP enabled: yes
NTP synchronized: no
 RTC in local TZ: yes
      DST active: n/a

   : 警告メッセージ省略
[root@osboxes ~]#
```

"/etc/localtime"を変更するだけでは、RTCとシステムクロックは変更されません。この状態は、タイムゾーンとの辻褄があっていない状態です。そのため、"hwclock --hctosys"でRTCとシステムクロックの関係性を修正しています。

このようにシステム全体のタイムゾーンを変更する場合や、システムクロックの変更が発生するような場合には、OSそのものの再起動を行っておくのが無難だろうと思います。整合性の合わない部分、時間が巻き戻ってしまうことによる問題が発生する部分があり得ます。そして、その範囲がシステム全体に及ぶとなると、なかなか影響範囲を見極めるのが難しいだろうと思います。

これを書いていて気になったことがあります。RTCをローカルタイムとみなしたときのRTCのタイムゾーンと、システム全体で持つタイムゾーンが同じでなければいけない、という前提の作りに見えるところです。そこは独立でいいだろうし、独立であれば不用意にRTCが書き換わることもなく、それに伴う弊害も排除されるような気がするのです。何か理解できていない仕組みがあるのか、それともやはり警告に表示されているとおり、RTCをUTCとして扱うのが最適解なのか。

### 環境変数TZ

一部の(大半の？)コマンドは"/etc/localtime"よりも環境変数TZを優先します。

例えばdateコマンドの場合、このような動きとなります。システム全体設定"/etc/localtime"は日本"Asia/Tokyo"に戻してあります。

```
[root@osboxes ~]# date
Sat Mar 30 10:04:35 JST 2019
[root@osboxes ~]# TZ=Asia/Tokyo date
Sat Mar 30 10:04:39 JST 2019
[root@osboxes ~]# TZ=Asia/Ho_Chi_Minh date
Sat Mar 30 08:04:43 +07 2019
[root@osboxes ~]# date
Sat Mar 30 10:04:50 JST 2019
[root@osboxes ~]# TZ=Asia/Tokyo date
Sat Mar 30 10:04:52 JST 2019
[root@osboxes ~]# TZ=JST-9 date
Sat Mar 30 10:04:56 JST 2019
[root@osboxes ~]# TZ=Asia/Ho_Chi_Minh date
Sat Mar 30 08:05:01 +07 2019
[root@osboxes ~]# TZ=VST-7 date
Sat Mar 30 08:05:06 VST 2019
[root@osboxes ~]# TZ= date
Sat Mar 30 01:05:14 UTC 2019
[root@osboxes ~]# date --utc
Sat Mar 30 01:05:26 UTC 2019
[root@osboxes ~]#
```

環境変数TZに"Asia/Tokyo"や"JST-9"を設定すると、設定していない場合同様に日本時間が表示されています。環境変数TZに"Asia/Ho_Chi_Minh"や"VST-7"を設定すると、ベトナム時間で表示されています。

環境変数TZに空文字列を設定するとUTC相当の動きとなります。"/etc/localtime"が使われるわけではありません。

システムクロックを中心に説明しているため、dateコマンドを挙げました。しかし、lsコマンドやtouchコマンドなど、日時を扱う他のコマンドにも同様のことが言えます。

```
[root@osboxes ~]# mkdir tmp
[root@osboxes ~]# ls -ld tmp
drwxr-xr-x. 2 root root 4096 Mar 30 10:10 tmp
[root@osboxes ~]# TZ= ls -ld tmp
drwxr-xr-x. 2 root root 4096 Mar 30 01:10 tmp
[root@osboxes ~]#
```

# 意図的に書かなかったこと
もともとLinuxのタイムゾーンの話だったので、そこから大きく外れる以下の話は省略しました。

- Windows上のVirtualBoxがゲストOSに渡すRTCの仕組み
 - VBoxManage.exe showvminfo ゲストOS名 | findstr "RTC Time"
- WindowsでもRTCをUTCとみなすことができること、その関連レジストリ
 - reg query "HKLM\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal
- WindowsでもRTCを単純にローカルタイムとみなしているわけではないこと、その関連レジストリ
 - reg query "HKLM\System\CurrentControlSet\Control\TimeZoneInformation"