一、概述

1、问题描述

使用Java处理时间时,我们可能会经常发现时间不对,比如相差8个小时等等,其真实原因便是TimeZone。只有正确合理的运用TimeZone,才能保证系统时间无论何时都是准确的。由于我在外企工作,服务器在美国,美国也有很多时区,经常会碰到向处于不同时区的服务器发请求时需要考虑时区转换的问题。譬如,服务器位于西八区(GMT-8:00),而身处东八区的用户想要查询当天的销售记录。则需把东八区的“今天”这个时间范围转换为服务器所在时区的时间范围。

2、时区认识

GMT时间:即格林威治平时(Greenwich Mean Time)。平太阳时是与视太阳时对应的,由于地球轨道非圆形,运行速度随地球与太阳距离改变而出现变化,因此视太阳时欠缺均匀性。为了纠正这种不均匀 性,天文学家就计算地球非圆形轨迹与极轴倾斜对视太阳时的效应,而平太阳时就是指经修订之后的视太阳时。在格林威治子午线上的平太阳时称为世界时(UTC), 又叫格林威治平时(GMT)。

3、Java 时间和时区API

3.1、Date

类Date表示特定的瞬间,精确到毫秒。获得一个表示当前时间的Date对象有两种方式:

Java代码   
1. Date date = new Date();    
2. Date date = Calendar.getInstance().getTime();   
1. Date date = new Date();  
2. Date date = Calendar.getInstance().getTime();

Date对象本身所存储的毫秒数可以通过date.getTime()方法得到;该函数返回自1970年1月1日 00:00:00 GMT以来此对象表示的毫秒数。它与时区和地域没有关系(其实可以认为是GMT时间),而且还会告诉我们这个时区是否使用夏令时。有个这个信息,我们就能够继续将时区对象和日期格式化器结合在一起在其它的时区和其它的语言显示时间了。

3.2、 Calendar

Calendar的getInstance()方法有参数为TimeZone和Locale的重载,可以使用指定时区和语言环境获得一个日历。无参则使用默认时区和语言环境获得日历。

3.2、TimeZone

TimeZone对象给我们的是原始的偏移量,也就是与GMT相差的微秒数,即TimeZone表示时区偏移量,本质上以毫秒数保存与GMT的差值。

获取TimeZone可以通过时区ID,如"America/New_York",也可以通过GMT+/-hh:mm来设定。例如北京时间可以表示为GMT+8:00。

TimeZone.getRawOffset()方法可以用来得到当前时区的标准时间到GMT的偏移量。上段提到的"America/New_York"和"GMT+8:00"两个时区的偏移量分别为-18000000和28800000。

4、影响TimeZone的因素

1. 操作系统的时区设置。

2. 数据传输时时区设置。

第一个原因其实是根本原因,当数据在不同操作系统间流转时,就有可能因为操作系统的差异造成时间偏差,而JVM默认情况下获取的就是操作系统的时区设置。因此在项目中最好事先设置好时区,例如:

Java代码   
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));    
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));

5、解决的方法:

从以上的分析可以看出,解决时区问题就简单了,在时区间转换时间时,首先用原时间减掉原时间所在时区相对于GMT的偏移量,得到原时间相对于GMT的值,然后再加上目标时区相对GMT的偏移量即可。需要注意的是这样得到的结果依然是毫秒数,所以我们要按照指定日期格式重新转换成Date对象即可。

6、实例:

在实例之前,假设当前的时区为中国的东八区。即GMT+8:00

Java代码   
package com.wsheng.aggregator.timezone;  
  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.TimeZone;  
  
/** 
 * @author Josh Wang(Sheng) 
 *  
 * @email  swang6@ebay.com 
 *  
 */  
public class TimeZone1 {  
      
    public static void main(String[] args) {  
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50    
        String dateStr = "2014-1-31 21:20:50 ";    
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));    
        try {    
            Date dateTmp = dateFormat.parse(dateStr);    
            System.out.println(dateTmp);    
         } catch (ParseException e) {    
            e.printStackTrace();    
        }    
        String dateStrTmp = dateFormat.format(date);    
        System.out.println(dateStrTmp);    
    }  
      
  
}  
package com.wsheng.aggregator.timezone;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
 
