一、概述
1、问题描述
使用Java处理时间时,我们可能会经常发现时间不对,比如相差8个小时等等,其真实原因便是TimeZone。只有正确合理的运用TimeZone,才能保证系统时间无论何时都是准确的。由于我在外企工作,服务器在美国,美国也有很多时区,经常会碰到向处于不同时区的服务器发请求时需要考虑时区转换的问题。譬如,服务器位于西八区(GMT-8:00),而身处东八区的用户想要查询当天的销售记录。则需把东八区的“今天”这个时间范围转换为服务器所在时区的时间范围。
2、时区认识
GMT时间:即格林威治平时(Greenwich Mean Time)。平太阳时是与视太阳时对应的,由于地球轨道非圆形,运行速度随地球与太阳距离改变而出现变化,因此视太阳时欠缺均匀性。为了纠正这种不均匀 性,天文学家就计算地球非圆形轨迹与极轴倾斜对视太阳时的效应,而平太阳时就是指经修订之后的视太阳时。在格林威治子午线上的平太阳时称为世界时(UTC), 又叫格林威治平时(GMT)。
3、Java 时间和时区API
3.1、Date
类Date表示特定的瞬间,精确到毫秒。获得一个表示当前时间的Date对象有两种方式:
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默认情况下获取的就是操作系统的时区设置。因此在项目中最好事先设置好时区,例如:
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。这些类在处理时间日期时扮演着不同的角色。 1. **Date类**:Date对象代表的是一个时间点,精确到毫秒。它不包含任何时区信息,...
在Java编程中,`TimeZone` 类是处理时区的关键组件,它允许我们获取和设置与特定地理位置相关的日期和时间偏移。时区不仅反映了地理位置相对于格林尼治标准时间(GMT)的偏移,还考虑了夏令时(DST)的调整。本文将...
在Java编程语言中,`Calendar`和`Date`类是处理日期和时间的核心组件。这两个类在处理日期、时间计算以及格式化等任务时扮演着关键角色。理解并熟练运用它们,对于提升Java开发能力至关重要。 `Date`类是Java早期...
Java通过`java.util.TimeZone`类来管理全球不同的时区信息。时区是地球上的地理位置,它决定了本地时间与协调世界时间(UTC)之间的差异。Java中的时区数据主要来自于TZ数据库,也称为IANA时区数据库。 在Java程序...
- `Date`类没有内置时区支持,如果需要处理时区,通常会配合`Calendar`类使用,通过`Calendar.getInstance(TimeZone timeZone)`创建带时区的`Calendar`对象,然后设置到`Date`上。 7. **Java 8及以后的改进**: -...
时区是通过`TimeZone`类来管理的,它可以反映全球不同的时间标准。 `Calendar`的子类`GregorianCalendar`是Java中最常用的实现,它基于格里高利历(公历)。在源码中,我们可以看到`GregorianCalendar`是如何处理...
如果需要处理不同时区的日期,可以使用 `TimeZone` 类。例如,设置为美国东部时间: ```java TimeZone easternTimeZone = TimeZone.getTimeZone("America/New_York"); Calendar easternCal = Calendar....
在Java中,可以使用SimpleDateFormat和TimeZone类来进行时区转换。例如,可以将当前时间转换为指定时区显示: ```java @Test public void test() throws Exception { Date a = new Date(); SimpleDateFormat sf = ...
`TimeZone`类是Java中处理时区的关键,它代表了世界上的特定时区。时区的设置可以影响到时间的解析和格式化。 在Java中,`TimeZone.getDefault()`方法会返回JVM当前运行所在操作系统的默认时区。这可能会导致问题,...
在Java编程语言中,日历(Calendar)类是处理日期和时间的核心类,它提供了丰富的API来实现各种日期和时间操作。本知识点主要探讨如何在Java中创建、修改和使用日历对象,以及如何实现时间选择功能。 1. **日历类的...
总结来说,`Calendar`类是Java中处理日期和时间的重要工具,它提供了一套完整的API来操作和管理日期,包括获取和设置日期字段、比较日期、处理时区和夏令时等。通过熟练掌握`Calendar`类,开发者可以轻松地处理各种...
Java中的Date、Calendar和Timestamp是处理日期和时间的三个核心类,它们各有特点,并且在不同的场景下有各自的优势。接下来我们将深入探讨这三个类的区别、转换方法及其在实际使用中的应用。 1. **java.util.Date**...
Java提供了多种类来处理日期和时间,包括`java.util.Date`、`java.util.Calendar`以及自Java 8引入的`java.time`包中的类。下面我们将深入探讨这些知识点。 1. **java.util.Date** - `Date`类是Java早期用于表示...
总结来说,Android中获取和处理时间主要涉及Date、SimpleDateFormat、Calendar、TimeZone以及java.time包的类。理解并熟练运用这些工具,可以满足大部分时间相关的编程需求。在实际项目中,根据Android版本和具体...
- 对于Java应用程序,可以通过设置系统属性`user.timezone`来指定默认时区,例如通过命令行参数`-Duser.timezone=Asia/Shanghai`。 2. **动态设置时区** - PHP中可以使用`date_default_timezone_set()`函数来...
在Java编程语言中,`Calendar`类是处理日期和时间的核心类之一,它提供了一种灵活的方法来操作日期。`Calendar`类不仅能够处理日期,还可以处理时间,甚至包括具体的时区信息。在这个主题中,我们将深入探讨如何使用...
Java提供了多种类库来处理时间,如`java.util.Date`, `java.util.Calendar`, `java.text.SimpleDateFormat`, `java.time`包(自Java 8引入)等。下面我们将详细探讨Java中的时间格式大全以及时间转换的方法。 1. **...
9. **时区处理**:`java.util.TimeZone`类用于处理时区信息,`java.time.ZoneId`是Java 8引入的新的时区处理类,可以方便地进行时区转换。 10. **日期操作库**:除了标准库,还有一些优秀的第三方库如Joda-Time和...
- `TimeZone`:代表了世界时区,用于处理不同地区的日期和时间。 - `ZoneId`:Java 8中的时区ID,提供了更精确的时区处理。 6. **日期时间流(Stream)操作:** - Java 8引入的流API可以与日期时间类结合,用于...
`Calendar`类提供了处理这些复杂情况的方法,如`getTimeZone()`获取时区,`setTimeZone(TimeZone)`设置时区。 总结,Android开发中的日历功能主要依赖于`java.util.Calendar`类,通过这个类可以创建、修改、比较...