目录
  • 一、简介
    • 1. 相关概念
  • 二、 相关类型的分析比较
    • 1. jdk1.8之前
      • 1.1 java.util.Date
      • 1.2 java.util.Calendar
      • 1.3 java.text.SimpleDateFormat
      • 1.4 java.sql.Date/Time/Timestamp
    • 2. jdk1.8的时间类
      • 2.1 总览
      • 2.2 相互转化和Instant
      • 2.3 Clock
      • 2.4 ZoneId/ZoneOffset/ZoneRules
      • 2.4 LocalDateTime/LocalTime/LocalDate/ZoneDateTime
      • 2.5 DateTimeFormatter
  • 三、扩展及思考
  • 四、参考及扩展资料

一、简介

java中的日期处理一直是个问题,没有很好的方式去处理,所以才有第三方框架的位置比如joda。
文章主要对java日期处理的详解,用1.8可以不用joda。

1. 相关概念

首先我们对一些基本的概念做一些介绍,其中可以将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
扩展可以看参考内容

二、 相关类型的分析比较

1. jdk1.8之前

主要的类有记录时间戳的Date,时间和日期进行转换的Calendar,用来格式化和解析时间字符串的DateFormat

1.1 java.util.Date

使用前要注意时间表示的规则。

In all methods of class Date that accept or return
year, month, date, hours, minutes, and seconds values, the
following representations are used:

  • A year y is represented by the integer y - 1900.
  • A month is represented by an integer from 0 to 11; 0 is January, 1 is February, and so forth; thus 11 is December.
  • A date (day of month) is represented by an integer from 1 to 31 in the usual manner.
  • An hour is represented by an integer from 0 to 23. Thus, the hour from midnight to 1 a.m. is hour 0, and the hour from noon to 1 p.m. is hour 12.
  • A minute is represented by an integer from 0 to 59 in the usual manner.
  • A second is represented by an integer from 0 to 61; the values 60 and 61 occur only for leap seconds and even then only in Java implementations that actually track leap seconds correctly. Because of the manner in which leap seconds are currently introduced, it is extremely unlikely that two leap seconds will occur in the same minute, but this specification follows the date and time conventions for ISO C.

还有这个类有很多过期方法不推荐使用,很多已经被Calendar代替。

1.1.1 构造方法

注释中说The class Date 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 主要方法

主要是比较方法和设置方法。由于项目遗留和数据层的原因限制这个类用到还是比较多。

1.2 java.util.Calendar

其实主要也是其中保存的毫秒值time字段

下面是我们常用的方法,用了默认的时区和区域语言

public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }

国内环境默认GregorianCalendar,但是TH-th用的BuddhistCalendar等

一些坑:

  1. set(int,int,int,int,int,int)方法
    方法不能设置毫秒值,所以当用getInstance后即使用设置相同的值,最后毫秒值也是不一致的。所以如果有需要,将MILLISECOND清零。

  2. set,add,get,roll
    set方法不会马上计算时间,指是修改了对应的成员变量,只有get()、getTime()、getTimeInMillis()、add() 或 roll()的时候才会做调整

        //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不会修改更大的字段的值。

1.3 java.text.SimpleDateFormat

创建设置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"); 
        }
 };

1.4 java.sql.Date/Time/Timestamp

这几个类都继承了java.util.Date。
相当于将java.util.Date分开表示了。Date表示年月日等信息。Time表示时分秒等信息。Timestamp多维护了纳秒,可以表示纳秒。

如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。

2. jdk1.8的时间类

2.1 总览

1.8增加了新的date-time包,遵循JSR310。核心代码主要放在java.time包下。默认的日历系统用的ISO-8601(基于格里高利历)。
java.time下主要内容包括:

  • java.time -主要包括,日期,时间,日期时间,时刻,期间,和时钟相关的类。
  • java.time.chrono -其他非ISO标准的日历系统可以用java.time.chrono,里面已经定义了一部分年表,你也可以自定义。
  • java.time.format -格式化和解析日期时间的类
  • java.time.temporal -扩展API,主要是提供给写框架和写库的人,允许日期时间相互操作,访问,和调整。字段和单位在这个包下定义。
  • java.time.zone -定义了时区,相对于时区的偏移量,时区规则等。

1.8日期时间api没有并发问题,清晰容易使用。

该包的API提供了大量相关的方法,这些方法一般有一致的方法前缀:

  • of:静态工厂方法。
  • parse:静态工厂方法,关注于解析。
  • get:获取某些东西的值。
  • is:检查某些东西的是否是true。
  • with:不可变的setter等价物。
  • plus:加一些量到某个对象。
  • minus:从某个对象减去一些量。
  • to:转换到另一个类型。
  • at:把这个对象与另一个对象组合起来,例如: date.atTime(time)。

2.2 相互转化和Instant

可以看到老的时间日期类里面都有了Instant的转化。Instant可以说是新旧转换的中转站。Instant主要维护了秒和纳秒字段,可以表示纳秒范围。当然不支持的话会抛出异常。主要还是java.util.Date转换成新的时间类。

2.3 Clock

提供了访问当前时间的方法,也可以获取当前Instant。Clock是持有时区或者时区偏移量的。如果只是获取当前时间戳,推荐还是用System.currentTimeMillis()

2.4 ZoneId/ZoneOffset/ZoneRules

zone id 主要包括两个方面,一个是相对于对于UTC/Greenwich的固定偏移量相当于一个大时区,另一个是时区内有特殊的相对于UTC/Greenwich偏移量的地区。通常固定偏移量部分可以用ZoneOffset表示,用normalized()判断是否可以用ZoneOffset表示。判断主要用到了时区规则ZoneRules。时区的真正规则定义在ZoneRules中,定义了什么时候多少偏移量。使用这种方式是因为ID是固定不变的,但是规则是政府定义并且经常变动。
Time-zone IDs是三种类型

  • 'z'和以'+'/'-'开头的id
  • 固定前缀和offset-style IDs,比如'GMT+2' or 'UTC+01:00',可识别的前缀有GMT,UTC,UT。可以标准化成ZoneOffset,通过normalized方法
  • 基于地区的IDs,需要包含2个或以上的特征,并且不以'UTC', 'GMT', 'UT' '+' 或者'-'开头。通过配置实现,具体可以看ZoneRulesProvider,配置实现了通过ID找到具体的ZoneRules。

2.4 LocalDateTime/LocalTime/LocalDate/ZoneDateTime

LocalDateTIme/LocalTime/LocalDate都是没有时区概念的。这句话并不是说不能根据时区获取时间,而是因为这些类不持有表示时区的变量。而ZoneDateTime持有时区和偏移量变量。
这些类都可以对时间进行修改其实都是生成新对象。所以这里的时间类都是天然支持多线程的。
这些时间类中都提供了获取时间对象,修改时间获取新的时间对象,格式化时间等。
注意点

  • LocaDateTime的atZone是调整本地时间的时区的。并不会改变时间。要使用其他时间需要获取的LocalDateTime.now的时候的就要传入时区变量。

2.5 DateTimeFormatter

时间对象进行格式化时间的需要用到格式化和解析日期和时间的时候需要用到DateTimeFormatter。

三、扩展及思考

  1. 用SimpleDateFormat格式化的时候不要用12小时制即hh,因为很容易导致上午下午不分,比如“2017-01-01 00:00:00“可能就变显示成”2017-01-01 12:00:00”
  2. ::符号
    LocalDateTime的方法
public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
    Objects.requireNonNull(formatter, "formatter");
    return formatter.parse(text, LocalDateTime::from);
}

parse调用的方法是

public <T> T parse(CharSequence text, TemporalQuery<T> query) {
   ... ...
}

LocalDateTime::from调用的方法是

public static LocalDateTime from(TemporalAccessor temporal) {
    .... ...     
}

其中temporal是LocalDateTime的接口
这里其实大家都有一个疑问就是LocalDateTime::from到底代表什么意思。

LocalDateTime::from
//与下列表示相同
x ->  LocalDateTime.from(x)
//相当于
new TemporalQuery<LocalDateTime>(){
       @Override
        public LocalDateTime queryFrom(TemporalAccessor temporal) {
             return LocalDateTime.from(temporal);
        }
};

LocalDateTime

介绍

