Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat的区别于用法

一、概述

1、问题描述

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

2、时区认识

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

3、Java 时间和时区API

3.1、Date

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

Date date = new Date();
Date date = Calendar.getInstance().getTime(); 

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

3.2、 Calendar

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

3.3、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默认情况下获取的就是操作系统的时区设置。因此在项目中最好事先设置好时区,例如:

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); 

5、解决的方法:

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

6、实例:

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

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);
	}

}

执行结果:

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的话,如下:

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));
  } 

}

执行结果:

Fri Jan 31 21:20:50 CST 2014
13:20

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

正确的做法如下:

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());
  } 

}

执行结果:

Fri Jan 31 21:20:50 CST 2014
Fri Jan 31 13:20:50 CST 2014

完美通用转换方法

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

/**
 *
 */
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;
  } 

}

运行结果:

Fri Jan 31 21:20:50 CST 2014
Fri Jan 31 13:20:50 CST 2014

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

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小时。

到此这篇关于Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat的区别于用法的文章就介绍到这了,更多相关Java时区处理 Date,Calendar,TimeZone,SimpleDateFormat内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java时区转换及Date类实现原理解析

    这篇文章主要介绍了Java时区转换及Date类实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.时区的说明 地球表面按经线从东到西,被划成一个个区域,规定相邻区域的时间相差1小时.在同一区域内的东端和西端的人看到太阳升起的时间最多相差不过1小时.当人们跨过一个区域,就将自己的时钟校正1小时(向西减1小时,向东加1小时),跨过几个区域就加或减几小时 ,所以同一时刻在不同时区表示的时间是不一样的. 二.时间的表示 我们平时表示时间时通

  • Java对世界不同时区timezone之间时间转换的处理方法

    最近出现一个问题是这样的:我们的系统在国外打印的日志时间由于时差关系和国内不一致,看起来不方便,希望国外的日志和国内保持一致,即:需要对不同时区的时间做转换调整,统一为国内时间. 一.关于时区的一些概念 1.1 什么是时区? timezone,即由于世界各国家与地区经度不同,地方时也有所不同,按照经度将全球划分为24个时区. 时区有相应的英文字母缩写,例如GMT,UTC,CST等,常见的时区,具体参考:java时区理解. 1.2 什么是夏令时? 通俗易懂的解释就是:夏令时开始的时候,大家在夜里2

  • Java时区转换实例代码解析

    一.时区的说明 地球表面按经线从东到西,被划成一个个区域,规定相邻区域的时间相差1小时.在同一区域内的东端和西端的人看到太阳升起的时间最多相差不过1小时.当人们跨过一个区域,就将自己的时钟校正1小时(向西减1小时,向东加1小时),跨过几个区域就加或减几小时 ,所以同一时刻在不同时区表示的时间是不一样的. 二.时间的表示 我们平时表示时间时通常是以一个格式化的字符串来表示一个时间,例如"2019-11-5 20:05"这个字符串表示的是2019年11月5日20点05分.但这里有一个隐含的

  • PHP date_default_timezone_set()设置时区操作实例分析

    本文实例讲述了PHP date_default_timezone_set()设置时区操作.分享给大家供大家参考,具体如下: <?php echo function_exists(date_default_timezone_set)."<br>";//在这他总是返回1,这函数是判断这里面的字符是不是一个定义了的函数名 echo date('Y-m-d H:i:s')."<br>";//默认时区的时间 echo date_default_t

  • java转换时区时间过程详解

    这篇文章主要介绍了java转换时区时间过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一丶时区 由于世界各国家与地区经度不同,地方时也有所不同,因此会划分为不同的时区. 地球是自西向东自转,东边比西边先看到太阳,东边的时间也比西边的早.东边时刻与西边时刻的差值不仅要以时计,而且还要以分和秒来计算,这给人们带来不便. 为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东.西

  • Java时区处理之Date,Calendar,TimeZone,SimpleDateFormat的区别于用法

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

  • Java日期时间类(Date、DateFormat、Calendar)解析

    目录 Date类 概述 常用方法 DateFormat类 构造方法 格式规则 常用方法 format方法 parse方法 练习 Calendar类 概念 获取方式 常用方法 get/set方法 add方法 getTime方法 Date类 概述 java.util.Date类 表示特定的瞬间,精确到毫秒. 继续查阅Date类的描述,发现Date拥有多个构造函数,只是部分已经过时,但是其中有未过时的构造函数可以把毫秒值转成日期对象. public Date():分配Date对象并初始化此对象,以表示

  • Java中Date,Calendar,Timestamp的区别以及相互转换与使用

    1 Java.util.Date包含年.月.日.时.分.秒信息. 复制代码 代码如下: // String转换为DateString dateStr="2013-8-13 23:23:23";String pattern="yyyy-MM-dd HH:mm:ss";DateFormate dateFormat=new SimpleDateFormat(pattern);Date date=dateFormat.parse(dateStr);date=dateForm

  • 详解Java中格式化日期的DateFormat与SimpleDateFormat类

    DateFormat 1. DateFormat 介绍 DateFormat 的作用是 格式化并解析"日期/时间".实际上,它是Date的格式化工具,它能帮助我们格式化Date,进而将Date转换成我们想要的String字符串供我们使用 不过DateFormat的格式化Date的功能有限,没有SimpleDateFormat强大:但DateFormat是SimpleDateFormat的父类.所以,我们先对DateFormat有个整体了解,然后再学习SimpleDateFormat.

  • 基于java时区转换夏令时的问题及解决方法

    一.准备知识 1.America/New_York的夏令时时间如下: 包左不包右 2016-3-13, 02:00:00到2016-11-6, 02:00:00 2017-3-12, 02:00:00到2017-11-5, 02:00:00 2.三字母时区 ID 为了与 JDK 1.1.x 兼容,一些三字母时区 ID(比如 "PST"."CTT"."AST")也受支持. 但是,它们的使用被废弃,这是因为相同的缩写经常用于多个时区 例如 CST:有

  • java时区转换的理解及示例详解

    一.时区的基本概念 GMT(Greenwich Mean Time),即格林威治标准时,是东西经零度的地方.人们将地球人为的分为24等份,每一等份为一个时区,每时区横跨经度15度,时间正好为1小时.往西一个时区,则减去一小时:往东一个时区,则加上一小时.中国在东经120度上,(东经120°-东经0°)所得度数再除以15,即得8. UTC(Coordinated Universal Time),即世界协调时间,是经过平均太阳时(以格林威治时间GMT为准).地轴运动修正后的新时标以及以「秒」为单位的

  • Java 和 Javascript 的 Date 与 .Net 的 DateTime 之间的相互转换

    Java 和 Javascript 的 Date 对象内部存放的是从1970年1月1日0点以来的毫秒值. .Net 的 DateTime 对象内部存放的是从0001年1月1日12点以来的tick值,1ticks=100纳秒=0.1微秒. 因此,我们可以借助1970年1月1日0点这个特殊的时刻来对二者进行换算,代码如下: using System; namespace Extends { public static class DateTimeEx { #region DateTime Exten

  • 实例讲解String Date Calendar之间的转换

    1.Calendar 转化 String Calendar calendat = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String dateStr = sdf.format(calendar.getTime()); 2.String 转化Calendar String str="2012-5-27"; SimpleDateFormat s

随机推荐