java对日期时间的处理

时间:2022-01-18 22:05:25

公司在做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

看一下代码:

java对日期时间的处理

我故意截的一张图,主要是为了看到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

我自己创建一个2017年1月5日 2点48分52秒746毫秒的UTC时间。输出了它的Date和它的时间戳。

再说一个时区(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 199696
M 年中的月份 Month JulyJul07
w 年中的周数 Number 27
W 月份中的周数 Number 2
D 年中的天数 Number 189
d 月份中的天数 Number 10
F 月份中的星期 Number 2
E 星期中的天数 Text TuesdayTue
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 TimePSTGMT-08:00
Z 时区 RFC 822 time zone -0800

表格来源于网络