JDK1.8除了新增了lambda表达式、stream流之外,它还新增了全新的日期时间API。在JDK1.8之前,Java处理日期、日历和时间的方式一直为社区所诟病,将 java.util.Date设定为可变类型,以及SimpleDateFormat的非线程安全使其应用非常受限。因此推出了java.time包,该包下的所有类都是不可变类型而且线程安全。

关键类

  • Instant:瞬时时间。
  • LocalDate:本地日期,不包含具体时间, 格式 yyyy-MM-dd。
  • LocalTime:本地时间,不包含日期. 格式 yyyy-MM-dd HH:mm:ss.SSS 。
  • LocalDateTime:组合了日期和时间,但不包含时差和时区信息。
  • ZonedDateTime:最完整的日期时间,包含时区和相对UTC或格林威治的时差。

使用

1.获取当前的日期时间

通过静态工厂方法now()来获取当前时间。

	//本地日期,不包括时分秒
	LocalDate nowDate = LocalDate.now();
	//本地日期,包括时分秒
	LocalDateTime nowDateTime = LocalDateTime.now();
	System.out.println("当前时间:"+nowDate);
	System.out.println("当前时间:"+nowDateTime);
	//  当前时间:2018-12-19
	//  当前时间:2018-12-19T15:24:35.822

2.获取当前的年月日时分秒

获取时间之后,直接get获取年月日时分秒。

	 //获取当前的时间,包括毫秒
	 LocalDateTime ldt = LocalDateTime.now();
	 System.out.println("当前年:"+ldt.getYear());   //2018
	 System.out.println("当前年份天数:"+ldt.getDayOfYear());//172 
	 System.out.println("当前月:"+ldt.getMonthValue());
	 System.out.println("当前时:"+ldt.getHour());
	 System.out.println("当前分:"+ldt.getMinute());
	 System.out.println("当前时间:"+ldt.toString());
	//		 当前年:2018
	//		 当前年份天数:353
	//		 当前月:12
	//		 当前时:15
	//		 当前分:24
	//		 当前时间:2018-12-19T15:24:35.833

3.格式化时间

格式时间格式需要用到DateTimeFormatter类。