/**
 * @author Josh Wang(Sheng)
 * 
 * @email  swang6@ebay.com
 * 
 */
public class TimeZone1 {
    
    public static void main(String[] args) {
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50  
        String dateStr = "2014-1-31 21:20:50 ";  
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));  
        try {  
            Date dateTmp = dateFormat.parse(dateStr);  
            System.out.println(dateTmp);  
         } catch (ParseException e) {  
            e.printStackTrace();  
        }  
        String dateStrTmp = dateFormat.format(date);  
        System.out.println(dateStrTmp);  
    }
    
 
}

执行结果:

Results代码   
Sat Feb 01 05:20:50 CST 2014  
2014-01-31 13:20:50  
Sat Feb 01 05:20:50 CST 2014
2014-01-31 13:20:50
       我们发现同一时间,字符串和日期运行出来的结果并不相同,那么我们应该怎么理解呢?

一切都要以根本原因, 即当前操作系统的时间为基准。

我的操作系统 是"Asia/Shanghai",即GMT+8的北京时间,那么执行日期转字符串的format方法时,由于日期生成时默认是操作系统时区,因此 2014-1-31 21:20:50是北京时间,那么推算到GMT时区,自然是要减8个小时的,即结果(2014-01-31 13:20:50);而执行字符串转日期的parse方法时,由于字符串本身没有时区的概念,因此 2013-1-31 22:17:14就是指GMT(UTC)时间【ps:所有字符串都看做是GMT时间】,那么当转化为日期时要加上默认时区, 即"Asia/Shanghai",因此要加上8个小时。

用Calendar的话,如下:

Java代码   
package com.wsheng.aggregator.timezone;  
  
import java.util.Calendar;  
import java.util.Date;  
import java.util.TimeZone;  
  
/** 
 * @author Josh Wang(Sheng) 
 *  
 * @email  swang6@ebay.com 
 *  
 */  
public class TimeZone2 {  
      
    public static void main(String[] args) {    
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50    
        System.out.println(date);    
        Calendar calendar = Calendar.getInstance();    
        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));    
        // 或者可以 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));    
        calendar.setTime(date);    
        System.out.println(calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE));    
    }    
  
}  
package com.wsheng.aggregator.timezone;
 
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
 
/**
 * @author Josh Wang(Sheng)
 * 
 * @email  swang6@ebay.com
 * 
 */
public class TimeZone2 {
    
    public static void main(String[] args) {  
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50  
        System.out.println(date);  
        Calendar calendar = Calendar.getInstance();  
        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));  
        // 或者可以 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));  
        calendar.setTime(date);  
        System.out.println(calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE));  
    }  
 
}
 执行结果:

Results代码   
Fri Jan 31 21:20:50 CST 2014  
13:20  
Fri Jan 31 21:20:50 CST 2014
13:20

Calendar不涉及到日期与字符串的转化,因此不像SimpleDateFormat那么复杂,与日期转字符串的思路类似。但是需要注意的是,设置完时区后,我们不能用calendar.getTime()来直接获取Date日期,因为此时的日期与一开始setTime时是相同值,要想获取某时区的时间,正确的做法是用calendar.get()方法,那么我们怎么获得Date类型的日期呢?

正确的做法如下:

Java代码   
package com.wsheng.aggregator.timezone;  
  
import java.util.Calendar;  
import java.util.Date;  
import java.util.TimeZone;  
  
/** 
 * @author Josh Wang(Sheng) 
 *  
 * @email  swang6@ebay.com 
 *  
 */  
public class TimeZone3 {  
      
    public static void main(String[] args) {    
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50    
        System.out.println(date);    
        Calendar calendar = Calendar.getInstance();    
        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));    
        // 或者可以 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));    
        calendar.setTime(date);    
        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));    
        System.out.println(calendar2.getTime());    
    }    
  
}  
package com.wsheng.aggregator.timezone;
 
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
 
