Django项目时区更改错误的解决方案

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

SMJ
loading... read

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);

想法

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

Sooner or later, everything ends.