LocalDateTime ldt = LocalDateTime.now();
System.out.println("格式化时间: "+ ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
//格式化时间:2018-12-19 15:37:47.119

4.时间增减

在指定的时间进行增加/减少年月日时分秒。

	 LocalDateTime ldt = LocalDateTime.now();
	 System.out.println("后5天时间:"+ldt.plusDays(5));
	 System.out.println("前5天时间并格式化:"+ldt.minusDays(5).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); //2018-06-16
	 System.out.println("前一个月的时间:"+ldt2.minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM"))); //2018-06-16
	 System.out.println("后一个月的时间:"+ldt2.plusMonths(1)); //2018-06-16
	 System.out.println("指定2099年的当前时间:"+ldt.withYear(2099)); //2099-06-21T15:07:39.506
	//		后5天时间:2018-12-24T15:50:37.508
	//		前5天时间并格式化:2018-12-14
	//		前一个月的时间:201712
	//		后一个月的时间:2018-02-04T09:19:29.499
	//		指定2099年的当前时间:2099-12-19T15:50:37.508

5.创建指定时间

通过指定年月日来创建。

	LocalDate ld3=LocalDate.of(2017, Month.NOVEMBER, 17);
	LocalDate ld4=LocalDate.of(2018, 02, 11);

6.时间相差比较

比较相差的年月日时分秒。

示例一: 具体相差的年月日

	LocalDate ld=LocalDate.parse("2017-11-17");
	LocalDate ld2=LocalDate.parse("2018-01-05");
	Period p=Period.between(ld, ld2);
	System.out.println("相差年: "+p.getYears()+" 相差月 :"+p.getMonths() +" 相差天:"+p.getDays());
	// 相差年: 0 相差月 :1 相差天:19

注:这里的月份是不满足一年,天数是不满足一个月的。这里实际相差的是1月19天,也就是49天。

示例二:相差总数的时间

ChronoUnit 日期周期单位的标准集合。

	  	LocalDate startDate = LocalDate.of(2017, 11, 17);
        LocalDate endDate = LocalDate.of(2018, 01, 05);
        System.out.println("相差月份:"+ChronoUnit.MONTHS.between(startDate, endDate));
        System.out.println("两月之间的相差的天数   : " + ChronoUnit.DAYS.between(startDate, endDate));
		//	       相差月份:1
		//	       两天之间的差在天数   : 49

注:ChronoUnit也可以计算相差时分秒。

示例三:精度时间相差

Duration 这个类以秒和纳秒为单位建模时间的数量或数量。

	Instant inst1 = Instant.now();
    System.out.println("当前时间戳 : " + inst1);
    Instant inst2 = inst1.plus(Duration.ofSeconds(10));
    System.out.println("增加之后的时间 : " + inst2);
    System.out.println("相差毫秒 : " + Duration.between(inst1, inst2).toMillis());
    System.out.println("相毫秒 : " + Duration.between(inst1, inst2).getSeconds());
	//	当前时间戳 : 2018-12-19T08:14:21.675Z
	//	增加之后的时间 : 2018-12-19T08:14:31.675Z
	//	相差毫秒 : 10000
	//	相毫秒 : 10

示例四:时间大小比较

	 LocalDateTime ldt4 = LocalDateTime.now();
	 LocalDateTime ldt5 = ldt4.plusMinutes(10);
	 System.out.println("当前时间是否大于:"+ldt4.isAfter(ldt5));
	 System.out.println("当前时间是否小于"+ldt4.isBefore(ldt5));
	 // false
	 // true

7.时区时间计算

得到其他时区的时间。

示例一:通过Clock时钟类获取计算

Clock时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。

	 Clock clock = Clock.systemUTC();
	 System.out.println("当前时间戳 : " + clock.millis());
	 Clock clock2 = Clock.system(ZoneId.of("Asia/Shanghai"));
	 System.out.println("亚洲上海此时的时间戳:"+clock2.millis());
	 Clock clock3 = Clock.system(ZoneId.of("America/New_York"));
	 System.out.println("美国纽约此时的时间戳:"+clock3.millis());
	 //	 当前时间戳 : 1545209277657
	 //	 亚洲上海此时的时间戳:1545209277657
	 //	 美国纽约此时的时间戳:1545209277658

示例二:通过ZonedDateTime类和ZoneId

	 ZoneId zoneId= ZoneId.of("America/New_York");
	 ZonedDateTime dateTime=ZonedDateTime.now(zoneId);
	 System.out.println("美国纽约此时的时间 : " + dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
	 System.out.println("美国纽约此时的时间 和时区: " + dateTime);
	 //	 美国纽约此时的时间 : 2018-12-19 03:52:22.494
	 //	美国纽约此时的时间 和时区: 2018-12-19T03:52:22.494-05:00[America/New_York]

Java 8日期时间API总结:

  • 提供了javax.time.ZoneId 获取时区。
  • 提供了LocalDate和LocalTime类。
  • Java 8 的所有日期和时间API都是不可变类并且线程安全,而现有的Date和Calendar API中的java.util.Date和SimpleDateFormat是非线程安全的。
  • 主包是 java.time,包含了表示日期、时间、时间间隔的一些类。里面有两个子包java.time.format用于格式化, java.time.temporal用于更底层的操作。
  • 时区代表了地球上某个区域内普遍使用的标准时间。每个时区都有一个代号,格式通常由区域/城市构成(Asia/Tokyo),在加上与格林威治或 UTC的时差。例如:东京的时差是+09:00。
  • OffsetDateTime类实际上组合了LocalDateTime类和ZoneOffset类。用来表示包含和格林威治或UTC时差的完整日期(年、月、日)和时间(时、分、秒、纳秒)信息。
  • DateTimeFormatter 类用来格式化和解析时间。与SimpleDateFormat不同,这个类不可变并且线程安全,需要时可以给静态常量赋值。 DateTimeFormatter类提供了大量的内置格式化工具,同时也允许你自定义。在转换方面也提供了parse()将字符串解析成日期,如果解析出错会抛出DateTimeParseException。DateTimeFormatter类同时还有format()用来格式化日期,如果出错会抛出DateTimeException异常。
  • 再补充一点,日期格式“MMM d yyyy”和“MMM dd yyyy”有一些微妙的不同,第一个格式可以解析“Jan 2 2014”和“Jan 14 2014”,而第二个在解析“Jan 2 2014”就会抛异常,因为第二个格式里要求日必须是两位的。如果想修正,你必须在日期只有个位数时在前面补零,就是说“Jan 2 2014”应该写成 “Jan 02 2014”。

四、参考及扩展资料

1.universal-time

2.系统-不能上网的电脑如何同步时间?

3.JVM源码分析之System.currentTimeMillis及nanoTime原理详解

4.Change From Julian to Gregorian Calendar

4.Java日期时间(Date/Time)(附Date.java源码)

5.everything-about-java-8

6.深入理解Java 8 Lambda

7.Java Date-Time Packages官网资料

8.JSR 310: Date and Time API

9.STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES

10.gmt

 

日期和时间是一个比较复杂的概念,Java API中对它的支持不是特别好,有一个第三方的类库反而特别受欢迎,这个类库是Joda-Time,Java 1.8受Joda-Time影响,重新设计了日期和时间API,新增了一个包java.time。

虽然之前的设计有一些不足,但Java API依然是被大量使用的,本节介绍Java 1.8之前API中对日期和时间的支持,下节介绍Joda-Time,Java 1.8中的新API与Joda-Time比较类似,暂时就不介绍了。

关于日期和时间,有一些基本概念,我们先来看下。

基本概念

时区

我们都知道,同一时刻,世界上各个地区的时间可能是不一样的,具体时间与时区有关,一共有24个时区,英国格林尼治是0时区,北京是东八区,也就是说格林尼治凌晨1点,北京是早上9点。0时区的时间也称为GMT+0时间,GMT是格林尼治标准时间,北京的时间就是GMT+8:00。

时刻和Epoch Time (纪元时)

所有计算机系统内部都用一个整数表示时刻,这个整数是距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。为什么要用这个时间呢?更多的是历史原因,本文就不介绍了。

格林尼治标准时间1970年1月1日0时0分0秒也被称为Epoch Time (纪元时)。

这个整数表示的是一个时刻,与时区无关,世界上各个地方都是同一个时刻,但各个地区对这个时刻的解读,如年月日时分秒,可能是不一样的。

如何表示1970年以前的时间呢?使用负数。

年历

我们都知道,中国有公历和农历之分,公历和农历都是年历,不同的年历,一年有多少月,每月有多少天,甚至一天有多少小时,这些可能都是不一样的。

比如,公历有闰年,闰年2月是29天,而其他年份则是28天,其他月份,有的是30天,有的是31天。农历有闰月,比如闰7月,一年就会有两个7月,一共13个月。

公历是世界上广泛采用的年历,除了公历,还有其他一些年历,比如日本也有自己的年历。Java API的设计思想是支持国际化的,支持多种年历,但实际中没有直接支持中国的农历,本文主要讨论公历。

简单总结下,时刻是一个绝对时间,对时刻的解读,如年月日周时分秒等,则是相对的,与年历和时区相关。

Java日期和时间API

Java API中关于日期和时间,有三个主要的类:

  • Date:表示时刻,即绝对时间,与年月日无关。
  • Calendar:表示年历,Calendar是一个抽象类,其中表示公历的子类是GregorianCalendar
  • DateFormat:表示格式化,能够将日期和时间与字符串进行相互转换,DateFormat也是一个抽象类,其中最常用的子类是SimpleDateFormat。 

还有两个相关的类:

  • TimeZone: 表示时区
  • Locale: 表示国家和语言 

下面,我们来看这些类。

Date

Date是Java API中最早引入的关于日期的类,一开始,Date也承载了关于年历的角色,但由于不能支持国际化,其中的很多方法都已经过时了,被标记为了@Deprecated,不再建议使用。

Date表示时刻,内部主要是一个long类型的值,如下所示:

private transient long fastTime;

fastTime表示距离纪元时的毫秒数,此处,关于transient关键字,我们暂时忽略。

Date有两个构造方法:

复制代码
public Date(long date) {
    fastTime = date;
}

public Date() {
    this(System.currentTimeMillis());
}
复制代码

第一个构造方法,就是根据传入的毫秒数进行初始化,第二个构造方法是默认构造方法,它根据System.currentTimeMillis()的返回值进行初始化。System.currentTimeMillis()是一个常用的方法,它返回当前时刻距离纪元时的毫秒数。

Date中的大部分方法都已经过时了,其中没有过时的主要方法有:

返回毫秒数

public long getTime() 

判断与其他Date是否相同

public boolean equals(Object obj)

主要就是比较内部的毫秒数是否相同。

与其他Date进行比较

public int compareTo(Date anotherDate)

Date实现了Comparable接口,比较也是比较内部的毫秒数,如果当前Date的毫秒数小于参数中的,返回-1,相同返回0,否则返回1。

除了compareTo,还有另外两个方法,与给定日期比较,判断是否在给定日期之前或之后,内部比较的也是毫秒数。

public boolean before(Date when)
public boolean after(Date when)

哈希值

public int hashCode()

哈希值算法与Long类似。

TimeZone

TimeZone表示时区,它是一个抽象类,有静态方法用于获取其实例。

获取当前的默认时区,代码为:

TimeZone tz = TimeZone.getDefault();
System.out.println(tz.getID());

获取默认时区,并输出其ID,在我的电脑上,输出为:

Asia/Shanghai

默认时区是在哪里设置的呢,可以更改吗?Java中有一个系统属性,user.timezone,保存的就是默认时区,系统属性可以通过System.getProperty获得,如下所示:

System.out.println(System.getProperty("user.timezone"));

在我的电脑上,输出为:

Asia/Shanghai

系统属性可以在Java启动的时候传入参数进行更改,如

java -Duser.timezone=Asia/Shanghai xxxx

TimeZone也有静态方法,可以获得任意给定时区的实例,比如:

获取美国东部时区

TimeZone tz = TimeZone.getTimeZone("US/Eastern");

ID除了可以是名称外,还可以是GMT形式表示的时区,如:

TimeZone tz = TimeZone.getTimeZone("GMT+08:00");

国家和语言Locale

Locale表示国家和语言,它有两个主要参数,一个是国家,另一个是语言,每个参数都有一个代码,不过国家并不是必须的。

比如说,中国的大陆代码是CN,台湾地区的代码是TW,美国的代码是US,中文语言的代码是zh,英文是en。

Locale类中定义了一些静态变量,表示常见的Locale,比如:

  • Locale.US:表示美国英语
  • Locale.ENGLISH:表示所有英语
  • Locale.TAIWAN:表示台湾中文
  • Locale.CHINESE:表示所有中文
  • Locale.SIMPLIFIED_CHINESE:表示大陆中文

与TimeZone类似,Locale也有静态方法获取默认值,如:

Locale locale = Locale.getDefault();
System.out.println(locale.toString());

在我的电脑上,输出为:

zh_CN

Calendar

Calendar类是日期和时间操作中的主要类,它表示与TimeZone和Locale相关的日历信息,可以进行各种相关的运算。

我们先来看下它的内部组成。

内部组成

与Date类似,Calendar内部也有一个表示时刻的毫秒数,定义为:

protected long  time;

除此之外,Calendar内部还有一个数组,表示日历中各个字段的值,定义为:

protected int   fields[];

这个数组的长度为17,保存一个日期中各个字段的值,都有哪些字段呢?Calendar类中定义了一些静态变量,表示这些字段,主要有:

  • Calendar.YEAR:表示年
  • Calendar.MONTH:表示月,一月份是0,Calendar同样定义了表示各个月份的静态变量,如Calendar.JULY表示7月。
  • Calendar.DAY_OF_MONTH:表示日,每月的第一天是1。
  • Calendar.HOUR_OF_DAY:表示小时,从0到23。
  • Calendar.MINUTE:表示分钟,0到59。
  • Calendar.SECOND:表示秒,0到59。
  • Calendar.MILLISECOND:表示毫秒,0到999。
  • Calendar.DAY_OF_WEEK:表示星期几,周日是1,周一是2,周六是7,Calenar同样定义了表示各个星期的静态变量,如Calendar.SUNDAY表示周日。 

获取Calendar实例

Calendar是抽象类,不能直接创建对象,它提供了四个静态方法,可以获取Calendar实例,分别为:

public static Calendar getInstance()
public static Calendar getInstance(Locale aLocale)
public static Calendar getInstance(TimeZone zone)
public static Calendar getInstance(TimeZone zone, Locale aLocale)

最终调用的方法都是需要TimeZone和Locale的,如果没有,则会使用上面介绍的默认值。getInstance方法会根据TimeZone和Locale创建对应的Calendar子类对象,在中文系统中,子类一般是表示公历的GregorianCalendar。

getInstance方法封装了Calendar对象创建的细节,TimeZone和Locale不同,具体的子类可能不同,但都是Calendar,这种隐藏对象创建细节的方式,是计算机程序中一种常见的设计模式,它有一个名字,叫工厂方法,getInstance就是一个工厂方法,它生产对象。

获取日历信息

与new Date()类似,新创建的Calendar对象表示的也是当前时间,与Date不同的是,Calendar对象可以方便的获取年月日等日历信息。

来看代码,输出当前时间的各种信息:

复制代码
Calendar calendar = Calendar.getInstance();
System.out.println("year: "+calendar.get(Calendar.YEAR));
System.out.println("month: "+calendar.get(Calendar.MONTH));
System.out.println("day: "+calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("hour: "+calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("minute: "+calendar.get(Calendar.MINUTE));
System.out.println("second: "+calendar.get(Calendar.SECOND));
System.out.println("millisecond: " +calendar.get(Calendar.MILLISECOND));
System.out.println("day_of_week: " + calendar.get(Calendar.DAY_OF_WEEK));
复制代码

具体输出与执行时的时间和默认的TimeZone以及Locale有关,在写作时,我的电脑上的输出为:

复制代码
year: 2016
month: 7
day: 14
hour: 13
minute: 55
second: 51
millisecond: 564
day_of_week: 2
复制代码

内部,Calendar会将表示时刻的毫秒数,按照TimeZone和Locale对应的年历,计算各个日历字段的值,存放在fields数组中,Calendar.get方法获取的就是fields数组中对应字段的值。

设置和修改时间

Calendar支持根据Date或毫秒数设置时间:

public final void setTime(Date date)
public void setTimeInMillis(long millis)

也支持根据年月日等日历字段设置时间:

public final void set(int year, int month, int date)
public final void set(int year, int month, int date, int hourOfDay, int minute)
public final void set(int year, int month, int date, int hourOfDay, int minute, int second)
public void set(int field, int value)

除了直接设置,Calendar支持根据字段增加和减少时间:

public void add(int field, int amount)

amount为正数表示增加,负数表示减少。

比如说,如果想设置Calendar为第二天的下午2点15,代码可以为:

复制代码
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 14);
calendar.set(Calendar.MINUTE, 15);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
复制代码

Calendar的这些方法中一个比较方便和强大的地方在于,它能够自动调整相关的字段。

比如说,我们知道二月份最多有29天,如果当前时间为1月30号,对Calendar.MONTH字段加1,即增加一月,Calendar不是简单的只对月字段加1,那样日期是2月30号,是无效的,Calendar会自动调整为2月最后一天,即2月28或29。

再比如,设置的值可以超出其字段最大范围,Calendar会自动更新其他字段,如:

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR_OF_DAY, 48);
calendar.add(Calendar.MINUTE, -120);

相当于增加了46小时。

内部,根据字段设置或修改时间时,Calendar会更新fields数组对应字段的值,但一般不会立即更新其他相关字段或内部的毫秒数的值,不过在获取时间或字段值的时候,Calendar会重新计算并更新相关字段。

简单总结下,Calenar做了一项非常繁琐的工作,根据TimeZone和Locale,在绝对时间毫秒数和日历字段之间自动进行转换,且对不同日历字段的修改进行自动同步更新。

除了add,Calendar还有一个类似的方法:

public void roll(int field, int amount)

与add的区别是,这个方法不影响时间范围更大的字段值。比如说:

Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 13);
calendar.set(Calendar.MINUTE, 59);
calendar.add(Calendar.MINUTE, 3);

calendar首先设置为13:59,然后分钟字段加3,执行后的calendar时间为14:02。如果add改为roll,即:

calendar.roll(Calendar.MINUTE, 3);

则执行后的calendar时间会变为13:02,在分钟字段上执行roll不会改变小时的值。

转换为Date或毫秒数

Calendar可以方便的转换为Date或毫秒数,方法是:

public final Date getTime()
public long getTimeInMillis() 

Calendar的比较

与Date类似,Calendar之间也可以进行比较,也实现了Comparable接口,相关方法有:

public boolean equals(Object obj)
public int compareTo(Calendar anotherCalendar)
public boolean after(Object when)
public boolean before(Object when)

DateFormat

DateFormat类主要在Date和字符串表示之间进行相互转换,它有两个主要的方法:

public final String format(Date date)
public Date parse(String source)

format将Date转换为字符串,parse将字符串转换为Date。

Date的字符串表示与TimeZone和Locale都是相关的,除此之外,还与两个格式化风格有关,一个是日期的格式化风格,另一个是时间的格式化风格。

DateFormat定义了四个静态变量,表示四种风格,SHORT、MEDIUM、LONG和FULL,还定义了一个静态变量DEFAULT,表示默认风格,值为MEDIUM,不同风格输出的信息详细程度不同。

与Calendar类似,DateFormat也是抽象类,也用工厂模式创建对象,提供了多个静态方法创建DateFormat对象,有三类方法:

public final static DateFormat getDateTimeInstance()
public final static DateFormat getDateInstance()
public final static DateFormat getTimeInstance()

getDateTimeInstance既处理日期也处理时间,getDateInstance只处理日期,getTimeInstance只处理时间,看下面代码:

复制代码
Calendar calendar = Calendar.getInstance();
//2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
System.out.println(DateFormat.getDateTimeInstance()
        .format(calendar.getTime()));
System.out.println(DateFormat.getDateInstance()
        .format(calendar.getTime()));
System.out.println(DateFormat.getTimeInstance()
        .format(calendar.getTime()));
复制代码

输出为:

2016-8-15 14:15:20
2016-8-15
14:15:20

每类工厂方法都有两个重载的方法,接受日期和时间风格以及Locale作为参数:

DateFormat getDateTimeInstance(int dateStyle, int timeStyle)
DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)

比如,看下面代码:

复制代码
Calendar calendar = Calendar.getInstance();
//2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
System.out.println(DateFormat.getDateTimeInstance(
        DateFormat.LONG,DateFormat.SHORT,Locale.CHINESE)
        .format(calendar.getTime()));
复制代码

输出为:

2016年8月15日 下午2:15

DateFormat的工厂方法里,我们没看到TimeZone参数,不过,DateFormat提供了一个setter方法,可以设置TimeZone:

public void setTimeZone(TimeZone zone)

DateFormat虽然比较方便,但如果我们要对字符串格式有更精确的控制,应该使用SimpleDateFormat这个类。

SimpleDateFormat

SimpleDateFormat是DateFormat的子类,相比DateFormat,它的一个主要不同是,它可以接受一个自定义的模式(pattern)作为参数,这个模式规定了Date的字符串形式。先看个例子:

Calendar calendar = Calendar.getInstance();
//2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒");
System.out.println(sdf.format(calendar.getTime()));

输出为:

2016年08月15日 星期一 14时15分20秒 

SimpleDateFormat有个构造方法,可以接受一个pattern作为参数,这里pattern是:

yyyy年MM月dd日 E HH时mm分ss秒

pattern中的英文字符a-z和A-Z表示特殊含义,其他字符原样输出,这里:

  • yyyy:表示四位的年
  • MM:表示月,两位数表示
  • dd:表示日,两位数表示
  • HH:表示24小时制的小时数,两位数表示
  • mm:表示分钟,两位数表示
  • ss:表示秒,两位数表示
  • E:表示星期几 

这里需要特意提醒一下,hh也表示小时数,但表示的是12小时制的小时数,而a表示的是上午还是下午,看代码:

Calendar calendar = Calendar.getInstance();
//2016-08-15 14:15:20
calendar.set(2016, 07, 15, 14, 15, 20);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss a");
System.out.println(sdf.format(calendar.getTime()));

输出为:

2016/08/15 02:15:20 下午

更多的特殊含义可以参看SimpleDateFormat的Java文档。如果想原样输出英文字符,可以用单引号括起来。

除了将Date转换为字符串,SimpleDateFormat也可以方便的将字符转化为Date,看代码:

复制代码
String str = "2016-08-15 14:15:20.456";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {
    Date date = sdf.parse(str);
    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年M月d h:m:s.S a");
    System.out.println(sdf2.format(date));
} catch (ParseException e) {
    e.printStackTrace();
}
复制代码

输出为:

2016年8月15 2:15:20.456 下午

代码将字符串解析为了一个Date对象,然后使用另外一个格式进行了输出,这里SSS表示三位的毫秒数。

需要注意的是,parse会抛出一个受检异常(checked exception),异常类型为ParseException,调用者必须进行处理。

局限性

至此,关于Java 1.8之前的日期和时间相关API的主要内容,我们就介绍的差不多了,这里我们想强调一下这些API的一些局限性。

Date中的过时方法

Date中的方法参数与常识不符合,过时方法标记容易被人忽略,产生误用。比如说,看如下代码:

Date date = new Date(2016,8,15);
System.out.println(DateFormat.getDateInstance().format(date));

想当然的输出为2016-08-15,但其实输出为:

3916-9-15

之所以产生这个输出,是因为,Date构造方法中的year表示的是与1900年的差,month是从0开始的。

Calendar操作比较啰嗦臃肿

Calendar API的设计不是很成功,一些简单的操作都需要多次方法调用,写很多代码,比较啰嗦臃肿。

另外,Calendar难以进行比较复杂的日期操作,比如,计算两个日期之间有多少个月,根据生日计算年龄,计算下个月的第一个周一等。

下一节,我们会介绍Joda-Time,相比Calendar,Joda-Time要简洁方便的多。

DateFormat的线程安全性

DateFormat/SimpleDateFormat不是线程安全的,关于线程概念,后续文章我们会详解,这里简单说明一下,多个线程同时使用一个DateFormat实例的时候,会有问题,因为DateFormat内部使用了一个Calendar实例对象,多线程同时调用的时候,这个Calendar实例的状态可能就会紊乱。

解决这个问题大概有以下方案:

  • 每次使用DateFormat都新建一个对象
  • 使用线程同步
  • 使用ThreadLocal
  • 使用Joda-Time,Joda-Time是线程安全的

后续文章我们再介绍线程同步和ThreadLocal。

 

 

java api中日期类型的继承关系

    java.lang.Object

      --java.util.Date

          --java.sql.Date

            --java.sql.Time

              --java.sql.Timestamp

1. java.util.Date表示特定的瞬间,精确到了毫秒

     两个构造函数(别的过期了的我就不说了) Date()   Date(long date) 主要方法》》

 boolean

after(Date when) 
          测试此日期是否在指定日期之后。

 boolean

before(Date when) 
          测试此日期是否在指定日期之前。

 Object

clone() 
          返回此对象的副本。

 int

compareTo(Date anotherDate) 
          比较两个日期的顺序。

 boolean

equals(Object obj) 
          比较两个日期的相等性。

 long

getTime() 
          返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。(最常用的方法了)

java.sql下面的包主要是用在jdbc下的使用

2. Timestamp()

        此类型由 java.util.Date 和单独的毫微秒值组成。只有整数秒才会存储在 java.util.Date 组件中。小数秒(毫微秒)是独立存在的。传递 java.util.Date 类型的值时,Timestamp.equals(Object) 方法永远不会返回 true,因为日期的毫微秒组件是未知的。因此,相对于 java.util.Date.equals(Object)方法而言,Timestamp.equals(Object) 方法是不对称的。此外,hashcode 方法使用基础java.util.Date 实现并因此在其计算中不包括毫微秒。

鉴于 Timestamp 类和上述 java.util.Date 类之间的不同,建议代码一般不要将 Timestamp 值视为java.util.Date 的实例。Timestamp 和 java.util.Date 之间的继承关系实际上指的是实现继承,而不是类型继承。

   构造方法:

Timestamp(long time) 
          使用毫秒时间值构造 Timestamp 对象。

 boolean

after(Timestamp ts) 
          指示此 Timestamp 对象是否晚于给定的 Timestamp 对象。

 boolean

before(Timestamp ts) 
          指示此 Timestamp 对象是否早于给定的 Timestamp 对象。

 int

compareTo(Date o) 
          将此 Timestamp 对象与给定的 Date(必须为 Timestamp 对象)相比较。

 int

compareTo(Timestamp ts) 
          将此 Timestamp 对象与给定 Timestamp 对象相比较。

 boolean

equals(Object ts) 
          测试此对象是否等于给定的 Timestamp 对象。

 boolean

equals(Timestamp ts) 
          测试此 Timestamp 对象是否等于给定的 Timestamp 对象。

 int

getNanos()(因为刚才说过了 我们不能通过java.util.Date来获得毫秒 ,timestamp就自己提供)
          获取此 Timestamp 对象的 nanos 值。

 long

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

 void

setNanos(int n) 
          将此 Timestamp 对象的 nanos 字段设置为给定值。

 void

setTime(long time) 
          设置此 Timestamp 对象,以表示 1970 年 1 月 1 日 00:00:00 GMT 以后 time 毫秒的时间点。

 String

toString() 
          使用 JDBC 时间戳转义格式编排时间戳。

static Timestamp

valueOf(String s) 
          将使用 JDBC 时间戳转义格式的 String 对象转换为 Timestamp 值。

因为是继承了java.util.Date  所以也继承了里面的方法。。这个类的主要作用就是在数据库中插入时间,那有些人就会问为什么不用java.sql.Date,那么我们就来看下java.sql.Date>>

java.sql.Date》》

 构造方法Date(long date) 
          使用给定毫秒时间值构造一个 Date 对象。

 void

setTime(long date) 
          使用给定毫秒时间值设置现有 Date 对象。

 String

toString() 
          格式化日期转义形式 yyyy-mm-dd 的日期。

static Date

valueOf(String s) 
          将 JDBC 日期转义形式的字符串转换成 Date 值。

可能有人发现了 在我们用这个java.sql.Date类型插入数据库的时候会发现丢失了时分秒,发生了自动截取,这是为什么呢?

       原来java.sql.Date是为了配合SQL DATE而设置的数据类型。“规范化”的java.sql.Date只包含年月日信息,时分秒毫秒都会清零。格式类似:YYYY-MM-DD。当我们调用ResultSet的

getDate()方法来获得返回值时,java程序会参照"规范"的java.sql.Date来格式化数据库中的数值。因此,如果数据库中存在的非规范化部分的信息将会被劫取。所以我们要精确的日期时应该使用Timestamp

3. Time()

Time(long time) 
          使用毫秒时间值构造 Time 对象。

 void

setTime(long time) 
          使用毫秒时间值设置 Time 对象。

 String

toString() 
          使用 JDBC 时间转义格式对时间进行格式化。

static Time

valueOf(String s) 
          将使用 JDBC 时间转义格式的字符串转换为 Time 值。

有了上面这些知识后,我们要进行转换就发现一目了然了>>>基本都是可以通过getTime()方法获得long类型的毫秒数 在通过构造函数把这毫秒数传过去就完成了转换。如:

java.util.Date d = new java.util.Date(sqlDate.getTime());

 

深入理解Java常用类-----时间日期

     除了String这个类在日常的项目中比较常用之外,有关时间和日期的操作也是经常遇到的,本篇就讲详细介绍下Java API中对时间和日期的支持。其实在Java 8之前时间日期的API并不是很好用,以至于人们在项目中大多使用的是一个第三方库 Joda-Time,当然Java 8 吸收了该库的大部分优点,改进了相关API,现在的时间日期处理接口相对以前来说是好用很多,本篇也将学习下这个优秀的第三方库。下面是本篇主要涉及内容:

  • 古老的Date类
  • 处理年月日的年历类Calendar
  • 格式化字符串和日期对象的DateFormat格式转换类
  • 好用的SimpleDateFormat实现类
  • Joda-Time库

一、古老的Date类
     Date这个类自jdk1.0开始就被设计出来, 从它的源代码中我们也是可以看出来,Date类曾经扮演过很重要的角色,jdk早期的版本中有关日期和时间的操作几乎都是由Date类完成的,下面我们一起看看它的源码:

private transient long fastTime;

首先Date中有封装一个long类型的变量,这个变量是整个时间日期操作的对象,也就是我们使用该变量代表时间和日期。下面说明它是如何表示时间和日期的。所有计算机中的时间都是用一个整数表示的,该整数的值代表的是距离格林尼治标准时间(1970年1月1日0时0分0秒)的毫秒数,也就是说fastTime值为1000的时候代表时间为1970年1月1日0时0分1秒。至于为什么是这个时间,由于种种历史原因大家也可以去了解下,此处不再赘述。

由于该类中大部分方法都被注解了@Deprecated,已经不再推荐使用了,所以接下来我们主要还是看看其中还保留着的方法。只剩下两个构造方法:

public Date(long date) {
        fastTime = date;
    }
public Date() {
        this(System.currentTimeMillis());
    }

只推荐使用上述两个构造方法来构造我们的Date对象,一个是默认无参构造器(内部调用本地函数获取系统当前时间计算与标准时间的毫秒差值),另一个则需要手动传入一个毫秒值构造Date对象。

剩下的则主要是一些获取和设置fastTime的函数,以及比较日期大小的函数,其他的都被注解了,至于上述这些函数,代码相对简单此处不再赘述。

二、处理年月日的年历类Calendar
     以前我们是可以使用Date来处理日期年月日的,但是由于该类不支持国际化等原因,现在其中大部分方法被注解,不再推荐使用,现在的Date类更像是代表着某一个时刻的对象,而处理年月日的这种转换则完全交给了Calendar类处理。所以Calendar目前是日期时间处理中的核心类,接下来我们看看其中源码:

//和Date一样封装了毫秒属性
protected long  time;
protected int           fields[];
//封装了十七个静态常量
public final static int ERA = 0;
public final static int YEAR = 1;
public final static int MONTH = 2;
public final static int WEEK_OF_YEAR = 3;
.........
public final static int DST_OFFSET = 16;

在Calendar的内部封装了17个静态常量,这些常量将会作为索引用来检索fields属性,例如:fields[YEAR]将返回当前毫秒值对应的日期时间的年份部分,fields[MONTH]将返回的是月份部分的值等等。至于这些值是哪里来的,等我们介绍到后续源码的时候再说明,此处只需要理解这些常量的作用即可。

该类是抽象类,我们使用工厂方法获取该类实例:

public static Calendar getInstance()
{
	return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

public static Calendar getInstance(TimeZone zone)
{
    return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}

public static Calendar getInstance(Locale aLocale)
{
    return createCalendar(TimeZone.getDefault(), aLocale);
}

public static Calendar getInstance(TimeZone zone,
                                       Locale aLocale)
{
    return createCalendar(zone, aLocale);
}

主要有四个方法用于创建Calendar实例,其实内部调用的是同一的方法只是传入的参数的值不同。创建一个Calend 实例需要两个参数,一个是TimeZone时区,另一个是Locale语言国家。因为每个国家或地区他们表示时间的形式是不一样的,所以我们需要通过这两个参数确定具体需要使用的格式,当然是以本地时间作为fastTime的值的,如果我们没有指定时区和国家语言,那么将会默认使用本机系统信息。接下来我们看如何通过获取到Calendar实例完成对日期时间进行计算。

我们有获取和设置内部代表毫秒的time属性:

public final Date getTime() {
    return new Date(getTimeInMillis());
}
public void setTimeInMillis(long millis){}

也有获取上述介绍的17中属性的方法:

public int get(int field)
{
    complete();
    return internalGet(field);
}

其中complete方法就是调用了本地函数完成对fields属性中没有值的元素赋值。 调用internalGet方法其实就是调用的fields[field],为我们返回指定属性的结果值。我们可以看个例子:

public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.get(Calendar.YEAR));
    System.out.println(calendar.get(Calendar.MONTH));
    System.out.println(calendar.get(Calendar.AM_PM))
}