/**
 * @author Josh Wang(Sheng)
 * 
 * @email  swang6@ebay.com
 * 
 */
public class TimeZone3 {
    
    public static void main(String[] args) {  
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50  
        System.out.println(date);  
        Calendar calendar = Calendar.getInstance();  
        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));  
        // 或者可以 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));  
        calendar.setTime(date);  
        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));  
        System.out.println(calendar2.getTime());  
    }  
 
}

执行结果:

Results代码   
Fri Jan 31 21:20:50 CST 2014  
Fri Jan 31 13:20:50 CST 2014  
Fri Jan 31 21:20:50 CST 2014
Fri Jan 31 13:20:50 CST 2014

完美通用转换方法

其实上面两个转换方法都要受到操作系统的时区设置影响,如果软件在不同操作系统运行,仍然会有时间误差,那么怎么才能统一呢?

Java代码   
/** 
 *  
 */  
package com.wsheng.aggregator.timezone;  
  
import java.util.Date;  
import java.util.TimeZone;  
  
/** 
 * @author Josh Wang(Sheng) 
 *  
 * @email  swang6@ebay.com 
 *  
 */  
public class TimeZone4 {  
      
    public static void main(String[] args) {    
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50      
        System.out.println(date);    
        date = changeTimeZone(date, TimeZone.getTimeZone("Asia/Shanghai"), TimeZone.getTimeZone("GMT"));    
        System.out.println(date);    
    }    
        
    /**  
     * 获取更改时区后的日期  
     * @param date 日期  
     * @param oldZone 旧时区对象  
     * @param newZone 新时区对象  
     * @return 日期  
     */    
    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;    
    }    
  
}  
/**
 * 
 */
package com.wsheng.aggregator.timezone;
 
import java.util.Date;
import java.util.TimeZone;
 
/**
 * @author Josh Wang(Sheng)
 * 
 * @email  swang6@ebay.com
 * 
 */
public class TimeZone4 {
    
    public static void main(String[] args) {  
        Date date = new Date(1391174450000L); // 2014-1-31 21:20:50    
        System.out.println(date);  
        date = changeTimeZone(date, TimeZone.getTimeZone("Asia/Shanghai"), TimeZone.getTimeZone("GMT"));  
        System.out.println(date);  
    }  
      
    /** 
     * 获取更改时区后的日期 
     * @param date 日期 
     * @param oldZone 旧时区对象 
     * @param newZone 新时区对象 
     * @return 日期 
     */  
    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;  
    }  
 
}
 运行结果:

Results代码   
Fri Jan 31 21:20:50 CST 2014  
Fri Jan 31 13:20:50 CST 2014  
Fri Jan 31 21:20:50 CST 2014
Fri Jan 31 13:20:50 CST 2014

更通用的,我们可以写一个支持类型转换的类:

Java代码   
package com.wsheng.aggregator.timezone;  
import java.text.*;      
import java.util.*;      
   
/** 
 *  
 * @author Josh Wang(Sheng) 
 *  
 * @email  swang6@ebay.com 
 * 
 */  
public class DateTransformer  {    
    public static final String DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";    
           
    public static String dateTransformBetweenTimeZone(Date sourceDate, DateFormat formatter,    
        TimeZone sourceTimeZone, TimeZone targetTimeZone) {    
        Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset();    
        return DateTransformer.getTime(new Date(targetTime), formatter);    
    }    
           
    public static String getTime(Date date, DateFormat formatter){    
       return formatter.format(date);    
    }    
           
    public static void main(String[] args){    
        DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);    
        Date date = Calendar.getInstance().getTime();    
        System.out.println(" date: " + date);  
          
