有哪些适配微信小程序的开源UI项目?分享几个微信小程序开发框架

1.基于规范的小程序 UI 组件库,自定义标签组件,简洁、易用、工具化 https://meili.github.io/min/docs/minui/

https://meili.github.io/min/docs/minui/index.html

 

2.高颜值、好用、易扩展的微信小程序 UI 库,Powered by 有赞

https://github.com/youzan/zanui-weapp

 

3.A UI library by WeChat official design team, includes the most useful widgets/modules.

https://github.com/Tencent/weui-wxss

https://weui.io/

4.wux – 微信小程序自定义组件(对话框、指示器、五星评分…)

https://github.com/skyvow/wux

5.微信小程序日历组件

https://github.com/iamaddy/calendar

6.https://github.com/TalkingData/iview-weapp

7.https://github.com/aben1188/awesome-wepy

8.城市组件https://github.com/shmy/we-city-select

9. 微信小程序组件和功能封装,基于微信Component自定义组件开发

https://github.com/ChanceYu/weapp

10.https://www.bidianer.com/bag/443

 

 

【小程序开发框架】

1、官方框架MINA

小程序提供的开发框架为MINA框架,它类似于淘宝Weex、Vue框架。MINA框架通过封装微信客户端提供的文件系统、网络通信、任务管理、数据安全等基础功能,对上层提供一整套JavaScript API,让开发者能够非常方便地使用微信客户端提供的各种基础功能与能力,快速构建一个应用。

地址:

https://developers.weixin.qq.com/miniprogram/dev/framework/MINA.html

2、美团小程序框架mpvue

mpvue 是美团点评开源的一个使用Vue.js开发小程序的前端框架。框架基于 Vue.js 核心,mpvue 修改了 Vue.js 的  runtime 和  compiler 实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验。使用  mpvue 开发小程序,你将在小程序技术体系的基础上获取到这样一些能力:

  • 彻底的组件化开发能力:提高代码复用性
  • 完整的 Vue.js 开发体验
  • 方便的 Vuex 数据管理方案:方便构建复杂应用
  • 快捷的 webpack 构建机制:自定义构建策略、开发阶段 hotReload
  • 支持使用 npm 外部依赖
  • 使用 Vue.js 命令行工具 vue-cli 快速初始化项目
  • H5 代码转换编译成小程序目标代码的能力

Github:

https://github.com/Meituan-Dianping/mpvue

官网:

http://mpvue.com/

3、Tina.js 一款轻巧的渐进式微信小程序框架

tina-js.jpg

特性: 轻盈小巧。 极易上手,保留 MINA (微信小程序官方框架) 的大部分 API 设计;无论你有无小程序开发经验,都可以轻松过渡上手。 渐进增强,既有状态管理器,也有路由增强,还可以自己编写插件。

Tina.js 开源框架地址:

https://github.com/tinajs/tina

4、组件化开发框架wepy

WePY 是一款让小程序支持组件化开发的框架,通过预编译的手段让开发者可以选择自己喜欢的开发风格去开发小程序。框架的细节优化,Promise,Async Functions的引入都是为了能让开发小程序项目变得更加简单,高效。

特性:
  • 类Vue开发风格
  • 支持自定义组件开发
  • 支持引入NPM包
  • 支持Promise
  • 支持ES2015+特性,如Async Functions
  • 支持多种编译器,Less/Sass/Styus、Babel/Typescript、Pug
  • 支持多种插件处理,文件压缩,图片压缩,内容替换等
  • 支持 Sourcemap,ESLint等
  • 小程序细节优化,如请求列队,事件优化等

Github地址:

https://github.com/Tencent/wepy

官网地址:

https://tencent.github.io/wepy

5、前端框架weweb

weweb是一个兼容小程序语法的前端框架,你可以用小程序的写法,来写web应用。如果你已经有小程序了,通过它你可以将你的小程序运行在浏览器中。

特性: 跨平台,一套代码多端运行(小程序、h5、未来直接打包成安卓、ios app也不是梦) 自带常用组件,完美继承了小程序内置组件 兼容小程序rpx语法,使页面更容易适配各种机型

地址:

https://github.com/wdfe/weweb

小程序开发工具

1、图片处理工具Jinaconvertjinaconvert.jpg

Jinaconvert可以帮你把图片处理成各种需要的格式,你只需要在 Jinaconvert 上选择你需要的格式类型,再将图片文件上传至即可。

工具网站地址:

https://jinaconvert.com/cn/

2、单位转换工具postcss-px2units

将px单位转换为rpx单位,或者其他单位的PostCSS插件。 postcss-px2units就可以使用简单的配置,轻松实现转换。而且该插件可以将px转换为任意你想转换的单位,比如rem。

地址:

https://github.com/yingye/postcss-px2units

3、腾讯云上传插件qcloud-upload

基于nodejs的腾讯云上传插件 支持自定义文件前缀、覆盖及非覆盖上传方式

地址:

https://github.com/yingye/qcloud-upload

4、二维码工具weapp.qrcode.js

在微信小程序中,快速生成二维码 可自定义二维码内容、宽高、纠错级别。此外,还支持生成不同前后景色的二维码。

地址:

https://github.com/yingye/weapp-qrcode

微信小程序脚手架工具 wxdad

一款微信小程序脚手架工具,帮助你快速开发微信小程序应用。目前有两个功能: 快速构建初始项目架构。 把 wxdad 语法快速编译成 wxml 和 wxjs 文件,帮助开发者急速开发。

地址:

https://gitee.com/lisniuse/wxdad

5、小程序图标工具wx-charts

基于 canvas 绘制、体积小巧的微信小程序图表工具。

  • 支持图表类型:
  • 饼图 pie
  • 圆环图 ring
  • 线图 line
  • 柱状图 column
  • 区域图 area
  • 雷达图 radar

地址:

https://github.com/xiaolin3303/wx-charts

6、小程序开发、微信公号管理系统RhaPHP

RhaPHP微信平台管理系统,支持多公众号管理,小程序开发,APP接口开发,平台反文旁虫立且快速简洁易用。灵活的扩展应用机制,具有容易上手,几乎融合微信接口,简单的调用对二次开发与开发扩展应用模块大大提高开发效率,降低企业商家运营成本。扩展应用模块化,机制灵活,代码简单并快速上手。基于THINKPHP5强力内核驱动与LAYUI前端框架,支持 Linux/Windows/Mac。

官方网站:

https://www.rhaphp.com/

7、即速应用:适合技术小白的小程序开发工具

可视化操作,直接拖拽组件生成页面 提供大量可套用的模板 可将代码打包下载,直接对接到小程序的开发工具 下载下来后的代码可以任意编辑 复杂的功能仍然需要专业程序员二次开发

官网:

http://www.jisuapp.cn/

附:微信小程序开发者文档官网地址

https://mp.weixin.qq.com/debug/wxadoc/dev/index.html

 

 

Gearman-任务分发系统

简介

Gearman是一个分发任务的程序框架,可以用在各种场合,与Hadoop相比,Gearman更偏向于任务分发功能。

Gearman提供了一个通用的应用程序,可以工作在更加适合处理这项任务的其他计算机或其他处理器上。它允许并行处理,载入平衡处理,而且可以在不同语言间进行调用。它可以应用于各种各样的应用场合。

Gearman最初用于LiveJournal的图片resize功能,由于图片resize需要消耗大量计算资 源,因此需要调度到后端多台服务器执行,完成任务之后返回前端再呈现到界面。