结果如下:
这里写图片描述

上述代码运行在不同的时候的结果都是不一样的,写作时的时间:2017/5/29 14:02。需要注意一点的是,month属性是从0开始的,也就是0表示一月,4表示5月,星期也是一样。此外,上述中的AM_PM表示的是上下午的概念,上午为0,下午为1。

除了获取有关日期时间的信息,我们也是有可以用来设置他们的方法的:

//为指定属性设置值
public void set(int field, int value)
//设置年月日等,很多重载
public final void set(int year, int month, int date)
......
//清空所有该Calendar实例的属性值
public final void clear()

除此之外,还有一些通过计算来设置Calendar属性的方法:

//为指定属性添加值
abstract public void add(int field, int amount);

例如:

public static void main(String[] args){
     Calendar calendar = Calendar.getInstance();
     System.out.print(calendar.get(Calendar.YEAR));
     calendar.add(Calendar.YEAR,10);
     System.out.print(calendar.get(Calendar.YEAR));
}

改程序将输出:2017 2027。还有一个roll方法也很有意思:

abstract public void roll(int field, boolean up);
//重载
public void roll(int field, int amount)
{
     while (amount > 0) {
         roll(field, true);
         amount--;
     }
     while (amount < 0) {
         roll(field, false);
         amount++;
     }
}