        TimeZone srcTimeZone = TimeZone.getTimeZone("EST");    
        TimeZone destTimeZone = TimeZone.getTimeZone("GMT+8");    
        System.out.println(DateTransformer.dateTransformBetweenTimeZone(date, formatter, srcTimeZone, destTimeZone));    
    }    
}   
package com.wsheng.aggregator.timezone;
import java.text.*;    
import java.util.*;    
 
/**
 * 
 * @author Josh Wang(Sheng)
 * 
 * @email  swang6@ebay.com
 *
 */
public class DateTransformer  {  
    public static final String DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";  
         
    public static String dateTransformBetweenTimeZone(Date sourceDate, DateFormat formatter,  
        TimeZone sourceTimeZone, TimeZone targetTimeZone) {  
        Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset();  
        return DateTransformer.getTime(new Date(targetTime), formatter);  
    }  
         
    public static String getTime(Date date, DateFormat formatter){  
       return formatter.format(date);  
    }  
         
    public static void main(String[] args){  
        DateFormat formatter = new SimpleDateFormat(DATE_FORMAT);  
        Date date = Calendar.getInstance().getTime();  
        System.out.println(" date: " + date);
        
        TimeZone srcTimeZone = TimeZone.getTimeZone("EST");  
        TimeZone destTimeZone = TimeZone.getTimeZone("GMT+8");  
        System.out.println(DateTransformer.dateTransformBetweenTimeZone(date, formatter, srcTimeZone, destTimeZone));  
    }  
}

DateFormat是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间。日期/时间格式化子类(如 SimpleDateFormat)允许进行格式化(也就是日期 -> 文本)、解析(文本-> 日期)和标准化。将日期表示为 Date 对象,或者表示为从 GMT(格林尼治标准时间)1970 年 1 月 1 日 00:00:00 这一刻开始的毫秒数。SimpleDateFormat则是一个以与语言环境有关的方式来格式化和解析日期的具体类,可以以“日期和时间模式”字符串指定日期和时间格式。我们函数中所用模式字符串为"MM/dd/yyyy HH:mm:ss",则输出日期:"07/16/2013 04:00:00"

其他常见的模式字母定义如下:

字母 日期或时间元素 表示 示例

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

由上面的分析和事例说明可知:

1. 计算机内部记录的时间(Date date = new Date()), 为格林威治标准时(GMT). 即java.util.Date代表一个时间点,其值为距公元1970年1月1日 00:00:00的毫秒数。所以它可以认为是没有时区和Locale概念的。

2. 日期格式化类DateFormat, 对于不同地区的配置一般有两个点, 一个是Locale , 一个是TimeZone

前者(Locale)使DateFormat按所配置的地区特性来输出文字(例如中国,美国,法国不同地区对日期的表示格式不一样,中国可能是2001年10月5日)

后者(TimeZone)让DateFormat知道怎么去转换,去调整时间偏移度,从而得到符合配置的时区的时间.

(即假设取得当前时间(假设当前时区为GMT+0,即与new Date()最后转换的时间毫秒数一致)为2:00, 那么如果你配置DateFormat.setTimeZome("GMT+8"), 即北京时间的时区, 那么这时候格式化输出的就是10:00了, 因为系统对原始毫秒数进行了时间偏移调整(调到你设置的时区),即加多8小时,之后再格式化输出日期的字符串形式)