优点

以下是关于Gearman的一些优点:

  • 开源:完全开源,免费的。
  • 多语言:有许多语言接口,而且支持的语言数量一直在增加。
  • 灵活:不依赖于任何特定的设计。
  • 快速:简单的协议和接口,可以减少现有应用程序的开销。
  • 嵌入:轻量,可以以最小的代价引入到现有程序。
  • 没有单点故障:具有较强的容错性。

工作原理

Gearman Client:负责发起任务请求,创建Job
Gearman Job Server:负责任务调度,负责把来自Client的Job分发到不同服务器或不同处理器上的Worker。
Gearman Worker:负责Job处理,并通过Job Server返回结果给Client。

Worker工作流程

  1. Worker通过CAN_DO消息,注册到Job server上。
  2. 随后发起GRAB_JOB,主动要求分派任务。
  3. Job server如果没有job可分配,就返回NO_JOB。
  4. Worker收到NO_JOB后,进入空闲状态,并给Job server返回PRE_SLEEP消息,告诉Job server:”如果有工作来的话,用NOOP请求我先。”
  5. Job server收到worker的PRE_SLEEP消息后,明白了发送这条消息的worker已经进入了空闲态。
  6. 这时如果有job提交上来,Job server会给worker先发一个NOOP消息。
  7. Worker收到NOOP消息后,发送GRAB_JOB向Job server请求任务。
  8.  Job server把工作派发给worker。
  9. Worker干活,完事后返回WORK_COMPLETE给Job server。

处理时序图

同步

异步

集群

如果Job Server挂了该怎么办,Gearman会如何处理?我们可以同时运行多个Job Server。Clients和Workers通过配置好的Job Server,如果这个Job Server发生故障挂了,会自动故障切换到另一个可用的Job Server。Job Server建议两台以上,通过冗余来提高系统的容错性。
You probably don’t want to run too many job servers, but having two or three is a good idea for redundancy.(官网原文)
  • 高可用
启动两个job server,他们是独立的服务进程,有各自的内存队列。当一个job server进程出现故障,另一个job server可以正常调度。(worker api与client api可以完成job server故障的切换)。在任何时候我们可以关闭某个worker,即使那个worker正在处理工作任务(Gearman不会让正在被执行的job丢失的,由于worker在工作时与Job server是长连接,所以一旦worker发生异常,Job server能够迅速感知并重新派发这个异常worker刚才正在执行的工作)

  • 负载均衡

job server并不主动分派工作任务,而是由worker从空闲状态唤醒之后到job server主动抓取工作任务。

  • 可扩展

松耦合的接口和无状态的job,只需要启动一个worker,注册到Job server集群即可。新加入的worker不会对现有系统有任何的影响。

  • 分布式

gearman是分布式的任务分发框架,worker与job server,client与job server通信基于tcp的socket连接。
gearman内置内存队列,默认情况队列最大容量为300W,可以配置最大支持2^32-1,即4 294 967 295。

  • 高性能

作为Gearman的核心,Job server的是用C/C++实现的,由于只是做简单的任务派发,因此系统的瓶颈不会出在Job server上。

使用场景

  • 长时间运行的处理:图片处理,订单处理,批量邮件/通知 …
  • 要求高CPU或内存的处理:大容量的数据处理,MapReduce运算,日志聚集,视频编码
  • 分布式和并行的处理
  • 定时处理:增量更新,数据复制
  • 限制速率的FIFO处理

实例-python

Client-单任务

在没有worker启动的情况下,client并不会结束,会阻塞直到worker处理完job。

Client-多任务

同步非阻塞方式发送多个job的例子,在取得server返回结果之前,用了wait_until_jobs_completed函数来等待task中的所有job返回结果。

Worker

对worker而言,则会一直阻塞直到有新的job到来。

GearmanAdminClient

Job Server相关的API,用于监控和设置。
  • send_maxqueue(task, max_size): Sends a request to change the maximum queue size for a given task
  • end_shutdown(graceful=True): Sends a request to shutdown the connected gearman server
  • get_status():Retrieves a list of all registered tasks and reports how many items/workers are in the queue
  • get_version(): Retrieves the version number of the Gearman server
  • get_workers():Retrieves a list of workers and reports what tasks they’re operating on
  • ping_server(): Sends off a debugging string to execute an application ping on the Gearman server, return the response time

 

https://blog.csdn.net/zl18310999566/article/details/69615777?locationNum=10&fps=1

bash: netstat: command not found

apt-get install net-tools, 对于所有的linux系统都适用。

redhat系列的 Redhat、Centos、Fedora等 可以使用yum安装。

顺便说一句,wget只是个下载工具,相当于迅雷,不负责安装。

apt-get 和 yum才是包管理工具,不要傻傻分不清楚

php使用gearman进行任务分发

一、安装gearman

下载gearman源码包

1
https://launchpad.net/gearmand/+download

如: gearmand-1.1.12.tar.gz

下载php的gearman扩展包

1
http://pecl.php.net/package/gearman

如: gearman-1.1.2.tgz

安装gearman

1
2
3
4
5
> yum install boost-devel gperf libevent-devel libuuid-devel
> tar xf gearmand-1.1.12.tar.gz
> cd gearmand-1.1.12
> ./configure
> make && make install

安装gearman的php扩展(建议php版本不要过高,因为php7的gearman扩展目前还没有出来)

1
2
3
4
5
6
> yum install autoconf
> tar xf gearman-1.1.2.tgz
> cd gearman-1.1.2
> /data/php56/bin/phpize
> ./configure --with-php-config=/data/php56/bin/php-config
> make && make install

修改php.ini

1
> vi /data/php56/lib/php.ini

添加如下两项

1
2
extension_dir=/data/php56/lib/php/extensions/no-debug-zts-20131226/
extension=gearman.so

查看扩展

1
> /data/php56/bin/php -m

 

二、简单的使用gearman

gearman中请求的处理过程一般涉及三种角色:client->job->worker
其中client是请求的发起者
job是请求的调度者,用于把客户的请求分发到不同的worker上进行工作
worker是请求的处理者

比如这里我们要处理client向job发送一个请求,来计算两个数之和,job负责调度worker来具体实现计算两数之和。

首先我们编写client.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//创建一个客户端
$client = new GearmanClient();
//添加一个job服务
$client->addServer('127.0.0.1', 4730);
//doNormal是同步的,等待worker处理完成返回结果
//建议不要使用do()了
$ret = $client->doNormal('sum', serialize(array(10, 10)));
if($ret) {
    echo '计算结果:', $ret, "\n";
}

再编写worker.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
//创建一个worker
$worker = new GearmanWorker();
//添加一个job服务
$worker->addServer('127.0.0.1', 4730);
//注册一个回调函数,用于业务处理
$worker->addFunction('sum', function($job) {
    //workload()获取客户端发送来的序列化数据
    $data = unserialize($job->workload());
    return $data[0] + $data[1];
});
//死循环
while(true) {
    //等待job提交的任务
    $ret = $worker->work();
    if ($worker->returnCode() != GEARMAN_SUCCESS) {
        break;
    }
}

我们先启动gearmand服务

1
2
> mkdir -p /usr/local/var/log
> gearmand -d

运行worker文件

1
> /data/php56/bin/php /data/worker.php

再运行client文件

1
> /data/php56/bin/php /data/client.php