我们需要记住的是,roll方法完成的工作是和add一样的,只是add方法处理了越界的特殊情况(越界会向上进一位),而roll方法会重新回到初始值再加。例如:

public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.MONTH, 11);//十二月
    System.out.println(calendar.getTime());

    //calendar.add(Calendar.MONTH,5);
    //calendar.roll(Calendar.MONTH,5);

    System.out.println(calendar.getTime());
}

上述程序我们设置Calendar日期为2017/12,针对上述两种方式add和roll,输出结果如下:

这里写图片描述
这里写图片描述

对于12月,add方法加5之后,month为5月但是已经是2018年,而roll则没有向上进位,这就是区别,实际使用的时候还需加以区分。当然,如果你对某个属性的范围不是很明确,可以使用下面两个方法获取:

abstract public int getMinimum(int field);

abstract public int getMaximum(int field);

还有一些有关比较的函数,和Date是类似的:

public boolean equals(Object obj)
public int compareTo(Calendar anotherCalendar)
public boolean after(Object when)
public boolean before(Object when)

三、DateFormat处理格式转换
     DateFormat是一个抽象类,该类主要用于实现Date对象和字符串之间相互转换, 涉及到两个转换的方法:

//将Date类型转换为String类型
public final String format(Date date)
//将String类型转换Date类型
public Date parse(String source)

