您的位置:首页 > 编程语言 > Java开发

java编程中遇到的时区与时间问题总结

2016-03-19 10:04 411 查看

java编程中遇到的时区与时间问题总结

发表回复

最近在编程中遇到了时间与时区相关的问题,整理在这里

我的程序是一个在hadoop上运行的分布式程序,从mysql数据库中取数据,经过处理之后输出

一.基本概念

时区:timezone1884年国际经线会议规定,全球按经度分为24个时区,每区各占经度15°。

以本初子午线为中央经线的时区为零时区,由零时区向东、西各分12区,东、西12区都是半时区,共同使用180°经线的地方时。

CST:ChinaStandardTimeUTC+8:00中国标准时间(北京时间),在东八区

UTC:UniversalTimeCoordinated,世界协调时间,又称世界标准时间、世界统一时间。UTC提供了一种与时区无关(或非特定于时区)的时间。

世界上的所有时区都可以表示为UTC加上或减去一个偏移量。

因此,UTC是0时区的时间,如北京为早上八点(东八区),UTC时间就为零点,时间比北京时晚八小时

GMT:GreenwichMeanTime格林威治标准时间,指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。

Unixtimestamp:Unix时间戳,或称Unix时间(Unixtime)、POSIX时间(POSIXtime),是一种时间表示方式,

定义为从格林威治时间(UTC/GMT的午夜)1970年01月01日00时00分00秒起至现在的总秒数。

可以这么说:

UTC和GMT几乎是同一概念,两者的区别是GMT是一个天文上的概念,UTC是基于原子钟。

GMT=UTC

GMT+8=UTC+8=CST

UTC+时间差=本地时间(时间差东为正,西为负,东八区记为+0800)

二.从数据库取数据的过程

1
mysql>

select
auction_id,starts

from
auctions
where

auction_id=88888;
2
+
-------------+---------------------+
3
|auction_id|starts|
4
+
-------------+---------------------+
5
|88888|2011-10-2420:32:58|

6
+
-------------+---------------------+
7
1row
in

set
(0.00sec)
8
9
mysql>showcolumns
from

auctions;
10
+
--------------------------------+---------------+------+-----+---------+-------+
11
|Field|Type|
Null

|
Key

|
Default
|Extra|
12
+
--------------------------------+---------------+------+-----+---------+-------+
13
|starts|datetime|YES|MUL|
NULL

||
可见:数据库的时间字段starts存的是datetime类型,它是一个和时区相关的string(显然:string都是和时区相关的)

而且数据库是按照CST时区存的时间

程序中从数据库取数据用的sql语句:

1
mysql>
select

auction_id,DATE_FORMAT(starts,
'%Y%m%d%H%i%S'
)
from
auctions
where

auction_id=88888;
2
+
-------------+------------------------------------+
3
|auction_id|DATE_FORMAT(starts,
'%Y%m%d%H%i%S'
)|
4
+
-------------+------------------------------------+
5
|88888|20111024203258|
6
+
-------------+------------------------------------+
7
1row
in

set
(0.00sec)
这里只是简单的用DATE_FORMAT函数把datetime类型的starts字段转换为我们需要的格式%Y%m%d%H%i%S而已

三、java代码

看这样一段转换时间的java代码:

1
//将字符串时间转化为秒数(yyyyMMddHHmmss)
2
static

public
long
getUnixTimestamp(StringsrcTime)
3
{
4
SimpleDateFormatsdf=
new
SimpleDateFormat(
"yyyyMMddHHmmss"
);
5
Dateresult_date;
6
long

result_time=
0
;
7
try

{
8
result_date=sdf.parse(srcTime);
9
//返回的是毫秒数故除以1000
10
result_time=result_date.getTime()/
1000
;
11
}
catch
(Exceptione){

12
//出现异常时间赋值为20000101000000
13
result_time=
946684800
;
14
}
15
return

result_time;
16
}
计算结果:

1
getUnixTimestamp(
"20111204212224"
)=1323004944
2
3
说明:java.util.Date中的getTime函数定义如下:
java.util.Date代表一个时间点,其值为距公元1970年1月1日00:00:00的毫秒数。所以它是没有时区和Locale概念的。

publiclonggetTime()返回自1970年1月1日00:00:00GMT以来此Date对象表示的毫秒数

java中通过如下形式取得当前时间点:

1
Datenow=newDate();//这个时间点与本地系统的时区无关
而正因为其与时区的无关性,才使得我们的存储数据(时间)是一致的(时区一致性)。

一般的我们将now存储于数据库中,当我们需要展现数据时,将now格式化成想要的格式,如:2011-12-0421:22:24

而这个功能一般交由java.text.DateFormat来实现。例如:

1
SimpleDateFormatsdf=
new

SimpleDateFormat(
"yyyy-MM-ddHH:mm:ss"
);
2
Stringsnow=sdf.format(now);
我们发现snow是带时间(如2011-12-0421:22:24)的字符串,那么2011-12-0421:22:24这个时间是哪个时区的时间呢?

默认情况下,SimpleDateFormat取得本地系统的时区(我的时区为GMT+8北京),然后按照pattern(”yyyy-MM-ddHH:mm:ss”)格式化now,此时输出的就是GMT+8区的时间了。

如果想支持国际化时间,则先指定时区,然后再格式化date数据。例如:

1
SimpleDateFormatsdf=
new