结果如下:

 

三、gearman异步的处理任务

这里我们client向job发送一个发送邮件的请求,不等待请求完成,继续向下执行。

client.php代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
//创建一个客户端
$client = new GearmanClient();
//添加一个job服务
$client->addServer('127.0.0.1', 4730);
//doBackground异步,返回提交任务的句柄
$ret = $client->doBackground('sendEmail', json_encode(array(
    'email' => 'test@qq.com',
    'title' => '测试异步',
    'body' => '异步执行好牛B的样子',
)));
//继续执行下面的代码
echo "我的内心毫无波动,甚至还想笑\n";
do {
    sleep(1);
    //获取任务句柄的状态
    //jobStatus返回的是一个数组
    //第一个,表示工作是否已经知道
    //第二个,工作是否在运行
    //第三和第四,分别对应完成百分比的分子与分母
    $status = $client->jobStatus($ret);
    
    echo "完成情况:{$status[2]}/{$status[3]}\n";
    if(!$status[1]) {
        break;
    }
} while(true);

worker.php代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
//创建一个worker
$worker = new GearmanWorker();
//添加一个job服务
$worker->addServer('127.0.0.1', 4730);
//注册一个回调函数,用于业务处理
$worker->addFunction('sendEmail', function($job) {
    //workload()获取客户端发送来的序列化数据
    $data = json_decode($job->workload(), true);
    //模拟发送邮件所用时间
    sleep(6);
    echo "发送{$data['email']}邮件成功\n";
});
//死循环
//等待job提交的任务
while($worker->work());  

结果如下:

 

四、gearman并行的执行多个任务

我们如何并行的计算两个数的累加和? 通过addTask添加多个任务到队列,然后进行并行计算。

client.php代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
//创建一个客户端
$client = new GearmanClient();
//添加一个job服务
$client->addServer('127.0.0.1', 4730);
//设置任务完成时的回调函数
$client->setCompleteCallback(function($task) {
    //获取由worker返回的数据
    echo $task->data(), "\n";
});
//计算1到500的累加和
//添加五个任务到队列
$client->addTask('sum', json_encode(array(1, 100)));
$client->addTask('sum', json_encode(array(100, 200)));
$client->addTask('sum', json_encode(array(200, 300)));
$client->addTask('sum', json_encode(array(300, 400)));
$client->addTask('sum', json_encode(array(400, 500)));
//运行队列中的任务,do系列不需要runTask()
$client->runTasks();

worker.php代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//创建一个worker
$worker = new GearmanWorker();
//添加一个job服务
$worker->addServer('127.0.0.1', 4730);
//注册一个回调函数,用于业务处理
$worker->addFunction('sum', function($job) {
    //workload()获取客户端发送来的序列化数据
    $data = json_decode($job->workload(), true);
    sleep(1);
    $sum = 0;
    for($ix = $data[0]; $ix < $data[1]; ++$ix) {
        $sum += $ix;
    }
    return $sum;
});
//死循环
//等待job提交的任务
while($worker->work());

我们开启5个worker工作进程,当运行客户端请求时,5个计算任务几乎是同时返回结果。

结果如下:

 

https://www.cnblogs.com/jkko123/p/6493282.html

关于GMT和UTC时间? php 关于时区 date gmdate date_default_timezone_set/get 终极答疑

关于GMT和UTC时间?

GMT+0800
Greenwich: [gri:nitf], 格林威治/格林尼治.是英国伦敦泰晤士河附近的一个小镇
Mean: adj. 自私的; n. 平均..
he is mean about money.
GMT就是格林威治平均/标准时间

什么叫本初子午线?

国外叫 : prime mer’idian.只是在中国内才叫本初-子午线. 只是国内的翻译这样怪怪的.
prime 被国人翻译为本初,
meridian本意是 经线的/全盛的, 顶点的:
he is “at the meridian of ” life.
he was “at the meridian of ” his power then.
而在中国, 子为北方, 午为南方, 子午即南北方向, 所以就把经线 “南北线” 翻译成了子午线.
因此, 子午线只是中国的叫法, 国外没有 “子午” 这种天干地支的说法的.

经纬线都是假想 (imaginary)出来的线, 赤道是天然的纬线.经线从理论上来说, 任何连接南北两极的圆线都可以作为
prime meridian, 比如中国清朝的时候,还提出了自己的prime meridian. 因此, 需要从无数的子午线中 人为
地选出一条作为”首子午线”. 因为英国皇家天文台(Greenwich天文台)最先测量经线的长度, 所以把经过 Greenwich 天文台
子午仪中心的子午线作为经线的起点, 即0度子午线, 本初子午线.

Greenwich Village = the Village不是在英国, 而是在美国纽约.

GMT 又叫 UT0
经过 “地轴摆动”修正后的时间 叫 UT1, UT2
由铯原子钟提供的时间叫 “国际原子时” :TAI
那么, 把以上的所有时间: UT0, 1,2 TAI综合精确计算 提供的 “世界协调时” 就叫 UTC .
UTC与真的太阳时的误差在0.9秒内.否则, 要由巴黎…发布 “闰秒”.
UTC: universal time coordinated.

通常在程序中, php和javascript, jquery中, 就认为GMT和UTC是一样的, 没有区别的.

而CST, 则有 多义/歧义 解释了.
可以是 美国或澳大利亚的 Central Standard Time(中部标准时间)
也可以是 中国或古巴的标准时间: China/Cuba Standard Time
而在不同的语言中, 如php, java, 和 javascript中, 解释器/编译器对CST的理解不同, 而出错.

所以, 在编程中, 尽量少用 CST.

php和js/jquery中对时间/时区的操作?

  • 获得时间的函数:
php是 $dt = time()
js/jq 是 var dt = new Date();

// php中的time()时获得GMT时间戳的秒数. 是两个GMT时间的差 : 是指当前GMT英国Greenwich 0 时区的GMT时间
// 和unix纪元的 时间差.  跟php系统设置的 时区无关!
  • 也就是说, php的time()函数, 在某一时刻, 比如现在这一时刻, 在地球上的任何地方都是相同的. 因为他们都是返回的在伦敦的Greenwich的GMT时间.而不是本地时间, 所以跟php的系统设置时区无关.

  • 对日期/时间的操作
js获得new Date()对象后, 有丰富的成员函数来进行操作, 如getMonth, getDate, getDay, toString,UTC()等等
  • 对php的时间操作, 主要是date和gmdate的格式化问题
echo date("Y-m-d H:i:s"); 
// date() 返回的是: 当前(这一刻 time()函数执行/返回时) GMT标准时间 的"本地化时间" 的自定义格式时间
// date()跟php系统设置的时区有关!
echo gmdate("Y-m-d H:i:s");
// gmdate() 返回的是: 当前(这一刻 time()函数执行/返回时) GMT标准时间  的自定义格式时间
// gmdate() 跟你现在所处的位置无关, 跟php系统设置的时区无关!

也就说, date()和gmdate()的区别, 仅仅在于 处理的时间 是不同的!
  • strtotime(“str”)
strtotime("str"): 返回 string给定时间的 所对应的GMT标准时间的 unix时间戳,
如果当前php系统的设置的时区不是GMT标准时区,则在应用strtotime(string)时,系统会自动把时间
string折算成相应的GMT标准时间, 然后计算这个时间的unix时间戳。跟php系统设置的时区有关

关于时区

