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

(摘自http://www.cnblogs.com/flying5/archive/2011/12/05/2276578.html)

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

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

一. 基本概念

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

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

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

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

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

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

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

  Unix timestamp :Unix时间戳,或称Unix时间(Unix time)、POSIX时间(POSIX time),是一种时间表示方式,

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

  可以这么说:

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

  GMT=UTC

  GMT + 8 = UTC + 8 = CST

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

 

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

?
  mysql>selectauction_id,startsfromauctions where auction_id=88888;
+-------------+---------------------+
| auction_id  | starts |
+-------------+---------------------+
| 88888       | 2011-10-24 20:32:58 |
+-------------+---------------------+
1 rowinset (0.00 sec)
 
mysql> show columnsfromauctions;
+--------------------------------+---------------+------+-----+---------+-------+
| Field | Type |Null| Key|Default | Extra |
+--------------------------------+---------------+------+-----+---------+-------+
|starts | datetime | YES | MUL |NULL| |

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

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

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

?
mysql>selectauction_id,DATE_FORMAT(starts,'%Y%m%d%H%i%S')fromauctions where auction_id=88888;
+-------------+------------------------------------+
| auction_id  | DATE_FORMAT(starts,'%Y%m%d%H%i%S') |
+-------------+------------------------------------+
| 88888       | 20111024203258 |
+-------------+------------------------------------+
1 rowinset (0.00 sec)

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

 

三、Java代码

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

?
// 将字符串时间转化为秒数(yyyyMMddHHmmss)
staticpubliclong getUnixTimestamp(String srcTime)
{        
          SimpleDateFormat sdf =newSimpleDateFormat("yyyyMMddHHmmss");
          Date result_date;
          longresult_time =0;
          try
                   result_date = sdf.parse(srcTime);
                   //返回的是毫秒数故除以1000
                   result_time = result_date.getTime()/1000;
          }catch(Exception e) { 
                   //出现异常时间赋值为20000101000000
                   result_time =946684800;
          }
          returnresult_time;
 }

  计算结果:

?
getUnixTimestamp("20111204212224") =1323004944

  说明:java.util.Date中的getTime函数定义如下:

     java.util.Date代表一个时间点,其值为距公元1970年1月1日 00:00:00的毫秒数。所以它是没有时区和Locale概念的。

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

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

?
Date now =newDate(); //这个时间点与本地系统的时区无关

  而正因为其与时区的无关性,才使得我们的存储数据(时间)是一致的(时区一致性)。
  一般的我们将now存储于数据库中,当我们需要展现数据时,将now格式化成想要的格式,如:2011-12-04 21:22:24
  而这个功能一般交由java.text.DateFormat来实现。例如:

?
SimpleDateFormat sdf =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String snow = sdf.format(now);

  我们发现snow是带时间(如2011-12-04 21:22:24)的字符串,那么 2011-12-04 21:22:24 这个时间是哪个时区的时间呢?

  默认情况下,SimpleDateFormat 取得本地系统的时区(我的时区为GMT+8北京),然后按照 pattern("yyyy-MM-dd HH:mm:ss")格式化now,
  此时输出的就是 GMT+8 区的时间了。如果想支持国际化时间,则先指定时区,然后再格式化date数据。例如:

?
SimpleDateFormat sdf =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
String snow = sdf.format(now);// snow = 2011-12-04 21:22:24
sdf.setTimeZone(TimeZone.getTimeZone("GMT+7"));
String snow2 = sdf.format(now);// snow2 = 2011-12-04 20:22:24 (可见:东八区比东七区早一个小时)

  另外,你可以通过如下代码修改本地时区信息:

?
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));

  在windows操作系统中,是通过桌面右下角,也可以指定操作系统的时区。

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

?
[admin@localhost]$ date -R
Sun,04Dec 201122:49:00+0800

 

四、结论:

  getTime()返回的已经是一个UTC的unix timestamp秒数了,与时区无关;而转换为字符串后,就和时区相关了
  对于这个秒数,不同时区的人,按照自己所在的时区去解析,就可以得到正确的时间了

?
[admin@localhost]$ date -d@1323004944
20111204日 星期日21:22:24CST
[admin@localhost]$ date -d@1323004944-u
20111204日 星期日13:22:24UTC

  对于涉及到时间转换的程序来说,如果代码里面没有强行指定时区,那就会依赖于操作系统的时区。

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

 

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

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