除此之外,DateFormat还提供了四个静态常量,代表着四种不同的风格。不同的风格输出信息的内容详尽程度不同,默认的风格是MEDIUM。(折中)

    public static final int FULL = 0;
    public static final int LONG = 1;

    public static final int MEDIUM = 2;

    public static final int SHORT = 3;

    public static final int DEFAULT = MEDIUM;

该类是抽象类,一样需要使用静态工厂获取实例对象。

public final static DateFormat getTimeInstance()
public final static DateFormat getTimeInstance(int style)
public final static DateFormat getTimeInstance(int style,Locale aLocale)

public final static DateFormat getDateInstance()
public final static DateFormat getDateInstance(int style)
public final static DateFormat getDateInstance(int style,Locale aLocale)

public final static DateFormat getDateTimeInstance()
public final static DateFormat getDateTimeInstance(int dateStyle,int timeStyle)
public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)

很明显,有三种不同的方式来获取DateFormat实例,每种方式有三个重载,getDateInstance用来处理日期,getTimeInstance用来处理时间,getDateTimeInstance既可以处理日期,也可以处理时间。我们通过一个例子看看他们之间的区别:

public static void main(String[] args) {
																				      Calendar c = Calendar.getInstance();         System.out.println(DateFormat.getDateInstance().format(c.getTime()));
        System.out.println(DateFormat.getTimeInstance().format(c.getTime()));
        System.out.println(DateFormat.getDateTimeInstance().format(c.getTime()));
}

输出结果:

2017-5-29
17:18:26
2017-5-29 17:18:26

很显然,三者之间的区别也是不言而喻。对于他们另外两个重载来说,一个重载提供修改输出风格,另一个提供修改locale。无论是上述的哪一种工厂方法,在他们内部都调用的是同一个函数

private static DateFormat get(int timeStyle, int dateStyle,int flags, Locale loc)

四个参数,所有我们在调用工厂方法的时候没有提供的参数值都会使用默认值。至于该方法具体是如何实现创建一个实例返回的我们就暂时不深究了。至于其他的一些方法,我们将在其子类SimpleDateFormat中学习。

四、优秀的实现类SimpleDateFormat
     SimpleDateFormat是DateFormat的一个优秀的实现类,它增强了一个重要的性质。它允许自定义格式输出模板。构造SimpleDateFormat实例的时候,可以传入一个pattern作为输出模板。看个例子:

    public static void main(String[] args) {
        Calendar c = Calendar.getInstance();
        SimpleDateFormat sm = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒");
        System.out.println(sm.format(c.getTime()));
    }