地球(地球和太阳在宇宙中的相对位置,和人类历史文化的原因)是自西向东自转,东边比西边先看到太阳,
东边的时间也比西边的早. 东边时刻与西边时刻的差值不仅要以时计,而且还要以分和秒来计算,
这给人们带来不便.

为了克服时间上的混乱, 1884年 在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,
规定将全球划分为24个时区.它们是中时区(零时区)、东1-12区,西1-12区.
每个时区横跨经度15度,时间正好是1小时.最后的东、西第12区各跨经度7.5度,以东、西经180度为界.
每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时.相邻两个时区的时间相差1小时.

例如,我国东8区的时间总比泰国东7区的时间快1小时,而比日本东9区的时间慢1小时.
因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致.
凡向西走,每过一个时区,就要把表向前拨1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表向后拨1小时
(比如1点拨到2点).

实际上,世界上不少国家和地区都不严格按时区来计算时间.为了在全国范围内采用统一的时间,
一般都把某一个时区的时间作为全国统一采用的时间.
例如,我国把首都北京所在的东8区的时间作为全国统一的时间,称为北京时间.
而实际上, 我国整个面积范围共跨了5个时区.
又例如,英国、法国、荷兰和比利时等国,虽地处中时区,但为了和欧洲大多数国家时间相一致,则采用东1区的时间.

如何设置php中的时区? 两种方法

  1. 如果可以修改服务器, 就在服务器中修改phh.ini文件:
[Date]
; Defines the default timezone used by the date functions
date.timezone = Asia/Shanghai 或者 Asia/Chongqing 或者 PRC
// 注意, 没有北京 Asia/Beijng
// 更注意的是, 如果phi.ini中没有[Date] date.timezone的话, 就要自己手动添加.
  1. 如果没有权限 不能 修改服务器上的php.ini, 就用函数方法
// 一般,最好在初始化 文件中 写
// 或调用时间函数之前, 写
ini_set('date.timezone', 'Asia/Shanghai 或者Asia/Chongqing 或者PRC');
ini_set('date.timezone', 'Etc/GMT-8'); // 后面是 -8 减8 , 这个Etc是linux中的/etc目录吗? 那最好不用?
date_default_timezone_set('Asia/Shanghai 或者Asia/Chongqing 或者PRC');
使用date_default_timezone_get()就可以获得系统 当前设置的 时区.

或者, 不用设置时区, 直接手动调整时间:
在显示/输出时间时, 都统一处理:
不使用date()
统一使用gmdate(), 但是在时间上加上 8*3600 这么多秒 到time()上去!
  1. php 手册上说的: date_default_timezone_set自 PHP 5.1.0 起(此版本日期时间函数被重写了),
string date_default_timezone_get ( void )
本函数返回  "默认时区" ,使用如下“假定”的顺序: 

■用 date_default_timezone_set() 函数设定的时区(如果设定了的话) 

■TZ 环境变量(如果非空) 

■date.timezone 配置选项(如果设定了的话) 

■自己推测(如果操作系统支持) 

■如果以上选择都不成功,则返回 UTC 
string date/gmdate ( string $format [, int $timestamp ] )
返回将整数 timestamp 按照给定的格式字串而产生的字符串。
如果没有给出时间戳则使用 "本地" 当前时间。换句话说,timestamp 是可选的,默认值为 time()。 

如果没有给出时间戳则使用 “本地” 当前时间。换句话说,timestamp 是可选的,默认值为 time()。

<?php
// date_default_timezone_set('Asia/Chongqing');
// ini_set('date.timezone', 'PRC');
// ini_set('date.timezone', 'prc');

$tz = date_default_timezone_get();
echo "默认时区是: $tz";

/* 好像没有设置TZ  (timezone) 这个常量 */
if(defined('TZ')) echo TZ; 
echo "<br />";

echo time();
echo "<br />";
echo gmdate('Y-m-d H:i:s');
echo "<br />";
echo date('Y-m-d H:i:s');

?>

那么time()函数的内部实现, 是不是 通过网络去访问 Greenwich的” 相关时间服务器” 而得到的呢? 好像不是, 因为即使是断了网, 还是能够获得time()函数的值? 应该是通过 操作系统 去实现的??

原文地址

https://www.cnblogs.com/bkylee/p/5260296.html

GetMatchingProduct 根据 ASIN 值列表,返回商品及其属性列表。

