[TOC]
java 中的日期处理一直是个问题,没有很好的方式去处理,所以才有第三方框架的位置比如 joda。
文章主要对 java 日期处理的详解,用 1.8 可以不用 joda。
首先我们对一些基本的概念做一些介绍,其中可以将 GMT 和 UTC 表示时刻大小等同。
1.1 UT 时间
UT 反应了地球自转的平均速度。是通过观测星星来测量的。
具体可以看参考 1.
1.2 UTC
UTC 是用原子钟时间做参考,但保持和 UT1 在 0.9 秒内的时间,也就是说定时调整。现在计算机一般用的网络时间协议 NTP(Network Time Protocol)是用于互联网中时间同步的标准互联网协议。NTP 的用途是把计算机的时间同步到某些时间标准。目前采用的时间标准是世界协调时 UTC(Universal Time Coordinated)。如果计算机不联网即使再精确也是不准的,因为 UTC 会进行调整,而且一般走的时间也是不精确的。附不能上网的电脑如何同步时间资料可以看参考 2.
1.3 GMT
Today, GMT is used as the UK's civil time, or UTC. GMT has been referred to as"UT1", which directly corresponds to the rotation of the Earth, and is subject to that rotation's slight irregularities. It is the difference between UT1 and UTC that is kept > below 0.9s by the application of leap seconds.
简单点理解就是 GMT 是完全符合地球自转的时间,也被称为 UT1。UTC 时间是原子钟时间,当 UTC 时间比 GMT 时间相 0.9 秒的时候,UTC 会做调整与 GMT 一致,也就是说 UTC 时间和 GMT 的时间差不会大于 0.9 秒。
1.4 ISO 8601
一种时间交换的国际格式。
有些接口调用表示 UTC/GMT 时间的时候用 "yyyy-MM-dd'T'HH:mm:ss'Z'" 格式显示。
带毫秒格式 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"。
joda 中实现如下
- // Alternate ISO 8601 format without fractional seconds
- private static final String ALTERNATIVE_ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";private static DateFormat getAlternativeIso8601DateFormat() {
- SimpleDateFormat df = new SimpleDateFormat(ALTERNATIVE_ISO8601_DATE_FORMAT, Locale.US);
- df.setTimeZone(new SimpleTimeZone(0, "GMT"));
- return df;
- }
1.5 RFC 822
STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES
其中 ARPA 网络其实就是互联网的前身。
有些地方会用 RFC 822 里的时间格式,格式如下
- date-time = [ day "," ] date time ; dd mm yy
- ; hh:mm:ss zzz
- //第二个相当于现在格式
- "EEE, dd MMM yyyy HH:mm:ss z"
阿里 oss 里面有些头设置采用该格式。
joda 中实现如下
- // RFC 822 Date Format
- private static final String RFC822_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
- private static DateFormat getRfc822DateFormat() {
- SimpleDateFormat rfc822DateFormat =
- new SimpleDateFormat(RFC822_DATE_FORMAT, Locale.US);
- rfc822DateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
- return rfc822DateFormat;
- }
在 4,5 中创建 SimpleDateFormat 的 Locale.US 可以决定格式字符串某些字符的代替用哪个语言,比如 EEE 等
- SimpleDateFormat df1=new SimpleDateFormat("GGGG yyyy/MMMM/dd HH:mm:ss EEE aaa zzzz",Locale.CHINA);
- SimpleDateFormat df2=new SimpleDateFormat("GGGG yyyy/MMMM/dd HH:mm:ss EEE aaa zzzz",Locale.US);
- //公元 2016/三月/27 23:32:10 星期日 下午 中国标准时间
- //AD 2016/March/27 23:32:10 Sun PM China Standard Time
1.6 gregorian Calendar, julian Calendar
这是两种历法,我们一般用的通用的 gregorian Calendar
扩展可以看参考内容
主要的类有记录时间戳的 Date, 时间和日期进行转换的 Calendar, 用来格式化和解析时间字符串的 DateFormat
使用前要注意时间表示的规则。
In all methods of class
that accept or return year, month, date, hours, minutes, and seconds values, the following representations are used:
- Date
1.1.1 构造方法
注释中说 The class
represents a specific instant in time, with millisecond precision. 也就是说这个类代表某个时刻的毫秒值,既然是毫秒值也就说需要有一个参考值。
- Date
- public Date() {
- this(System.currentTimeMillis());
- }
- public Date(long date) {
- fastTime = date;
- }
System.currentTimeMillis() 是本地方法,注释为 the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC. 但是注释中也说这个可能会因为操作系统的时间而不准。有些操作系统不一定是用毫秒表示的。这个时间都是用的 UTC 时间,不和时区有关的,这个无关的意思是同一时刻每个时区下获得的值应该是一致的,可以简单用程序验证一下获取的时间表达内容。
- long time = System.currentTimeMillis();
- System.out.println(time = (time / 1000));
- System.out.println("秒:" + time % 60);
- System.out.println(time = (time / 60));
- System.out.println("分钟:" + time % 60);
- System.out.println(time = (time / 60));
- System.out.println("小时:" + time % 24);
源码解析可以看参考的相关内容
可以理解成和 UTC 的 1970 年 1 月 1 日零点的差值。而 fastTime 就是 Date 类保存这个时刻的变量。
1.1.2 成员变量
Date 对象打印出来是本地时间,而构造方法是没有时区体现的。那么哪里体现了时区呢?
下面是 Date 的成员变量
1.gcal
获取的是以下的对象。其中并没有自定义字段。可以说只是一个 gregorian(公历)时间工厂获取 CalendarDate 的子类。
2.jcal
儒略历相关的对象。
在以下方法中用到
- private static final BaseCalendar getCalendarSystem(BaseCalendar.Date cdate) {
- if (jcal == null) {
- return gcal;
- }
- if (cdate.getEra() != null) {
- return jcal;
- }
- return gcal;
- }
- synchronized private static final BaseCalendar getJulianCalendar() {
- if (jcal == null) {
- jcal = (BaseCalendar) CalendarSystem.forName("julian");
- }
- return jcal;
- }
当时间戳在以下情况下用儒略历,并且,在用到的时候会自动设置儒略历,所以在 clone 的时候也没有这个参数。所以这个可以忽略。
- private static final BaseCalendar getCalendarSystem(int year) {
- if (year >= 1582) {
- return gcal;
- }
- return getJulianCalendar();
- }
- private static final BaseCalendar getCalendarSystem(long utc) {
- // Quickly check if the time stamp given by `utc' is the Epoch
- // or later. If it's before 1970, we convert the cutover to
- // local time to compare.
- if (utc >= 0
- || utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER
- - TimeZone.getDefaultRef().getOffset(utc)) {
- return gcal;
- }
- return getJulianCalendar();
- }
3.fastTime
保存了一个时间戳表示时刻。最重要的参数。创建 Date 就是对这个值的赋值。
4.cdate
保存了时间相关内容,包括时区,语言等
- public static final int FIELD_UNDEFINED = -2147483648;
- public static final long TIME_UNDEFINED = -9223372036854775808L;
- private Era era;
- private int year;
- private int month;
- private int dayOfMonth;
- private int dayOfWeek;
- private boolean leapYear;
- private int hours;
- private int minutes;
- private int seconds;
- private int millis;
- private long fraction;
- private boolean normalized;
- private TimeZone zoneinfo;
- private int zoneOffset;
- private int daylightSaving;
- private boolean forceStandardTime;
- private Locale locale;
5.defalutCenturyStart
这个值可以忽略, 在过期方法中用到。
- @Deprecated
- public static long parse(String s) {
- ... ...
- // Parse 2-digit years within the correct default century.
- if (year < 100) {
- synchronized (Date.class) {
- if (defaultCenturyStart == 0) {
- defaultCenturyStart = gcal.getCalendarDate().getYear() - 80;
- }
- }
- year += (defaultCenturyStart / 100) * 100;
- if (year < defaultCenturyStart) year += 100;
- }
- ... ...
- }
6.serialVersionUID
验证版本一致性的 UID
7.wtb
保存 toString 格式化用到的值
8.ttb
保存 toString 格式化用到的值
1.1.3 主要方法
主要是比较方法和设置方法。由于项目遗留和数据层的原因限制这个类用到还是比较多。
其实主要也是其中保存的毫秒值 time 字段
下面是我们常用的方法,用了默认的时区和区域语言
- public static Calendar getInstance()
- {
- return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
- }
国内环境默认 GregorianCalendar,但是 TH-th 用的 BuddhistCalendar 等
一些坑:
也就是说多次设置的时候如果中间有需要调整的时间,但是实际是不会做调整的。所以尽量将无法确定的设置之后不要再进行其他调整,防止最后实际值与正常值不准。
- //2000-8-31
- Calendar cal1 = Calendar.getInstance();
- cal1.set(2000, 7, 31, 0, 0, 0);
- //应该是 2000-9-31,也就是 2000-10-1
- cal1.set(Calendar.MONTH, Calendar.SEPTEMBER);
- //如果 Calendar 转化到 2000-10-1,那么现在的结果就该是 2000-10-30
- cal1.set(Calendar.DAY_OF_MONTH, 30);
- //输出的是2000-9-30,说明 Calendar 不是马上就刷新其内部的记录
- System.out.println(cal1.getTime());
add 方法会马上做时间修改
roll 与 add 类似,但是 roll 不会修改更大的字段的值。
创建设置 pattern 字符串,可以表示的格式如下
日期格式是不同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须是外部同步的。
SimpleDateFormat 是线程不安全的类,其父类维护了一个 Calendar, 调用相关方法有可能会修改 Calendar。一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。 正例: 注意线程安全,使用 DateUtils。org.apache.commons.lang.time.DateUtils, 也推荐如下处理:
- private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
- @Override
- protected DateFormat initialValue() {
- return new SimpleDateFormat("yyyy-MM-dd");
- }
- };
这几个类都继承了 java.util.Date。
相当于将 java.util.Date 分开表示了。Date 表示年月日等信息。Time 表示时分秒等信息。Timestamp 多维护了纳秒,可以表示纳秒。
如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释: simple beautiful strong immutable thread-safe。
1.8 增加了新的 date-time 包,遵循 JSR310。核心代码主要放在 java.time 包下。默认的日历系统用的 ISO-8601(基于格里高利历)。
java.time 下主要内容包括:
1.8 日期时间 api 没有并发问题,清晰容易使用。
该包的 API 提供了大量相关的方法,这些方法一般有一致的方法前缀:
可以看到老的时间日期类里面都有了 Instant 的转化。Instant 可以说是新旧转换的中转站。Instant 主要维护了秒和纳秒字段,可以表示纳秒范围。当然不支持的话会抛出异常。主要还是 java.util.Date 转换成新的时间类。
提供了访问当前时间的方法,也可以获取当前 Instant。Clock 是持有时区或者时区偏移量的。如果只是获取当前时间戳,推荐还是用 System.currentTimeMillis()
zone id 主要包括两个方面,一个是相对于对于 UTC/Greenwich 的固定偏移量相当于一个大时区,另一个是时区内有特殊的相对于 UTC/Greenwich 偏移量的地区。通常固定偏移量部分可以用 ZoneOffset 表示,用 normalized() 判断是否可以用 ZoneOffset 表示。判断主要用到了时区规则 ZoneRules。时区的真正规则定义在 ZoneRules 中,定义了什么时候多少偏移量。使用这种方式是因为 ID 是固定不变的,但是规则是政府定义并且经常变动。
Time-zone IDs 是三种类型
LocalDateTIme/LocalTime/LocalDate 都是没有时区概念的。这句话并不是说不能根据时区获取时间,而是因为这些类不持有表示时区的变量。而 ZoneDateTime 持有时区和偏移量变量。
这些类都可以对时间进行修改其实都是生成新对象。所以这里的时间类都是天然支持多线程的。
这些时间类中都提供了获取时间对象,修改时间获取新的时间对象,格式化时间等。
注意点
时间对象进行格式化时间的需要用到格式化和解析日期和时间的时候需要用到 DateTimeFormatter。
- parse调用的方法是
public
- LocalDateTime: :from调用的方法是
public static LocalDateTime from(TemporalAccessor temporal) {
.... ...
}
- 其中temporal是LocalDateTime的接口
- 这里其实大家都有一个疑问就是LocalDateTime::from到底代表什么意思。
LocalDateTime::from
// 与下列表示相同
x -> LocalDateTime.from(x)
// 相当于
new TemporalQuery
10. gmt
未完成待续..
来源: http://www.cnblogs.com/daochang/p/8052951.html