输出结果:

2017年05月29日 星期一 20时25分31秒

上述的代码中,字符串yyyy年MM月dd日 E HH时mm分ss秒就是一个模板pattern,其中:

  • yyyy表示使用四位数字输出年份
  • MM表示使用两位数字表示月份
  • dd表示使用两位数字表示日
  • E表示星期几
  • HH表示使用两位数字表示小时(24以内)
  • mm和ss分别表示分钟和秒数

其中需要注意一点的是,m这个字母大写状态被用作表示月份,小写状态被用作表示分钟,不能混用二者。除了可以使用HH表示小时以外,hh也可以表示小时,只是它是12的(上午和下午)。当然我们也可以逆向操作:

    public static void main(String[] args) throws ParseException {
        Calendar c = Calendar.getInstance();
        SimpleDateFormat sm = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒");
        String s = "2016年11月11日 星期五 00时00分00秒";
        System.out.println(sm.parse(s));
    }

输出结果:

Fri Nov 11 00:00:00 CST 2016

五、开源第三方库Joda-Time
     Joda-Time库中的内容还是很多的,我们简单了解下基本的使用即可,至于深入学习该库,大家可以自行尝试,此处限于篇幅,不再赘述。在该库中DateTime相当于jdk中Calendar,主要完成对日期年月日的计算操作。首先我们通过简单易理解的方式创建DateTime的实例对象:

//2017-05-29 21:40
DateTime dt = new DateTime(2017,5,29,21,40);

//2017-05-29 21:40 50秒
DateTime dt2 = new DateTime(2017,5,29,21,40,50);

创建一个日期实例比Calendar中为每个属性set值方便多了。在该库中获取日期的操作被分解了,不像Calendar中共享一个int数组。

DateTime dt = new DateTime(2017,5,29,21,40);
System.out.println("year: "+dt.getYear());
System.out.println("month: "+dt.getMonthOfYear());
System.out.println("day: "+dt.getDayOfMonth());
System.out.println("hour: "+dt.getHourOfDay());
System.out.println("minute: "+dt.getMinuteOfHour());
System.out.println("second: "+dt.getSecondOfMinute());
System.out.println("millisecond: " +dt.getMillisOfSecond());
System.out.println("day_of_week: " +dt.getDayOfWeek());

我们也可以直接使用DateTime的tostring方法来实现将日期转换成指定pattern的字符串,例如:

DateTime dt = new DateTime(2017,5,29,21,40);
System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));

上述代码将会把日期类型按照指定的模板输出,该Joda-Time库中内容很多,此处就简单介绍到这, 感兴趣的同学可以自行研究,该库的核心优势就在于它将很多复杂的操作分解为单个简单操作,这也是我们程序设计中核心的思维方式。

有关Java中日期和时间的内容本篇已经简单介绍完了,有理解不到之处,望大家指出,相互学习!

前言

本来想写下Java 8的日期/时间API,发现已经有篇不错的文章了,那就直接转载吧~

PS:主要内容没变,做了部分修改。

原文链接: journaldev 翻译: ImportNew.com - Justin Wu
译文链接: http://www.importnew.com/14140.html

Java 8中的日期/时间(Date/Time)API是开发人员最受追捧的变化之一,Java从一开始就没有对日期/时间一致性处理的方法,因此在Java 8中新增的日期/时间API也是除Java核心API以外另一项倍受欢迎的内容。

为什么我们需要新的Java日期/时间API?

在开始研究Java 8日期/时间API之前,让我们先来看一下为什么我们需要这样一个新的API。在Java 8之前中,日期和时间相关的类存在如下问题:

  • Java中日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。
  • java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求。
  • 所有的日期类都是可变的,因此他们都不是线程安全的,这是Java日期类最大的问题之一。
  • 日期类并不提供国际化,没有时区支持,虽然引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

在现有的日期和日历类中定义的方法还存在一些其他的问题,但以上问题已经很清晰地表明:Java需要一个健壮的日期/时间类。这也是为什么Joda Time库,作为旧的日期/时间API的替换者,在Java 8日期/时间需求中扮演重要角色的原因。

Java 8日期/时间API

Java 8 Date, LocalDate, LocalDateTime, Instant

Java 8日期/时间API是JSR-310规范的实现,它的目标是克服旧的日期/时间API实现中所有的缺陷,新的日期/时间API的一些设计原则如下:

  • 不变性:新的日期/时间API中,所有的类都是不可变的,这种设计有利于并发编程。
  • 关注点分离:新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
  • 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用now()方法,在所有的类中都定义了format()和parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
  • 实用操作:所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分等操作。
  • 可扩展性:新的日期/时间API是工作在ISO-8601日历系统上的,但我们也可以将其应用在非IOS的日历上。

Java日期/时间API包

Java日期/时间API由以下包组成:

  • java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在大多数情况下,这些类足够应付常见需求。
  • java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。
  • java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。
  • java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。
  • java.time.zone包:这个包包含支持不同时区以及相关规则的类。

Java日期/时间API示例

我们已经浏览了Java 8日期/时间API中的大多数重要部分,是时候根据示例深入了解其中的核心类了。

LocalDate

LocalDate是一个不可变的类,它表示默认格式(yyyy-MM-dd)的日期,我们可以使用now()方法得到当前时间,也可以提供输入年份、月份和日期的输入参数来创建一个LocalDate实例。该类为now()方法提供了重载方法,我们可以传入ZoneId来获得指定时区的日期。该类提供与java.sql.Date相同的功能,对于如何使用该类,我们来看一个简单的例子。

复制代码
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;

/**
 * LocalDate Examples
 * 
 * @author pankaj
 *
 */
public class LocalDateExample
{

    public static void main(String[] args)
    {

        // 当前日期
        LocalDate today = LocalDate.now();
        System.out.println("当前日期=" + today);

        // 通过传入的参数创建LocalDate对象
        LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);
        System.out.println("指定日期=" + firstDay_2014);

        // 根据有效输入创建日期,以下代码会抛异常,无效输入,2014年2月没有29日
        // LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
        // Exception in thread "main" java.time.DateTimeException:
        // Invalid date 'February 29' as '2014' is not a leap year

        // 获取不同时区的日期 "Asia/Kolkata", you can get it from ZoneId javadoc
        LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
        System.out.println("当前印度标准日期=" + todayKolkata);

        // java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
        // LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));

        // 从基准日期获取日期  例如: 01/01/1970
        LocalDate dateFromBase = LocalDate.ofEpochDay(365);
        System.out.println("基准日期的第365天= " + dateFromBase);

        //2014年的第一百天
        LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);
        System.out.println("2014年的第一百天=" + hundredDay2014);
    }

}
复制代码

示例方法的详解都包含在注释内,当我们运行程序时,可以得到以下输出:

当前日期=2016-10-17
指定日期=2014-01-01
当前印度标准日期=2016-10-17
基准日期的第365天= 1971-01-01
2014年的第一百天=2014-04-10

LocalTime

LocalTime是一个不可变的类,它的实例代表一个符合人类可读格式的时间,默认格式是hh:mm:ss.zzz。像LocalDate一样,该类也提供了时区支持,同时也可以传入小时、分钟和秒等输入参数创建实例,我们来看一个简单的程序,演示该类的使用方法。

复制代码
import java.time.LocalTime;
import java.time.ZoneId;

/**
 * LocalTime Examples
 * 
 * @author pankaj
 *
 */
public class LocalTimeExample
{

    public static void main(String[] args)
    {

        // Current Time
        LocalTime time = LocalTime.now();
        System.out.println("Current Time=" + time);

        // Creating LocalTime by providing input arguments
        LocalTime specificTime = LocalTime.of(12, 20, 25, 40);
        System.out.println("Specific Time of Day=" + specificTime);

        // Try creating time by providing invalid inputs
        // LocalTime invalidTime = LocalTime.of(25,20);
        // Exception in thread "main" java.time.DateTimeException:
        // Invalid value for HourOfDay (valid values 0 - 23): 25

        // Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
        LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));
        System.out.println("Current Time in IST=" + timeKolkata);

        // java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
        // LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));

        // Getting date from the base date i.e 01/01/1970
        LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
        System.out.println("10000th second time= " + specificSecondTime);

    }

}
复制代码

当运行以上程序时,可以看到如下输出。