Array
(
    [GetMatchingProductResult] => Array
        (
            [0] => Array
                (
                    [@attributes] => Array
                        (
                            [ASIN] => B071Y2X2D6
                            [status] => Success
                        )

                    [Product] => Array
                        (
                            [Identifiers] => Array
                                (
                                    [MarketplaceASIN] => Array
                                        (
                                            [MarketplaceId] => ATVPDKIKX0DER
                                            [ASIN] => B071Y2X2D6
                                        )

                                )

                            [AttributeSets] => Array
                                (
                                    [ItemAttributes] => Array
                                        (
                                            [Binding] => Paperback
                                            [Brand] => OXA
                                            [Color] => Navy Blue- Upgraded
                                            [Department] => boys
                                            [IsAutographed] => false
                                            [IsMemorabilia] => false
                                            [Label] => OXA
                                            [Manufacturer] => OXA
                                            [PackageDimensions] => Array
                                                (
                                                    [Height] => 4.49
                                                    [Length] => 14.88
                                                    [Width] => 11.30
                                                    [Weight] => 3.30
                                                )

                                            [PartNumber] => B-0232
                                            [ProductGroup] => Apparel
                                            [ProductTypeName] => LUGGAGE
                                            [Publisher] => OXA
                                            [Size] => Medium
                                            [SmallImage] => Array
                                                (
                                                    [URL] => http://ecx.images-amazon.com/images/I/51Ncfy-NHvL._SL75_.jpg
                                                    [Height] => 75
                                                    [Width] => 75
                                                )

                                            [Studio] => OXA
                                            [Title] => OXA Travel Hiking Camping Backpack, Duffle Backpack Bag
                                        )

                                )

                            [Relationships] => Array
                                (
                                    [VariationParent] => Array
                                        (
                                            [Identifiers] => Array
                                                (
                                                    [MarketplaceASIN] => Array
                                                        (
                                                            [MarketplaceId] => ATVPDKIKX0DER
                                                            [ASIN] => B01AJMMY9S
                                                        )

                                                )

                                        )

                                )

                            [SalesRankings] => Array
                                (
                                )

                        )

                )

            [1] => Array
                (
                    [@attributes] => Array
                        (
                            [ASIN] => B06Y2DZ1KM
                            [status] => Success
                        )

                    [Product] => Array
                        (
                            [Identifiers] => Array
                                (
                                    [MarketplaceASIN] => Array
                                        (
                                            [MarketplaceId] => ATVPDKIKX0DER
                                            [ASIN] => B06Y2DZ1KM
                                        )

                                )

                            [AttributeSets] => Array
                                (
                                    [ItemAttributes] => Array
                                        (
                                            [Binding] => Apparel
                                            [Brand] => OXA
                                            [Color] => Purple
                                            [Department] => unisex-adult
                                            [ItemDimensions] => Array
                                                (
                                                    [Height] => 2.00
                                                    [Length] => 9.00
                                                    [Width] => 9.00
                                                    [Weight] => 1.16
                                                )

                                            [Label] => OXA
                                            [Manufacturer] => OXA
                                            [Model] => BA0264
                                            [PackageDimensions] => Array
                                                (
                                                    [Height] => 2.68
                                                    [Length] => 10.39
                                                    [Width] => 10.00
                                                    [Weight] => 1.32
                                                )

                                            [PackageQuantity] => 1
                                            [PartNumber] => BA0264
                                            [ProductGroup] => Apparel
                                            [ProductTypeName] => LUGGAGE
                                            [Publisher] => OXA
                                            [Size] => 23''
                                            [SmallImage] => Array
                                                (
                                                    [URL] => http://ecx.images-amazon.com/images/I/417W8zuXuiL._SL75_.jpg
                                                    [Height] => 75
                                                    [Width] => 75
                                                )

                                            [Studio] => OXA
                                            [Title] => OXA 53L Lightweight Foldable Travel Duffel Bag With Shoes Bag
                                        )

                                )

                            [Relationships] => Array
                                (
                                    [VariationParent] => Array
                                        (
                                            [Identifiers] => Array
                                                (
                                                    [MarketplaceASIN] => Array
                                                        (
                                                            [MarketplaceId] => ATVPDKIKX0DER
                                                            [ASIN] => B06Y2DJGC8
                                                        )

                                                )

                                        )

                                )

                            [SalesRankings] => Array
                                (
                                )

                        )

                )

            [2] => Array
                (
                    [@attributes] => Array
                        (
                            [ASIN] => B06Y4GN8B8
                            [status] => Success
                        )

                    [Product] => Array
                        (
                            [Identifiers] => Array
                                (
                                    [MarketplaceASIN] => Array
                                        (
                                            [MarketplaceId] => ATVPDKIKX0DER
                                            [ASIN] => B06Y4GN8B8
                                        )

                                )

                            [AttributeSets] => Array
                                (
                                    [ItemAttributes] => Array
                                        (
                                            [Brand] => OXA
                                            [Color] => Light Blue
                                            [PackageDimensions] => Array
                                                (
                                                    [Height] => 2.52
                                                    [Length] => 10.98
                                                    [Width] => 9.02
                                                    [Weight] => 1.29
                                                )

                                            [ProductGroup] => Apparel
                                            [ProductTypeName] => LUGGAGE
                                            [SmallImage] => Array
                                                (
                                                    [URL] => http://ecx.images-amazon.com/images/I/51okCn6ecQL._SL75_.jpg
                                                    [Height] => 75
                                                    [Width] => 75
                                                )

                                            [Title] => OXA 4-Piece Packing Cube Set with Laundry and Shoes Bag, Travel Luggage Packing Organizers
                                        )

                                )

                            [Relationships] => Array
                                (
                                    [VariationParent] => Array
                                        (
                                            [Identifiers] => Array
                                                (
                                                    [MarketplaceASIN] => Array
                                                        (
                                                            [MarketplaceId] => ATVPDKIKX0DER
                                                            [ASIN] => B01KHAN85Q
                                                        )

                                                )

                                        )

                                )

                            [SalesRankings] => Array
                                (
                                )

                        )

                )

            [3] => Array
                (
                    [@attributes] => Array
                        (
                            [ASIN] => B071ZF9C2F
                            [status] => Success
                        )

                    [Product] => Array
                        (
                            [Identifiers] => Array
                                (
                                    [MarketplaceASIN] => Array
                                        (
                                            [MarketplaceId] => ATVPDKIKX0DER
                                            [ASIN] => B071ZF9C2F
                                        )

                                )

                            [AttributeSets] => Array
                                (
                                    [ItemAttributes] => Array
                                        (
                                            [Brand] => OXA
                                            [Color] => Orange
                                            [ItemDimensions] => Array
                                                (
                                                    [Height] => 20.00
                                                    [Length] => 14.00
                                                    [Width] => 7.50
                                                )

                                            [IsAutographed] => false
                                            [IsMemorabilia] => false
                                            [Label] => OXA
                                            [Manufacturer] => OXA
                                            [MaterialType] => nylon
                                            [PackageDimensions] => Array
                                                (
                                                    [Height] => 3.82
                                                    [Length] => 18.70
                                                    [Width] => 13.58
                                                    [Weight] => 2.20
                                                )

                                            [PartNumber] => BG-625
                                            [ProductGroup] => Sports
                                            [ProductTypeName] => OUTDOOR_RECREATION_PRODUCT
                                            [Publisher] => OXA
                                            [SmallImage] => Array
                                                (
                                                    [URL] => http://ecx.images-amazon.com/images/I/51Sedv35e-L._SL75_.jpg
                                                    [Height] => 75
                                                    [Width] => 75
                                                )

                                            [Studio] => OXA
                                            [Title] => OXA 40L Hiking Backpack Hydration Backpack with 2 L Water Bladder, for Outdoor Camping, Hiking, Travel, Rain Cover Included (Orange)
                                        )

                                )

                            [Relationships] => Array
                                (
                                    [VariationParent] => Array
                                        (
                                            [Identifiers] => Array
                                                (
                                                    [MarketplaceASIN] => Array
                                                        (
                                                            [MarketplaceId] => ATVPDKIKX0DER
                                                            [ASIN] => B0719Q12CN
                                                        )

                                                )

                                        )

                                )

                            [SalesRankings] => Array
                                (
                                )

                        )

                )

            [4] => Array
                (
                    [@attributes] => Array
                        (
                            [ASIN] => B01MZI9SA1
                            [status] => Success
                        )

                    [Product] => Array
                        (
                            [Identifiers] => Array
                                (
                                    [MarketplaceASIN] => Array
                                        (
                                            [MarketplaceId] => ATVPDKIKX0DER
                                            [ASIN] => B01MZI9SA1
                                        )

                                )

                            [AttributeSets] => Array
                                (
                                    [ItemAttributes] => Array
                                        (
                                            [Binding] => Sports
                                            [Brand] => OXA
                                            [Color] => Yellow
                                            [ItemDimensions] => Array
                                                (
                                                    [Height] => 2.00
                                                    [Length] => 3.00
                                                    [Width] => 2.00
                                                    [Weight] => 0.31
                                                )

                                            [IsAutographed] => false
                                            [IsMemorabilia] => false
                                            [Label] => OXA
                                            [Manufacturer] => OXA
                                            [Model] => BAG0237-1
                                            [PackageDimensions] => Array
                                                (
                                                    [Height] => 1.50
                                                    [Length] => 12.52
                                                    [Width] => 5.00
                                                    [Weight] => 0.70
                                                )

                                            [PackageQuantity] => 1
                                            [PartNumber] => BAG0237-1
                                            [ProductGroup] => Sports
                                            [ProductTypeName] => OUTDOOR_RECREATION_PRODUCT
                                            [Publisher] => OXA
                                            [Size] => 10 L
                                            [SmallImage] => Array
                                                (
                                                    [URL] => http://ecx.images-amazon.com/images/I/41BFv-Atq7L._SL75_.jpg
                                                    [Height] => 75
                                                    [Width] => 75
                                                )

                                            [Studio] => OXA
                                            [Title] => OXA Ultralight Dry Sack 10-30L
                                        )

                                )

                            [Relationships] => Array
                                (
                                    [VariationParent] => Array
                                        (
                                            [Identifiers] => Array
                                                (
                                                    [MarketplaceASIN] => Array
                                                        (
                                                            [MarketplaceId] => ATVPDKIKX0DER
                                                            [ASIN] => B01NC2MNI3
                                                        )

                                                )

                                        )

                                )

                            [SalesRankings] => Array
                                (
                                )

                        )

                )

        )

    [ResponseMetadata] => Array
        (
            [RequestId] => 1ddac799-28b4-4674-8ee3-8ff50f9a3a0d
        )

)

