django 时间 时区 语言问题
场景:
1.ORM操作Model
中DateField
, DateTimeField
, TimeField
字段时, 数据库(以MySQL举例)中对应存储时间字段的实际情况.
2.django开发中, django服务内部调用其它服务的API接口返回了时间字段, 该时间字段如何处理成前端 (以React.js举例)框架可以正确转换成本地时间的对象.
前置知识:
1.什么是 UTC时间 (自行百度谷歌)
2.常见表示时间的规范: GMT UTC CST 见下面的参考资料1
场景1: 重要结论和实践操作
1.django可以通过setting.py
文件中进行 时间 和 语言 的本地化 配置, 此配置是全局性的.
- 配置意义: 如果用到原生的django admin等, 配置之后界面的时间由UTC变成本地时间, 语言变成本地语言.对用户更加友好.
- 但是, 和数据库中存储的时间并无关系, 下面有详细说明.
默认配置:
略
本地化配置如下:
LANGUAGE_CODE = 'zh-Hans' # 语言
TIME_ZONE = 'Asia/Shanghai' # 时区
USE_I18N = True # 语言
USE_L10N = True # 数据和时间格式
USE_TZ = True # 启用时区
2.django在写数据库的时候, 日期和时间 都用的UTC时间
- 换句话说: 即使进行了上面的本地化配置, 实际
datatime
等时间字段在数据库中也是UTC时间, 会比本地时间(以东八区举例)慢 8h - 可以自己使用
DateTimeField
字段的auto_now
选项来测试每次save
的时候, 数据库时间和当前时间的变动.
class XxModel(models.Model):
created_time = models.DateTimeField(auto_now_add=True, editable=False, verbose_name="创建时间")
....
python runserver shell
>>>obj = XxModel.objects.creat(...)
>>> obj.created_time
同数据库中保存的 created_time 字段的值进行比对.
3.前端(如react框架)可以直接接收后端API返回的datetime
对象.
-
该结论的意义: 前端可以通过后端返回的
datetime
对象, 进行 时区timezone 转换. 将UTC时间转换成local time时间. -
一般后端是从数据库返回的datetime对象, 经过前端本地化处理(如, +8h), 成功的把之前数据库相对django慢的8h加回来了.
-
Chrome的
console
来演示:- 谷歌浏览器页面按
F12
打开之后, 选择console
的选项卡, 输入下面的内容 - 切换操作系统的时区
- 谷歌浏览器页面按
>new Date("2020-02-06T01:56:21.744Z")
<Thu Feb 06 2020 09:56:21 GMT+0800 (中国标准时间)
# 注: 我是win10平台的chrome.
# 修改 操作系统默认的时区设置, 成UTC时间. 再输入下面的内容重新看控制台输出
>new Date("2020-02-06T01:56:21.744Z")
<Thu Feb 06 2020 01:56:21 GMT+0000 (格林尼治标准时间)
- 结论:
- 1.浏览器默认读取操作系统的时区设置, 然后将收到的时间参数进行本地化转换.
- 2.提示: 不同操作系统默认的时区是不一样的. Win 和 Unix 系统就有区别, 可以深入研究.
场景2: 实践操作和解决方案优化
1.访问外部服务API获取的时间的时区 和 django本地化 的时区可能不一致, 需要具体问题具体处理.
详细背景:
开发中, django本地化采用的是东八区的datetime, 调用外部服务获取的datetime是UTC时间.
我返回给前端的时候没有直接返回datetime对象, 采用的是format后的datetime字符串, 由我手动将时间+8h, 然后再将加之后的时间进行format给前端.
- 导致: 前端没法利用模块直接做时间转化.
- 反思: 我这样的操作多此一举.
2.我的解法:
def datetime_monkey(dt: datetime) -> str:
_ = dt + timedelta(hours=8)
return datetime.strftime(_, "%Y-%m-%dT%H:%M:%S")
# 伪代码
return JsonResponse(data={"upload_time": datetime_monkey(obj.last_modified), ...})
# obj.last_modified 是一个 datetime 对象.
3.我优化的解法:
-
上面的解法, 对时区处理直接 +8h 显然存在 hard code 的问题.
-
厘清问题: 时间本身并不存在bug, 只是展示结果不是我们预期想要的(不够人性化)
-
思路: 时区转换.
-
工具:
pytz
The second way of building a localized time is by converting an existing localized time using the standard
astimezone()
method -
辅助验证工具:
tzlocal
查看本地时区
-
import pytz
import tzlocal
# 伪代码
from ....settings import TIME_ZONE # 从django项目的settings.py文件中导入 TIME_ZONE 配置
local_tz = tzlocal.get_localzone() # 查看本地所时区TimeZone
print(f">>{local_tz}<< {type(local_tz)}") # Asia/Shanghai
# tz = pytz.timezone('Asia/Kuala_Lumpur')
tz = pytz.timezone(TIME_ZONE) # TIME_ZONE = 'Asia/Shanghai'
# now_kl = obj.last_modified.replace(tzinfo=pytz.utc).astimezone(tz)
now_kl = obj.last_modified.astimezone(tz)
print(now_kl)
4.时区转换
- 见参考资料4,5
5.当然, 最最优的方案就是:直接返回前端 datetime 对象, 由前端统一处理。
一点反思:
1.最正确的解决问题的思路还是看 python 官方相关文档;
2.当文档写的不好的时候, 或者时间紧迫的时候, 先解决问题。
3.对知识点理解的越透彻, 越容易化繁为简, 最终解决方案也许简单, 但是解决的过程可能充满挑战。
参考资料(均来自网络):
4.utc时间转换成其它时区的时间: python-convert-utc-to-timezone