3. GMT与UTC的时区是一样的,都是以伦敦时间为基准. 而GMT+8时区就是北京时间所在时区.同一时刻的时间比GMT快8小时。
————————————————
版权声明:本文为CSDN博主「tsunzhang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tsunzhang/article/details/84880251

Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat相关推荐

  1. java calendar 设置时区_详解Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat

    一.概述 1.问题描述 使用Java处理时间时,我们可能会经常发现时间不对,比如相差8个小时等等,其真实原因便是TimeZone.只有正确合理的运用TimeZone,才能保证系统时间无论何时都是准确的 ...

  2. 探索 Java 中的 Date, Calendar, TimeZone 和Timestamp

    探索 Java 中的 Date, Calendar, TimeZone 和Timestamp java 2010-12-31 08:56:49 阅读8 评论0  字号:大中小 订阅 对象 宋晟 (sh ...

  3. Java时区处理之夏令时,冬令时 - 美国的6个时区

    在开始之前,如果要了解Java中的时区操作的基本概念和事例,可以参见另一篇博客:Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat 一.夏令时概述: Da ...

  4. java date类 时区_Java时区转换及Date类实现原理解析

    这篇文章主要介绍了Java时区转换及Date类实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.时区的说明 地球表面按经线从东到西,被 ...

  5. Java中Date, Calendar, SimpleDateFormat的相互转换

    1 import java.text.ParseException; 2 import java.text.SimpleDateFormat; 3 import java.util.Calendar; ...

  6. Java基础 时间相关类小结[Timestamp,Date,Calendar]

    Java基础 时间相关类[Timestamp,Date,Calendar] 前言 一.Timestamp(时间戳) 二.Date(日期) 三.Calendar(日历) 四.Timestamp.Date ...

  7. java date dateformat_Java中SimpleDateFormat的使用方法

    本文内容大多基于官方文档和网上前辈经验总结,经过个人实践加以整理积累,仅供参考. java.text.SimpleDateFormat 以区域语言环境敏感的方式格式化和解析日期,可以将日期格式化为指定 ...

  8. 正则表达式、常用类(Pattern类、Matcher类、Math类、System类、Random类、BigDecimal类、Date类、SimpleDateFormat类、Calendar类)

    JAVA学习第十六天-正则表达式.常用类(Pattern类.Matcher类.Math类.System类.Random类.BigDecimal类.Date类.SimpleDateFormat类.Cal ...

  9. calendar类_带有时区的字符怎样转换为时间及Java 8中日期 与 Calendar 转换

    概述 Java 8 日期,时间常用操作及格式化. 重点怎么把时间戳转换为带有时区的时间字符串? 带有时区的字符串怎样转换为时间? Java 8中时间,日期 与Date, Calendar 怎样互转? ...

最新文章

  1. 软件测试培训 高级测试/测试开发基本技能列表
  2. 网页设计布局选择:固定,流行和弹性布局 (2010-12-14 13:07:35)
  3. 【BZOJ4028】[HEOI2015]公约数数列(分块/数量级很小法)
  4. MATLAB自定义画布大小
  5. mysql集群fuzhi_MySQL集群 和MySQL主从复制的不同
  6. python提取英文单词 每行显示一个_使用python对文件中的单词进行提取
  7. 牛了!Python最容易入的10个坑!
  8. git上传过滤忽略文件
  9. html----js控制下拉框(详解
  10. ora-01033 解决方法
  11. 【观察】从最佳实践到行业使能,华为HiCampus定义智慧园区
  12. 【2020/3/12】Java 提示 java.lang.ClassNotFoundException(错误: 找不到或无法加载主类)的解决办法
  13. pwm调速c语言,PWM调速的C语言程序编写
  14. 数据挖掘十大经典算法(转存)
  15. UEFI Console Splitter
  16. Hierarchical Attention-Based Multimodal Fusion Network for Video Emotion Recognition
  17. FCoin币改试验区(主版C)筹备公告(5号)
  18. open62541 (R 1.1.2)中文文档 (译文)第一篇 (1 - 5)
  19. Python-OpenCV图像处理(一):读取并指定窗口大小显示图片
  20. 怎么隐藏响应头中的server和X-Powered-By?

热门文章

  1. UI设计新手常遇到的问题及解决方法,快来看一看!
  2. 通达信应用程序编程接口如何获取k线?
  3. SOME/IP的车载网络应用
  4. golang leaf调试Game、Login模块收发信息
  5. 宽带连接要求用户名和密码是灰色的
  6. 公众号内添加投票链接制作投票的软件网络投票器
  7. summernote禁止图片视频上传
  8. 用python3发短信_Python3调用阿里短信api发送短信
  9. NumPy库(一):数组创建、切片、索引
  10. 神州租车持续创新,为消费者带去更好的出行体验