关于“时间”的一次探索 日期格式为 ISO 8601

最近使用 sequelize 过程中发现一个“奇怪”的问题,将某个时间插入到表中后,通过 sequelize 查询出来的时间和通过 mysql 命令行工具查询出来的时间不一样。非常困惑,于是研究了下,下面是学习成果。

基本概念

我们先来介绍一些可能当年在地理课上学习过的基本概念。

说起来,时间真是一个神奇的东西。以前人们通过观察太阳的位置来决定时间(比如:使用日晷),这就使得不同经纬度的地区时间是不一样的。后来人们进一步规定以子午线为中心,向东西两侧延伸,每 15 度划分一个时区,刚好是 24 个时区。然后因为一天有 24 小时,地球自转一圈是 360 度,360 度 / 24 小时 = 15 度/小时,所以每差一个时区,时间就差一个小时。

最开始的标准时间(子午线中心处的时间)是英国伦敦的皇家格林威治天文台的标准时间(因为它刚好在本初子午线经过的地方),这就是我们常说的 GMT(Greenwich Mean Time)。然后其他各个时区根据标准时间确定自己的时间,往东的时区时间晚(表示为 GMT+hh:mm)、往西的时区时间早(表示为 GMT-hh:mm)。比如,中国标准时间是东八区,我们的时间就总是比 GMT 时间晚 8 小时,他们在凌晨 1 点,我们已经是早晨 9 点了。

但是 GMT 其实是根据地球自转、公转计算的(太阳每天经过英国伦敦皇家格林威治天文台的时间为中午 12 点),不是非常准确,于是后面提出了根据原子钟计算的标准时间 UTC(Coordinated Universal Time)。

一般情况下,GMTUTC 可以互换,但是实际上,GMT 是一个时区,而 UTC 是一个时间标准。

可以在这里看到所有的时区:http://www.timeanddate.com/time/map/

所以,当我们“展示”某个时间时,明确时区就变得非常重要了。不然你只说现在是 2016-01-11 19:30:00,然后不告诉我时区,我其实是没法准确知道时间的(当然,我可以认为这个时间是我所在时区的当地时间)。如果你说现在是 2016-01-11 19:30:00 GMT+0800,那我就知道这个时间是东八区的时间了。如果我在东八区,那时间就是 19:30,如果我在 GMT 时区,那时间就是 11:30(减掉 8 小时)。

JavaScript 中的“时间”

我们现在来介绍下 JavaScript 中的“时间”,包括:DateDate.parseDate.UTCDate.now

注:下面的代码示例可以在 node shell 里面运行,如果你运行的时候结果和下面的不一致,那可能咱们不在一个时区:)

Date 构造器

构造时间的方法有下面几种:

new Date();           // 当前时间
new Date(value);      // 自 1970-01-01 00:00:00 UTC 经过的毫秒数
new Date(dateString); // 时间字符串
new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);

需要注意的是:构造出的日期用来显示时,会被转换为本地时间(调用 toString 方法):

> new Date()
Mon Jan 11 2016 20:15:18 GMT+0800 (CST)

打印出我写这篇文章时的本地时间。后面的 GMT+0800 表示是“东八区”,CST 表示是“中国标准时间(China Standard Time)”。

有一个很“诡异”的地方是如果我们直接使用 Date,而不是 new Date,得到的将会是字符串,而不是 Date 类型的对象:

> typeof Date()
'string'
> typeof new Date()
'object'

时间字符串

我们先说最复杂的时间字符串形式。它实际上支持两种格式:一种是 RFC-2822 的标准;另一种是 ISO 8601 的标准。我们主要介绍后一种。

ISO 8601

ISO 8601的标准格式是:YYYY-MM-DDTHH:mm:ss.sssZ,分别表示:

  • YYYY:年份,0000 ~ 9999
  • MM:月份,01 ~ 12
  • DD:日,01 ~ 31
  • T:分隔日期和时间
  • HH:小时,00 ~ 24
  • mm:分钟,00 ~ 59
  • ss:秒,00 ~ 59
  • .sss:毫秒
  • Z:时区,可以是:Z(UFC)、+HH:mm-HH:mm

这里我们主要来说下 T、以及 Z

T

T 也可以用空格表示,但是这两种表示有点不一样,T 其实表示 UTC,而空格会被认为是本地时区(前提是不通过 Z 指定时区)。比如下面的例子:

> new Date('1970-01-01 00:00:00')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> new Date('1970-01-01T00:00:00')
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)

示例 1 的空格表示法被当做了本地时区,所以显示的时间和传入的时间一致。

示例 2 的 T 被当做 UTC 时间,所以显示的时间会加上本地时区(东八区)的 8 小时偏移。实际上,1970-01-01T00:00:00 等价于 1970-01-01 00:00:00Z

Z

Z 用来表示传入时间的时区(zone),不指定并且没有使用 T 分隔而是使用空格分隔时,就按本地时区处理,比如下面的例子:

> new Date('1970-01-01T00:00:00+08:00')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> new Date('1970-01-01 00:00:00')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> new Date('1970-01-01T00:00:00+00:00')
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)

示例 1 是东八区时间,显示的时间和传入的时间一致(因为我本地时区是东八区)。

示例 2 和示例 1 结果一样,不指定时区就是本地时区。

示例 3 指定时区为 GMT 时区(偏移为 0),显示的时间会加上本地时区的偏移(8 小时)。

RFC-2822

RFC-2822 的标准格式大概是这样:Wed Mar 25 2015 09:56:24 GMT+0100。其实就是上面显示时间时使用的形式:

> new Date('Thu Jan 01 1970 00:00:00 GMT+0800 (CST)')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

除了能表示基本信息,还可以表示星期,但是一点也不容易读,不建议使用。完整的规范可以在这里查看:http://tools.ietf.org/html/rfc2822#page-14

时间戳

Date 构造器还可以接受整数,表示想要构造的时间自 UTC 时间 1970-01-01 00:00:00 经过的毫秒数。比如下面的代码:

> new Date(1000 * 1)
Thu Jan 01 1970 08:00:01 GMT+0800 (CST)

传人 1 秒,等价于:1970-01-01 00:00:01Z,显示的时间加上了本地时区的偏移(8 小时)。

多参数

最后,Date 构造器还支持传递多个参数,这种方法就没办法指定时区了,都当做本地时间处理。比如下面的代码:

> new Date(1970, 0, 1, 0, 0, 0)
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

显示时间和传入时间一致,均是本地时间。注意:月份是从 0 开始的。

Date.parse

Date.parse 接受一个时间字符串,如果字符串能正确解析就返回自 UTC 时间 1970-01-01 00:00:00 经过的毫秒数,否则返回 NaN

> Date.parse('1970-01-01 00:00:00')
-28800000

> new Date(Date.parse('1970-01-01 00:00:00'))
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> Date.parse('1970-01-01T00:00:00')
0

> new Date(Date.parse('1970-01-01T00:00:00'))
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)

示例 1,-28800000 换算后刚好是 8 小时表示的毫秒数,28800000 / (1000 * 60 * 60),我们传入的是本地时区时间,等于 UTC 时间的 1969-12-31 16:00:00,和 UTC 时间 1970-01-01 00:00:00 相差刚好 -8 小时。

