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


目录:

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的时区设置即可。然而一个报错直接给我整不会了。

错误信息

根据错误提示,我推测应该是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);

想法

最后,虽然这只是个小问题,也解决了,但是也让我有点意识到了自己的一些不好的思维方式,本来只需要直接查找一下类似的错误,很快就能找到解决方案。但是实际上是,我先入为主的认为这是一个简单的问题,只需要更改一下Django的时区就可以了,所以当我发现只改Django的时区设置出错时,我就下意识的认为时MySQL的时区也有问题,,,陷入了一个思维怪圈,导致我花费了1个小时才意识到了根源问题,进而找到了解决方案。

END