Current Time=16:19:00.232
Specific Time of Day=12:20:25.000000040
Current Time in IST=13:49:00.233
10000th second time= 02:46:40

LocalDateTime

LocalDateTime是一个不可变的日期-时间对象,它表示一组日期-时间,默认格式是yyyy-MM-dd-HH-mm-ss.zzz。它提供了一个工厂方法,通过接收LocalDate和LocalTime输入参数,来创建LocalDateTime实例。我们来看一个简单的例子。

复制代码
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class LocalDateTimeExample
{

    public static void main(String[] args)
    {

        // Current Date
        LocalDateTime today = LocalDateTime.now();
        System.out.println("Current DateTime=" + today);

        // Current Date using LocalDate and LocalTime
        today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
        System.out.println("Current DateTime=" + today);

        // Creating LocalDateTime by providing input arguments
        LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);
        System.out.println("Specific Date=" + specificDate);

        // Try creating date by providing invalid inputs
        // LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28,
        // 25,1,1);
        // Exception in thread "main" java.time.DateTimeException:
        // Invalid value for HourOfDay (valid values 0 - 23): 25

        // Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
        LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
        System.out.println("Current Date in IST=" + todayKolkata);

        // java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
        // LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));

        // Getting date from the base date i.e 01/01/1970
        LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
        System.out.println("10000th second time from 01/01/1970= " + dateFromBase);

    }

}
复制代码
在所有这三个例子中,我们已经看到如果我们提供了无效的参数去创建日期/时间,那么系统会抛出java.time.DateTimeException,这是一种运行时异常,我们并不需要显式地捕获它。同时我们也看到,能够通过传入ZoneId得到日期/时间数据,你可以从它的Javadoc中得到支持的Zoneid的列表,当运行以上类时,可以得到以下输出。
Current DateTime=2016-10-17T16:22:19.453
Current DateTime=2016-10-17T16:22:19.453
Specific Date=2014-01-01T10:10:30
Current Date in IST=2016-10-17T13:52:19.454
10000th second time from 01/01/1970= 1970-01-01T02:46:40

Instant

Instant类是用在机器可读的时间格式上的,它以Unix时间戳的形式存储日期时间,我们来看一个简单的程序。

复制代码
import java.time.Duration;
import java.time.Instant;

public class InstantExample
{

    public static void main(String[] args)
    {
        // Current timestamp
        Instant timestamp = Instant.now();
        System.out.println("Current Timestamp = " + timestamp);

        // Instant from timestamp
        Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
        System.out.println("Specific Time = " + specificTime);

        // Duration example
        Duration thirtyDay = Duration.ofDays(30);
        System.out.println(thirtyDay);
    }

}
复制代码

输出结果如下:

Current Timestamp = 2016-10-17T08:25:08.069Z
Specific Time = 2016-10-17T08:25:08.069Z
PT720H

JAVA 8日期/时间工具方法

我们早些时候提到过,大多数日期/时间API类都实现了一系列工具方法,如:加/减天数、周数、月份数,等等。还有其他的工具方法能够使用TemporalAdjuster调整日期,并计算两个日期间的周期。

复制代码
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class DateAPIUtilities
{

    public static void main(String[] args)
    {

        LocalDate today = LocalDate.now();

        // Get the Year, check if it's leap year
        System.out.println("Year " + today.getYear() + " is Leap Year? " + today.isLeapYear());

        // Compare two LocalDate for before and after
        System.out.println("Today is before 01/01/2015? " + today.isBefore(LocalDate.of(2015, 1, 1)));

        // Create LocalDateTime from LocalDate
        System.out.println("Current Time=" + today.atTime(LocalTime.now()));

        // plus and minus operations
        System.out.println("10 days after today will be " + today.plusDays(10));
        System.out.println("3 weeks after today will be " + today.plusWeeks(3));
        System.out.println("20 months after today will be " + today.plusMonths(20));

        System.out.println("10 days before today will be " + today.minusDays(10));
        System.out.println("3 weeks before today will be " + today.minusWeeks(3));
        System.out.println("20 months before today will be " + today.minusMonths(20));

        // Temporal adjusters for adjusting the dates
        System.out.println("First date of this month= " + today.with(TemporalAdjusters.firstDayOfMonth()));
        LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
        System.out.println("Last date of this year= " + lastDayOfYear);

        Period period = today.until(lastDayOfYear);
        System.out.println("Period Format= " + period);
        System.out.println("Months remaining in the year= " + period.getMonths());
    }
}
复制代码

上述程序的输出是:

Year 2016 is Leap Year? true
Today is before 01/01/2015? false
Current Time=2016-10-17T16:30:30.743
10 days after today will be 2016-10-27
3 weeks after today will be 2016-11-07
20 months after today will be 2018-06-17
10 days before today will be 2016-10-07
3 weeks before today will be 2016-09-26
20 months before today will be 2015-02-17
First date of this month= 2016-10-01
Last date of this year= 2016-12-31
Period Format= P2M14D
Months remaining in the year= 2

解析和格式化

将一个日期格式转换为不同的格式,之后再解析一个字符串,得到日期时间对象,这些都是很常见的。我们来看一下简单的例子。

复制代码
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateParseFormatExample
{

    public static void main(String[] args)
    {

        // Format examples
        LocalDate date = LocalDate.now();
        // default format
        System.out.println("Default format of LocalDate=" + date);
        // specific format
        System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
        System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));

        LocalDateTime dateTime = LocalDateTime.now();
        // default format
        System.out.println("Default format of LocalDateTime=" + dateTime);
        // specific format
        System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
        System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));

        Instant timestamp = Instant.now();
        // default format
        System.out.println("Default format of Instant=" + timestamp);

        // Parse examples
        LocalDateTime dt = LocalDateTime.parse("27::四月::2014 21::39::48",
            DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));
        System.out.println("Default format after parsing = " + dt);
    }

}
复制代码

当运行以上程序时,可以看到如下输出:

Default format of LocalDate=2016-10-17
17::十月::2016
20161017
Default format of LocalDateTime=2016-10-17T16:37:47.846
17::十月::2016 16::37::47
20161017
Default format of Instant=2016-10-17T08:37:47.847Z
Default format after parsing = 2014-04-27T21:39:48

旧的日期时间支持

旧的日期/时间类已经在几乎所有的应用程序中使用,因此做到向下兼容是必须的。这也是为什么会有若干工具方法帮助我们将旧的类转换为新的类,反之亦然。我们来看一下简单的例子。

复制代码
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class DateAPILegacySupport
{

    public static void main(String[] args)
    {

        // Date to Instant
        Instant timestamp = new Date().toInstant();
        // Now we can convert Instant to LocalDateTime or other similar classes
        LocalDateTime date = LocalDateTime.ofInstant(timestamp,
            ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
        System.out.println("Date = " + date);

        // Calendar to Instant
        Instant time = Calendar.getInstance().toInstant();
        System.out.println(time);
        // TimeZone to ZoneId
        ZoneId defaultZone = TimeZone.getDefault().toZoneId();
        System.out.println(defaultZone);

        // ZonedDateTime from specific Calendar
        ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
        System.out.println(gregorianCalendarDateTime);

        // Date API to Legacy classes
        Date dt = Date.from(Instant.now());
        System.out.println(dt);

        TimeZone tz = TimeZone.getTimeZone(defaultZone);
        System.out.println(tz);

        GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
        System.out.println(gc);

    }

}
复制代码

当运行以上程序时,可以看到如下输出。

Date = 2016-10-17T01:39:34.121
2016-10-17T08:39:34.176Z
Asia/Shanghai
2016-10-17T16:39:34.191+08:00[Asia/Shanghai]
Mon Oct 17 16:39:34 CST 2016
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
java.util.GregorianCalendar[time=1476693574191,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2016,MONTH=9,WEEK_OF_YEAR=42,WEEK_OF_MONTH=3,DAY_OF_MONTH=17,DAY_OF_YEAR=291,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=39,SECOND=34,MILLISECOND=191,ZONE_OFFSET=28800000,DST_OFFSET=0]
你可以看到,旧的TimeZone和GregorianCalendar类的toString()方法太啰嗦了,一点都不友好。

结语

这就是所有的Java 8 日期/时间API的内容,我非常喜欢这个API,它易于使用,同时它采取了某项工作,使相似的方法也易于寻找,虽然从旧的类转移到新的日期时间类需要消耗一定的时间,但我相信这是值得的。