示例 2,将 parse 后的毫秒数传递给构造器,最后显示的时间加上了本地时区的偏移(8 小时),所以结果刚好是 1970-01-01 00:00:00

示例 3,传入的是 UTC 时区时间,所以结果为 0。

示例 4,将 parse 后的毫秒数传递给构造器,最后显示的时间加上了本地时区的偏移(8 小时),所以结果刚好是 1970-01-01 08:00:00

Date.UTC

Date.UTC 接受的参数和 Date 构造器多参数形式一样,然后返回时间自 UTC 时间 1970-01-01 00:00:00 经过的毫秒数:

> Date.UTC(1970,0,1,0,0,0)
0

> Date.parse('1970-01-01T00:00:00')
0

> Date.parse('1970-01-01 00:00:00Z')
0

可以看出,Date.UTC 进行的是一种“绝对运算”,传入的时间就是 UTC 时间,不会转换为当地时间。

Date.now

Date.now 返回当前时间距 UTC 时间 1970-01-01 00:00:00 经过的毫秒数:

> Date.now()
1452520484343

> new Date(Date.now())
Mon Jan 11 2016 21:54:55 GMT+0800 (CST)

> new Date()
Mon Jan 11 2016 21:55:00 GMT+0800 (CST)

MySQL 中的“时间”

MySQL 中和时间相关的数据类型主要包括:YEARTIMEDATEDATETIMETIMESTAMP

DATEYEARTIME 比较简单,大概总结如下:

名称 占用字节 取值
DATE 3 字节 1000-01-01 ~ 9999-12-31
YEAR 1 字节 1901 ~ 2155
TIME 3 字节 -838:59:59 ~ 838:59:59

注:TIME 的小时范围可以这么大(超过 24 小时),是因为它还可以用来表示两个时间点之差。

DATEIME vs TIMESTAMP

我们主要来说明下 DATETIMETIMESTAMP,可以做下面的总结:

名称 占用字节 取值 受 time_zone 设置影响
DATETIME 8 字节 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
TIMESTAMP 4 字节 1970-01-01 00:00:00 ~ 2038-01-19 03:14:07

第一个区别是占用字节不同,导致能表示的时间范围也不一样。

第二个区别是 DATETIME 是“常量”,保存时就是保存时的值,检索时是一样的值,不会改变;而 TIMESTAMP 则是“变量”,保存时数据库服务器将其从time_zone 时区转换为 UTC 时间后保存,检索时将其转换从 UTC 时间转换为 time_zone 时区时间后返回。

比如,我们有下面这样一张表:

CREATE TABLE `tests` (
    `id` INTEGER NOT NULL auto_increment , 
    `datetime` DATETIME, 
    `timestamp` TIMESTAMP, 
    PRIMARY KEY (`id`)
) ENGINE=InnoDB;

连接到数据库服务器后,可以执行 SHOW VARIABLES LIKE '%time_zone%' 查看当前时区设置。类似下面这样的结果:

Variable_name Value
system_time_zone CST
time_zone SYSTEM

说明我目前时区是 CST(China Standard Time),也就是东八区。

我们尝试插入下面的数据:

INSERT INTO `tests` (`id`, `datetime`, `timestamp`) VALUES (DEFAULT, '1970-01-01 00:00:00', '1970-01-01 00:00:00');

会发现有一个报错:Error Code: 1292. Incorrect datetime value: '1970-01-01 00:00:00' for column 'timestamp'。给 timestamp 这一列提供的值不对,因为我们尝试插入 1970-01-01 00:00:00 时,数据库服务器会根据 time_zone 的设置将其转换为 UTC 时间,也就是 1969-12-31 16:00:00,而这个值明显超过了 TIMESTAMP 类型的范围。

我们换个大一点的值:

INSERT INTO `tests` (`id`, `datetime`, `timestamp`) VALUES (DEFAULT, '2000-01-01 00:00:00', '2000-01-01 00:00:00');

这次就成功插入了。

再次检索时结果也是正确的(数据库服务器将值从 UTC 时间转换为 time_zone 设置的时区时间):

SELECT * FROM sample.tests;

返回:

id datetime timestamp
1 2000-01-01 00:00:00 2000-01-01 00:00:00

如果我们先将 time_zone 设置为一个不同的值后再进行检索就会发现不同的结果:

SET time_zone = '+00:00';
SELECT * FROM sample.tests;

返回:

id datetime timestamp
1 2000-01-01 00:00:00 1999-12-31 16:00:00

可以看到 datetime 列值没有受 time_zone 设置的影响,而 timestamp 列值却改变了。数据库服务器将其从 UTC 时区转换为 time_zone 时区的时间(首先 2000-01-01 00:00:00 在上面进行插入时根据 time_zone 被转换为了 1999-12-31 16:00:00,此次检索时 time_zone 被设置为 +00:00,转换回来刚好就是 1999-12-31 16:00:00)。

那这两种类型怎么选择呢?建议优先使用 DATETIME,表示范围大、不容易受服务器的设置影响。

在 JavaScript 和 MySQL 间转换

分别说明了 JavaScript 和 MySQL 中的“时间”后,我们来聊聊 ORM 框架一般都是怎么样在两者间进行正确、合适的转换来避免混乱的。下面的说明将基于 sequelize 框架来解释,主要是一种思路,其他的框架可以阅读框架提供的文档或是源码。

sequelize 实际上有一个 timezone 的配置,默认是 +00:00http://sequelize.readthedocs.org/en/latest/api/sequelize/)。这个 timezone 有下面的用途:

  • 建立数据库连接时,执行 SET time_zone = opts.timezone
  • MySQL 的时间类型和 JavaScript 的时间类型的互相转换

第一个用途很简单,体现在源码里就是执行一个 SQL 语句:

connection.query("SET time_zone = '" + self.sequelize.options.timezone + "'"); /* jshint ignore: line */

第二个用途主要体现在两个地方:1)在 JavaScript 中调用 ORM 方法进行插入、更新时,需要将 Date 类型转为正确的 SQL 语句;2)从 MySQL 服务器查询数据时,需要将数据库查询到的值转换为 JavaScript 中的 Date 类型。下面我们分别来看一看。

JavaScript -> MySQL

这个转换的核心代码如下:

SqlString.dateToString = function(date, timeZone, dialect) {
  if (moment.tz.zone(timeZone)) {
    date = moment(date).tz(timeZone);
  } else {
    date = moment(date).utcOffset(timeZone);
  }

  if (dialect === 'mysql' || dialect === 'mariadb') {
    return date.format('YYYY-MM-DD HH:mm:ss');
  } else {
    // ZZ here means current timezone, _not_ UTC
    return date.format('YYYY-MM-DD HH:mm:ss.SSS Z');
  }
};

代码逻辑如下:

  1. 检查 timeZone 是否存在,如果存在(存在指的是类似 America/New_York 这样的表示法),调用 tz 设置 date 的时区。
  2. 如果不存在(类似 +00:00-07:00 这样的表示法),调用 utcOffset 设置 date 的相对 UTC 的时区偏移。
  3. 最后使用上面设置的时区偏移将其 format 成 MySQL 需要的 YYYY-MM-DD HH:mm:ss 格式。

举两个例子。

