公司在做im的时候碰到了这么一个问题:服务器返回的时间是UTC格式的2017-01-05T02:48:52.746Z。而本地的时区是GMT+8.
所以我需要对时间进行转化。
这里对时区进行简单的介绍一下,便于下面的理解:
整个地球分为二十四时区,每个时区都有自己的本地时间,时区差东为正,西为负。在此,把东八区时区差记为 +0800。
UTC + 时区差 = 本地时间
UTC + (+0800) = 本地(北京)时间
那么,UTC = 本地时间(北京时间))- 0800
如果结果是负数就意味着是UTC前一天,把这个负数加上2400就是UTC在前一天的时间。例如,本地 (北京)时间是 0432 (凌晨四点三十二分),那么,UTC就是 0432 - 0800 = -0368,负号意味着是前一天, -0368 + 2400 = 2032,既前一天的晚上八点三十二分。
这里不需要纠结什么是UTC(世界协调时间)什么事GMT(格林威治标准时间),它们只是计算时间的方式不一样,相对来说UTC比较准确一点,在本文中我认为它们是一样的。
闲话就说到这里,我们看看java对时间的处理。
这里我们还是以服务器给我传的这个时间来举例子:
UTC:2017-01-05T02:48:52.746Z
根据:UTC + (+0800) = 本地(北京)时间来计算
GMT+8:Thu Jan 05 10:48:52 GMT+08:00 2017
看一下代码:
我故意截的一张图,主要是为了看到AS上面的一个警告(这个后面来看)。
运行结果:
01-05 11:40:33.224 5288-5288/tbw.eage.rxjava E/TAG:Thu Jan 05 10:48:52 GMT+08:00 2017
有的就说了,何必搞得真么复杂,就要服务器给你一个时间戳。
下面我们看看时间戳:
时间戳:1483584532746
看一下代码:
private void testTimeStamp(){
Date date = new Date(1483584532746L);
Log.e("TAG",date+"");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStrTmp = dateFormat.format(date);
Log.e("TAG",dateStrTmp);
SimpleDateFormat dateFormat1 = new SimpleDateFormat("EEE MMM dd hh:mm:ss z yyyy", Locale.US);
dateFormat1.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStrTmp1 = dateFormat1.format(date);
Log.e("TAG",dateStrTmp1);
}
运行结果:
01-05 15:18:45.212 4673-4673/tbw.eage.rxjava E/TAG:Thu Jan 05 10:48:52 GMT+08:00 2017
01-05 15:18:45.214 4673-4673/tbw.eage.rxjava E/TAG: 2017-01-05T02:48:52.746Z
01-05 15:18:45.214 4673-4673/tbw.eage.rxjava E/TAG: Thu Jan 05 02:48:52 GMT+00:00 2017
这个结果就非常好了。
我们来分析分析上面2中方法。
首先我要指出的是我的手机的时区是:GMT+8:00
第一种情况:用UTC字符串传输,由于字符串本身是没有时区的概念的,我为DateFormat设置时区是GMT(因为服务器传过来的时间是UTC的),所以这时字符串"2017-01-05T02:48:52.746Z"就有了时区,并且是GMT.所以当转换成时间的时候要加上8个小时(因为系统当前时区是GMT+8)。所以结果就是Thu Jan 05 10:48:52 GMT+08:00 2017
第二种情况:用时间戳传输,Date代表一个时间点,其值为距格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000的毫秒数。所以它是没有时区和Locale概念的。正因为其与时区的无关性,才使得我们的存储数据(时间)是一致的(时区一致性)。但通过Date.getYear()/Date.getMonth()/...方法获取到的读数是有时区,代表的是本地时间。简单说呢,Date内部是绝对时间,但是其时间的读数是有时区。所以我简单来说用时间戳生成的时间是有时区,就是系统的当前时区。所以我们看到第一条输出是GMT+8的时间。后面2条分别是按GMT时区格式化之后的输出。
为了证明Date的存储的是一个绝对时间点(就是时间戳),但是读出的时间是有时区的,我把手机的时区换成GMT的输出:
01-05 07:42:58.828 5318-5318/tbw.eage.rxjava E/TAG:Thu Jan 05 02:48:52 GMT+00:00 2017
01-05 07:42:58.828 5318-5318/tbw.eage.rxjava E/TAG: 2017-01-05T02:48:52.746Z
01-05 07:42:58.829 5318-5318/tbw.eage.rxjava E/TAG: Thu Jan 05 02:48:52 GMT+00:00 2017
可以看到第一条输出变了,后面2条格式化的输出没有改变。
所以传输时间戳是最好的选择。唉~~~
但是有的公司就是不传时间戳,比如我的公司。这是就需要我们学会转化。
上面提供2中方法。
利用Calendar转化。
Calendar不像SimpleDateFormat那么复杂,但是它可以设置时区,设置完时区后,我们不能用calendar.getTime()来直接获取Date日期,因为此时的日期与一开始setTime时是相同值,为什么呢?因为Date是没有时区和Local概念的。要想获取某时区的时间,正确的做法是用calendar.get()方法,那么我们怎么获得Date类型的日期呢?
private void testTimeStamp() {
Date date = new Date(1483584532746L);
Log.e("TAG", date + "");
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
calendar.setTime(date);
Log.e("TAG", calendar.getTime() + "");
Calendar calendar2 = Calendar.getInstance();
calendar2.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND));
Log.e("TAG", "" + calendar2.getTime());
}
运行结果:
01-05 16:05:41.435 5575-5575/tbw.eage.rxjava E/TAG: Thu Jan 05 10:48:52 GMT+08:00 2017
01-05 16:05:41.436 5575-5575/tbw.eage.rxjava E/TAG: Thu Jan 05 10:48:52 GMT+08:00 2017
01-05 16:05:41.436 5575-5575/tbw.eage.rxjava E/TAG: Thu Jan 05 02:48:52 GMT+08:00 2017
回头来看看上面用SimpleDateFormat转化的方法,其实呢DateFormat中就持有一个Calendar对象,SimpleDateFormat#setTimeZoone()内部就是调用的Calendar#setTimeZoone()方法。所以呢这一切就真相大白了。
也许有人还是有点糊涂。
看下面一段代码:
public static void testUtc() {
// String dateStr = "2017-01-05T02:48:52.746Z";
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.clear();
calendar.set(2017, 0, 5, 2, 48, 52);
calendar.set(Calendar.MILLISECOND, 746);
Log.e("TAG",String.valueOf(calendar.getTime()));
Log.e("TAG",String.valueOf(calendar.getTime().getTime()));
}
运行结果:
01-05 17:03:29.599 7199-7199/tbw.eage.rxjava E/TAG: Thu Jan 05 10:48:52 GMT+08:00 2017
01-05 17:03:29.599 7199-7199/tbw.eage.rxjava E/TAG: 1483584532746
再说一个时区(TimeZone)的方法TimZone#getRawOffset(),获取的是当前时区到UTC的偏移,单位:毫秒,东正西负
有了它,时区想怎么换怎么换。
public static Date changeTimeZone(Date date, TimeZone oldZone, TimeZone newZone) {
Date dateTmp = null;
if (date != null) {
int timeOffset = oldZone.getRawOffset() - newZone.getRawOffset();
dateTmp = new Date(date.getTime() - timeOffset);
}
return dateTmp;
}
最后讲讲上面截图的那个警告。
警告就是说想要你给一个Local给SimpleDateFormat。
其实Local就是本地化的意思。其实用心的童鞋在上面的例子代码中就看出来它的用法。我再把上面的代码清下来看看。
private void testLocal(){
Date date = new Date(1483584532746L);
SimpleDateFormat dateFormat = new SimpleDateFormat("G EEE MMM dd a hh:mm:ss z yyyy");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStrTmp = dateFormat.format(date);
Log.e("TAG","默认:"+dateStrTmp);
SimpleDateFormat dateFormat_ = new SimpleDateFormat("G EEE MMM a dd hh:mm:ss z yyyy",Locale.CHINA);
dateFormat_.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStrTmp_ = dateFormat_.format(date);
Log.e("TAG","local.china:"+dateStrTmp_);
SimpleDateFormat dateFormat1 = new SimpleDateFormat("G EEE MMM a dd hh:mm:ss z yyyy", Locale.US);
dateFormat1.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStrTmp1 = dateFormat1.format(date);
Log.e("TAG","local.us:"+dateStrTmp1);
SimpleDateFormat dateFormat2 = new SimpleDateFormat("G EEE MMM a dd hh:mm:ss z yyyy", Locale.KOREAN);
dateFormat2.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateStrTmp2 = dateFormat2.format(date);
Log.e("TAG","local.korean:"+dateStrTmp2);
}
运行结果:
01-05 17:29:52.078 7844-7844/tbw.eage.rxjava E/TAG: 默认:公元 周四 1月 05 上午 02:48:52 GMT+00:00 2017
01-05 17:29:52.079 7844-7844/tbw.eage.rxjava E/TAG: local.china:公元 周四 1月 上午 05 02:48:52 GMT+00:00 2017
01-05 17:29:52.079 7844-7844/tbw.eage.rxjava E/TAG: local.us:AD Thu Jan AM 05 02:48:52 GMT+00:00 2017
01-05 17:29:52.079 7844-7844/tbw.eage.rxjava E/TAG: local.korean:서기 목 1월 오전 05 02:48:52 GMT+00:00 2017
细观察结果发现,并不是所有的表示都有明显的区别。
但是G(年代),E(星期中的天数,周几),M(月份),a(上午还是下午)
从上面的表示方法可以看出最好使用Local.US,这样所以计算机都可以识别,防止编码问题。
附录:
字母 | 日期或时间元素 | 表示 | 示例 |
---|---|---|---|
G |
Era 标志符 | Text | AD |
y |
年 | Year | 1996 ; 96 |
M |
年中的月份 | Month | July ; Jul ; 07 |
w |
年中的周数 | Number | 27 |
W |
月份中的周数 | Number | 2 |
D |
年中的天数 | Number | 189 |
d |
月份中的天数 | Number | 10 |
F |
月份中的星期 | Number | 2 |
E |
星期中的天数 | Text | Tuesday ; Tue |
a |
Am/pm 标记 | Text | PM |
H |
一天中的小时数(0-23) | Number | 0 |
k |
一天中的小时数(1-24) | Number | 24 |
K |
am/pm 中的小时数(0-11) | Number | 0 |
h |
am/pm 中的小时数(1-12) | Number | 12 |
m |
小时中的分钟数 | Number | 30 |
s |
分钟中的秒数 | Number | 55 |
S |
毫秒数 | Number | 978 |
z |
时区 | General time zone | Pacific Standard Time ; PST ; GMT-08:00 |
Z |
时区 | RFC 822 time zone | -0800 |
表格来源于网络