?
// 将字符串时间转化为秒数(yyyyMMddHHmmss),有8个小时的时差
  staticpubliclong getLongTime(String srcTime)
  {        
            SimpleDateFormat sdf =newSimpleDateFormat("yyyyMMddHHmmss");
            Date result_date;
            longresult_time =0;
            try
                     result_date = sdf.parse(srcTime);
                     //返回的是毫秒数故除以1000
                     result_time = result_date.getTime()/1000+8 * 3600// 这里加了八个小时
            }catch(Exception e) { 
                     //出现异常时间赋值为20000101000000
                     result_time =946684800;
            }
             
            returnresult_time;
   }

  计算结果:

?
getUnixTimestamp("20111204212224") =1323033744

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

       1323033744 - 1323004944 = 28800 = 8 * 3600 = 8h

  如果用户将得到的 1323033744 按照自己所在的时区解析后得到的结果是:

?
[admin@localhost]$ date -d@1323033744
20111205日 星期一05:22:24CST

  得到了一个完全错误的结果!

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

?
[admin@localhost]$ date -d@1323033744-u
20111204日 星期日21:22:24UTC

  为了方便对比,把 1323004944 的解析结果也拿来对比

?
[admin@localhost]$ date -d@1323004944
20111204日 星期日21:22:24CST
[admin@localhost]$ date -d@1323004944-u
20111204日 星期日13:22:24UTC

  可以看到,这个代码中得到的秒数时间是比UTC的unix timestamp秒数多了八个小时

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

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

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

?
SimpleDateFormat df =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Date end = df.parse("2011-12-04 21:22:24");
java.util.Date start = df.parse("1970-01-01 00:00:00");
longdelta = (end.getTime() - start.getTime())/1000;
System.out.println("delta="+ delta);// delta=1323033744

  或者用shell命令来求时间差

?
[admin@localhost]$ date -d"2011-12-04 21:22:24"+%s
1323004944
[admin@localhost]$ date -d"1970-01-01 0:0:0"+%s
-28800
[admin@localhost]$ date -d"2011-12-04 21:22:24"+%s -u
1323033744
[admin@localhost]$ date -d"1970-01-01 0:0:0"+%s -u
0

    1323004944 + 28800 = 1323033744

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

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

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

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

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

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

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

 

六、从数据库获取unix timestamp时间

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

?
mysql> select auction_id,unix_timestamp(starts)  from auctions where auction_id=88888;                                                   
 +-------------+------------------------+
 | auction_id  | unix_timestamp(starts) |
 +-------------+------------------------+
 |88888      |            1319459578|
 +-------------+------------------------+
 1row in set (0.02sec)

 

七、参考

  java.util.Date  http://www.jar114.com/jdk6/zh_CN/api/java/util/Date.html

  关于Java Date和时区的问题  http://blog.163.com/haizai219@126/blog/static/444125552009101924912981/

八、unix时间戳转换工具

http://code.aosoo.com/unixtime
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java ,你可以使用 `LocalTime` 类来比较两个 `Time` 类型的时间的大小。`LocalTime` 是 Java 8 引入的日期时间类,用于表示时间,不包含日期和时区信息。 要比较两个 `LocalTime` 对象的大小,可以使用 `compareTo()` 方法或者使用比较运算符(如 `<`、`>`、`==`)进行比较。 以下是示例代码: ```java import java.time.LocalTime; public class CompareTimeExample { public static void main(String[] args) { LocalTime time1 = LocalTime.of(12, 30); LocalTime time2 = LocalTime.of(9, 45); // 使用 compareTo() 方法比较两个时间 int result = time1.compareTo(time2); if (result > 0) { System.out.println(time1 + " is later than " + time2); } else if (result < 0) { System.out.println(time1 + " is earlier than " + time2); } else { System.out.println(time1 + " is the same as " + time2); } // 使用比较运算符比较两个时间 if (time1.isAfter(time2)) { System.out.println(time1 + " is later than " + time2); } else if (time1.isBefore(time2)) { System.out.println(time1 + " is earlier than " + time2); } else { System.out.println(time1 + " is the same as " + time2); } } } ``` 在上面的示例,我们创建了两个 `LocalTime` 对象 `time1` 和 `time2`,分别表示 12:30 和 9:45。我们使用 `compareTo()` 方法比较这两个时间,并根据比较结果输出相应的消息。另外,我们还使用 `isAfter()` 和 `isBefore()` 方法来使用比较运算符比较两个时间。 请注意,比较时间时,需要确保两个时间对象都是同一个时区下的时间。如果涉及到不同时区时间比较,可以使用 `ZonedDateTime` 类来处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值