如果 timeZone 等于 +00:00,date 等于 new Date('2016-01-12 09:46:00'),到 UTC 的偏移等于 (timeZone – 本地时区) + timeZone:(00:00 - 08:00) + 00:00 = -08:00,即 2016-01-12 09:46:00-08:00,于是 format 后的结果是 2016-01-12 01:46:00

如果 timeZone 等于 +08:00,date 等于 new Date('2016-01-12 09:46:00'),到 UTC 的偏移等于 (timeZone – 本地时区) + timeZone:(08:00 - 08:00) + 08:00 = 08:00,即 2016-01-12 09:46:00+08:00。于是 format 后的结果是 2016-01-12 09:46:00

如果 timeZone 等于 Asia/Shanghai,结果也会是 2016-01-12 09:46:00,和 +08:00 等价。

sequelize 的 timezone 默认是 +00:00,所以,我们在 JavaScript 中的时间最后应用到数据库中都会被转换成 UTC 的时间(比实际的时间早 8 小时)。

MySQL -> JavaScript

这个转换过程实际上是更底层的 node-mysql 库来实现的。核心代码如下:

  switch (field.type) {
    case Types.TIMESTAMP:
    case Types.DATE:
    case Types.DATETIME:
    case Types.NEWDATE:
      var dateString = parser.parseLengthCodedString();
      if (dateStrings) {
        return dateString;
      }
      var dt;

      if (dateString === null) {
        return null;
      }

      var originalString = dateString;
      if (field.type === Types.DATE) {
        dateString += ' 00:00:00';
      }

      if (timeZone !== 'local') {
        dateString += ' ' + timeZone;
      }

      dt = new Date(dateString);
      if (isNaN(dt.getTime())) {
        return originalString;
      }

      return dt;
   // 更多代码...
}

处理过程大概是这样:

  1. parser 将服务器返回的二进制数据解析为时间字符串
  2. 如果配置了强制返回字符串 dateStrings 而不是转换回 Date 类型,直接返回 dateString
  3. 如果字段类型是 DATE,时间字符串的时间部分统一为 00:00:00
  4. 如果配置的 timeZone 不是 local(本地时区),时间字符串加上时区信息
  5. 将时间字符串传给 Date 构造器,如果构造出的时间不合法,返回原始时间字符串,否则返回时间对象

默认情况下,sequelize 在进行连接时传递给 node-mysql 的 timeZone+00:00,所以,第 4 步的时间字符串会是类似这样的值 2016-01-12 01:46:00+00:00,而这个值传递给 Date 构造器,在显示时转换回本地时区时间,就变成了 2016-01-12 09:46:00(比数据库中的时间晚 8 小时)。

一个例子

在使用 sequelize 定义模型时,其实是没有 TIMESTAMP 类型的,sequelize 只提供了一个 Sequelize.DATE 类型,生成建表语句时被转换为 DATETIME

如果是在旧表上定义模型,而这张旧表刚好有 TIMESTAMP 类型的列,对 TIMESTAMP 类型的列定义模型时还是可以使用 Sequelize.DATE,对操作没有任何影响。但是 TIMESTAMP 是受 time_zone 设置影响的,这会引起一些困惑。下面我们来看一个例子。

sequelize 默认将 time_zone 设置为 +00:00,当我们执行下面代码时:

Test.create({
    'datetime': new Date('2016-01-10 20:07:00'),
    'timestamp': new Date('2016-01-10 20:07:00')
  });

会进行上面提到的 JavaScript 时间到 MySQL 时间字符串的转换,生成的 SQL 其实是(时间被转换为了 UTC 时间,比本地时间早了 8 小时):

INSERT INTO `tests` (`id`,`datetime`,`timestamp`) VALUES (DEFAULT,'2016-01-10 12:07:00','2016-01-10 12:07:00');

当我们执行 Test.findAll() 来查询数据时,会进行上面提到的 MySQL 时间到 JavaScript 时间的转换,其实就是返回这样的结果(显示时时间从 UTC 时间转换回了本地时间):

> new Date('2016-01-10 12:07:00+00:00')
Sun Jan 10 2016 20:07:00 GMT+0800 (CST)

和我们插入时的时间是一致的。

如果我们通过 MySQL 命令行来查询数据时,发现其实是这样的结果:

id datetime timestamp
1 2016-01-10 12:07:00 2016-01-10 20:07:00

这很好理解,因为我们数据库服务器的 time_zone 默认是东八区,TIMESTAMP 是受时区影响的,查询时被数据库服务器从 UTC 时间转换回了 time_zone 时区时间;DATETIME 不受影响,还是 UTC 时间。

如果我们先执行 SET time_zone = '+00:00',再进行查询,那结果就都会是 UTC 时间了。所以,不要以为数据出错了哦。

总结下就是,sequelize 会将本地时间转换为 UTC 时间后入库,查询时再将 UTC 时间转换为本地时间。这能达到最好的兼容性,存储总是使用 UTC 时间,展示时应用端自己转换为本地时区时间后显示。当然这个的前提是数据类型选用 DATETIME

兼容老数据

这里要说的最后一个问题是基于旧表定义 sequelize 模型,并且表中时间值插入时没有转换为 UTC 时间(全部是东八区时间),而且 DATETIMETIMESTAMP 混用,该怎么办?

在默认配置下,情况如下:

查询 DATETIME 类型数据时,时间总是会晚 8 小时。比如,数据库中某条老数据的时间是 2012-01-01 01:00:00(已经是本地时间了,因为没转换),查询时被 sequelize 转换为 new Date('2012-01-01 01:00:00+00:00'),显示时转换为本地时间 2012-01-01 09:00:00,结果显然不对。

查询 TIMESTAMP 类型数据时,时间是正确的。这是因为 TIMESTAMPtime_zone 影响,sequelize 默认将其设置为 +00:00,查询时数据库服务器先将时间转换到 time_zone 设置的时区时间,由于没有时区偏移,刚好查出来的就是数据库中的值。比如:2012-01-01 00:00:00(注意这个值是 UTC 时间),sequelize 将其转换为 new Date('2012-01-01 00:00:00+00:00'),显示时转换为本地时间 2012-01-01 08:00:00,刚好“侥幸”正确。

新插入的数据 sequelize 会进行上一部分说的双向转换来保证结果的正确。

维持默认配置显然导致查询 DATETIME 不准确,解决方法就是将 sequelize 的 timezone 配置为 +08:00。这样一来,情况变成下面这样:

查询 DATETIME 类型数据时,时间 2012-01-01 01:00:00 被转换为 new Date('2012-01-01 01:00:00+08:00'),显示时转换为本地时间 2012-01-01 01:00:00,结果正确。

查询 TIMESTAMP 类型数据时,由于 time_zone 被设置为了 +08:00,数据库服务器先将库中 UTC 时间 2011-01-01 00:00:00 转换到 time_zone 时区时间(加上 8 小时偏移)为 2011-01-01 08:00:00,sequelize 将其转换为 new Date('2011-01-01 08:00:00+08:00'),显示时转换为本地时间 2011-01-01 08:00:00,结果正确。

插入、更新数据时,所有 JavaScript 时间会转换为东八区时间入库。

这样带来的问题是,所有入库时间都是东八区时间,如果有其他应用的时区不是东八区,那就需要自己基于东八区时间计算偏移并转换时间后显示了。

https://segmentfault.com/a/1190000004292140

参考资料

一不小心写的有点长了,下面列出参考资料供大家进一步学习: