记录我在更改时区时踩到的坑和解决方案,以及一点小小的感想。

TL;DR#

  1. 修改 Django 项目的TIME_ZONE设置为Asia/Shanghai
  2. 填充 MySQL 时区表:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
  3. 向 MySQL 全局配置文件的mysqld中添加default-time-zone='Asia/Shanghai';或者在 MySQL shell 中执行SET GLOBAL time_zone = 'Asia/Shanghai';
  4. 向 Django 项目设置中的DATABASES字段添加时区,并设置为Asia/Shanghai
  5. (optional)更新已经存在数据的时间:update blog_article set column_name=DATE_ADD(column_name, INTERVAL 8 HOUR);

正文#

起因#

今天在博客迁移服务器之后,突然想起来之前部署的时候,使用的是之前服务器的时区(UTC),没有使用 CST,就想着把时区改一下,不然挺不方便,而且看着挺难受的。修改的时候碰到了一些问题,这里记录一下解决方案以及一些个人感想。

尝试#

最开始,我觉得修改时区应该很简单,直接修改一下 Django 的时区设置即可。然而一个报错直接给我整不会了。

image

根据错误提示,我推测应该是 MySQL 的时区设置也不对,也要更新一下时区,然后直接在 mysql 配置文件中设置了 CST 时区:

default-time-zone='Asia/Shanghai'

重启 MySQL,竟然失败了?!!!

Restarting mysql (via systemctl): mysql.serviceJob for mysql.service failed because the control process exited with error code.

See "systemctl status mysql.service" and "journalctl -xe" for details.

failed!

不知道什么原因,这个方法不行,就在网上找到了另一个如何设置 MySQL 时区的答案,发现了另一种解决方法,在 MySQL 的 shell 中更新全局变量@@global.time_zone。所以删除了之前添加的时区配置,进入 MySQL shell 中进行设置:

SET @@global.time_zone = '+08:00';

然后,输出时区设置:

mysql> SELECT @@global.time_zone;

+--------------------+

| @@global.time_zone |

+--------------------+

| +08:00 |

+--------------------+

1 row in set (0.00 sec)

感觉设置成功了,然后尝试运行服务,发现还是同样的错误。明明时区设置成功了,为什么不生效呢?

答案?#

然后突然想到一个问题,我直接 Google 报错信息不就行了?果然,立刻就发现了一个答案,直接使用这个命令就能搞定:

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql

动手试了一下,发现还真的可以,只不过会提示一些 Warning 信息:

Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.

Warning: Unable to load '/usr/share/zoneinfo/leap-seconds.list' as time zone. Skipping it.

Warning: Unable to load '/usr/share/zoneinfo/leapseconds' as time zone. Skipping it.

Warning: Unable to load '/usr/share/zoneinfo/tzdata.zi' as time zone. Skipping it.

Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.

Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.

问题来了,为什么呢?虽然这样可以,但是不知道原因,心里有点不舒服,所以就查了官方文档。然后在这里中找到了答案:

Several tables in the mysql system schema exist to store time zone information . The MySQL installation procedure creates the time zone tables, but does not load them. To do so manually, use the following instructions.

根据上面这句话我们可以很容易发现,MySQL 安装过程创建时区表,但不会加载它们,需要我们手动加载。加载方法也很简单,就是我们上面提到的那行命令:

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql

其中,mysql_tzinfo_to_sqly 用来读取系统的时区文件并从中生成 SQL 语句。 mysqly 用来处理这些语句以加载时区表。

有一点要注意的是,上面的命令会加载/usr/share/zoneinfo下的所有时区信息,所以如果你不想这样的话,也可以选择加载自己需要的时区,命令的基本格式是:

mysql_tzinfo_to_sql tz_file tz_name | mysql -u root -p mysql

比如:只加载Asia/Shanghai时区,可以使用下面的命令:

mysql_tzinfo_to_sql /usr/share/zoneinfo/Asia/Shanghai ‘Asia/Shanghai’ | mysql -u root -p mysql

在更新时区表后,重新启动mysqld以确保它不会继续提供过时的时区配置。

然后在 MySQL shell 中确认一下是否成功:

mysql> SELECT * FROM mysql.time_zone_name;

+---------------+--------------+

| Name | Time_zone_id |

+---------------+--------------+

| Asia/Shanghai | 1 |

+---------------+--------------+

1 row in set (0.01 sec)

mysql> SELECT @@global.time_zone;

+--------------------+

| @@global.time_zone |

+--------------------+

| Asia/Shanghai |

+--------------------+

1 row in set (0.00 sec)

更新完 MySQL 时区设置,然后重新进入 admin 界面,时间已经改变成了 CST 时区时间。

此外,在文档中我还找到了设置时区为Asia/Shanghai导致 MySQL 启动失败的原因:

Note

Named time zones can be used only if the time zone information tables in the **mysql** database have been created and populated. Otherwise, use of a named time zone results in an error:

mysql> SET time_zone = 'UTC';

ERROR 1298 (HY000): Unknown or incorrect time zone: 'UTC'

我们从上面的粗体部分可以发现:之前之所以设置Asia/Shanghai不生效是因为**MySQL 仅当数据库中的时区信息表已创建并填充时,才能使用命名时区。**因为刚开始没有填充时区表,MySQL 不知道 Asia/Shanghai 代表什么意思,所以才会出错无法启动 MySQL 服务。

测试#

尝试创建了一篇文章,发现时间也是对的,感觉到这里应该没什么问题了。但是,有点强迫症的我还是进入 MySQL 查看了一下数据,结果。。。数据库中的时间竟然还是 UTC 时间??

虽然还是没有成功,但是现在有一点可以确认的是,MySQL 的配置已经没有了问题,所以失败的原因出在了 Django 配置上。然后直接查看官方文档,在其中找到了原因。

When support for time zones is enabled, Django stores datetime information in UTC in the database, uses time-zone-aware datetime objects internally, and translates them to the end user’s time zone in templates and forms.

换句话说,只要是项目使用时区支持,那么,无论你设置什么时区,Django 在数据库中存储数据默认都是使用 UTC 时间。所以,如果想要更改 Django 存储数据的时区,还需要在 setting 中的 DATABASES 里,将TIME_ZONE选项设置为你想要的时区。

更新了 Django 项目的配置,然后重新启动,进入 admin 界面,时间没问题,然后进入 MySQL shell 查看时间列,也没问题。终于,终于成功了!!!

收尾#

当然,还有一步,那就是更新 MySQL 数据库中已经存在的数据的时间,这个直接在原来的时间上加 8 小时即可:

update blog_article set column_name=DATE_ADD(column_name, INTERVAL 8 HOUR);

想法#

最后,虽然这只是个小问题,也解决了,但是也让我有点意识到了自己的一些不好的思维方式,本来只需要直接查找一下类似的错误,很快就能找到解决方案。