SimpleDateFormat(
"yyyy-MM-ddHH:mm:ss"
);
2
sdf.setTimeZone(TimeZone.getTimeZone(
"GMT+8"
));
3
Stringsnow=sdf.format(now);
//snow=2011-12-0421:22:24
4
sdf.setTimeZone(TimeZone.getTimeZone(
"GMT+7"
));
5
Stringsnow2=sdf.format(now);
//snow2=2011-12-0420:22:24(可见:东八区比东七区早一个小时)
另外,你可以通过如下代码修改本地时区信息:

1
TimeZone.setDefault(TimeZone.getTimeZone(
"GMT+8"
));
在windows操作系统中,是通过桌面右下角,也可以指定操作系统的时区。

在linux系统中,通过如下命令可以得到当前时区

1
[admin@localhost]$
date

-R
2
Sun,04Dec201122:49:00+0800
四、结论:

getTime()返回的已经是一个UTC的unixtimestamp秒数了,与时区无关;而转换为字符串后,就和时区相关了

对于这个秒数,不同时区的人,按照自己所在的时区去解析,就可以得到正确的时间了

1
[admin@localhost]$
date

-d@1323004944
2
2011年12月04日星期日21:22:24CST
3
[admin@localhost]$
date

-d@1323004944-u
4
2011年12月04日星期日13:22:24UTC
对于涉及到时间转换的程序来说,如果代码里面没有强行指定时区,那就会依赖于操作系统的时区。

特别是对于分布式程序,如果不同机器上系统时区不一样,那就会出现不一致的数据了!

五、对unixtimestamp和时区概念的曲解和误用

由于历史原因,发现程序中有这样一段代码:

1
//将字符串时间转化为秒数(yyyyMMddHHmmss),有8个小时的时差
2
static

public
long

getLongTime(StringsrcTime)
3
{

4
SimpleDateFormatsdf=
new
SimpleDateFormat(
"yyyyMMddHHmmss"
);
5
Dateresult_date;
6
long

result_time=
0
;
7
try

{
8
result_date=sdf.parse(srcTime);
9
//返回的是毫秒数故除以1000
10
result_time=result_date.getTime()/
1000

+
8
*

3600
;
//这里加了八个小时
11
}

catch
(Exceptione){
12
//出现异常时间赋值为20000101000000
13
result_time=
946684800
;
14
}

15
16
return

result_time;
17
}
计算结果:

1
getUnixTimestamp(
"20111204212224"
)=1323033744
显然,这个时间比上面通过getUnixTimestamp(“20111204212224”)=1323004944得到的时间多了8个小时

1
1323033744-1323004944=28800=8*3600=8h
如果用户将得到的1323033744按照自己所在的时区解析后得到的结果是:

1
[admin@localhost]$
date

-d@1323033744
2
2011年12月05日星期一05:22:24CST
得到了一个完全错误的结果!

但如果用户将这个1323033744按照UTC时区来解析后得到的结果是:

1
[admin@localhost]$
date

-d@1323033744-u
2
2011年12月04日星期日21:22:24UTC
为了方便对比,把1323004944的解析结果也拿来对比

1
[admin@localhost]$
date

-d@1323004944
2
2011年12月04日星期日21:22:24CST
3
[admin@localhost]$
date

-d@1323004944-u
4
2011年12月04日星期日13:22:24UTC
可以看到,这个代码中得到的秒数时间是比UTC的unixtimestamp秒数多了八个小时

这个时间1323033744可以理解为北京时区得到的秒数,但是不是unixtimstamp时间!

unixtimestamp秒数是与时区无关的,不管是在哪个时区得到的unixtimestamp都是一样的

我们可以验证一下,用北京时间“20111204212224”减去“19700000000000”得到的秒数,就是1323033744

1
SimpleDateFormatdf=
new

SimpleDateFormat(
"yyyy-MM-ddHH:mm:ss"
);
2
java.util.Dateend=df.parse(
"2011-12-0421:22:24"
);
3
java.util.Datestart=df.parse(
"1970-01-0100:00:00"
);
4
long
delta=(end.getTime()-start.getTime())/
1000
;
5
System.out.println(
"delta="

+delta);
//delta=1323033744
或者用shell命令来求时间差

1
[admin@localhost]$
date

-d
"2011-12-0421:22:24"
+%s
2
1323004944
3
[admin@localhost]$
date

-d
"1970-01-010:0:0"
+%s
4
-28800
5
[admin@localhost]$
date

-d
"2011-12-0421:22:24"
+%s-u
6
1323033744
7
[admin@localhost]$
date

-d
"1970-01-010:0:0"
+%s-u
8
0
1323004944+28800=1323033744

对于东八区的人来说,1323033744这个时间按照UTC时间可以解析正确。不能按照自己所在的时区去解析,不然就是错的

但是如果是东七区的人呢?需要按照UTC时间解析后,自己去减1个小时的时差,sougly!

所以,用户在解析1323033744这个数据的时候:

(1)按照UTC时间来解析得到北京时间,然后根据时间差换算成自己所在时区的时间

(当然,一般都是在北京时区了,所以不用换算,按UTC时间来解析就能得到正确的时间)

(2)将这个时间减去8小时得到unixtimestamp,然后按照自己所在的时区去解析就可以了

总结:这段代码是对unixtimestamp和时区的曲解和误用。

六、从数据库获取unixtimestamp时间

其实从数据库是可以直接获取到unixtimestamp时间的

查看源代码打印帮助

1
mysql>
select

auction_id,unix_timestamp(starts)fromauctionswhereauction_id=88888;
2
+-------------+------------------------+
3
|auction_id|unix_timestamp(starts)|
4
+-------------+------------------------+
5
|88888|1319459578|
6
+-------------+------------------------+
7
1row

in
set

(0.02sec)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: