**
**
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。// 可以加锁
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
时区处理麻烦 −
日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,
但他们同样存在上述所有的问题。
1.LocalDate类
LocalDate类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息
// 通过of重载的工厂方法创建
LocalDate ofDate = LocalDate.of(2019, 3, 29);// 2019-03-29
LocalTime ofTime = LocalTime.of(13, 45, 20);// 13:45:20
//使用静态方法parse来创建
LocalDate parseDate = LocalDate.parse("2019-03-29");//2019-03-29
LocalTime parseTime = LocalTime.parse("13:45:20");// 13:45:20
// 读取LocalDate和LocalTime常用值的两种方式
//LocalDate 和 LocalTime 类提供了多种方法来 读取常用的值,比如年份、月份、星期几等
//小时
int hour = ofTime.getHour(); // 13
//分钟
int minute = ofTime.getMinute(); // 45
//秒
int second = ofTime.getSecond(); // 20
System.out.println(ofTime);// 13:45:20
//年
int year = ofDate.getYear(); // 2019
//月份
Month month = ofDate.getMonth(); // MARCH
//天数
int day = ofDate.getDayOfMonth(); // 29
//周几
DayOfWeek dow = ofDate.getDayOfWeek(); // TUESDAY
//月长度
int len = ofDate.lengthOfMonth(); // 31 (days in March)
//判断是否为闰年
boolean leap = ofDate.isLeapYear(); // false (判断是否为为闰年)
System.out.println(ofDate);
//通过传递一个TemporalField(时域)参数给get方法拿到同样的信息。
int y = ofDate.get(ChronoField.YEAR);//2019
int m = ofDate.get(ChronoField.MONTH_OF_YEAR);//3
int d = ofDate.get(ChronoField.DAY_OF_MONTH);//29
//几周
int dow2 = ofDate.get(ChronoField.DAY_OF_WEEK);//5
int hour2 = ofTime.get(ChronoField.HOUR_OF_DAY);//13
int minute2 = ofTime.get(ChronoField.MINUTE_OF_HOUR);//45
int second2 = ofTime.get(ChronoField.SECOND_OF_MINUTE);//20
//LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息。
//创建LocalDateTime的两种方式
//通过重载的of工厂方法创建
//2014-03-18T13:45:20
LocalDateTime dt1 = LocalDateTime.of(2019, Month.MARCH, 29, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(ofDate, ofTime);//2019-03-29T13:45:20
//通过合并日期和时间的方式创建
LocalDateTime dt3 = ofDate.atTime(13, 45, 20);//2019-03-29T13:45:20
LocalDateTime dt4 = ofDate.atTime(ofTime); //2019-03-29T13:45:20
LocalDateTime dt5 = ofTime.atDate(ofDate);//2019-03-29T13:45:20
// 从LocalDateTime中提取LocalDate或者LocalTime 组件
LocalDate date1 = dt1.toLocalDate();//2019-03-29
LocalTime time1 = dt1.toLocalTime();//13:45:20
//到目前为止,这些日期和时间都是不可修改的,这是为了更好的支持函数式编程,确保线程安全。
对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。withAttribute方法会创建对象的一个副本, 并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
LocalDate dd = LocalDate.of(2019,3,29); //2019-03-29
LocalDate dd1 = dd.withYear(2018); //2017-03-22
LocalDate dd2 = dd.withDayOfMonth(22); //2019-03-22
LocalDate dd4 = dd.withMonth(10); //2019-10-29改变月份
LocalDate dd3 = dd.with(ChronoField.MONTH_OF_YEAR,10); //2019-10-29改变月份
//除了withAttribute详细的年月日,也可以采用通用的with方法,第一个参数是TemporalField对象,第二个参数是修改的值。
它也可以操纵LocalDate对象:
LocalDate dd5 = dd.plusWeeks(1); //加一周2019-04-05
LocalDate dd6 = dd.minusYears(3); //减去三年2016-03-29
LocalDate dd7 = dd.plus(6,ChronoUnit.MONTHS); //加6月2019-09-29
操纵更复杂的日期,比如将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时可以使用with的重载版本,向>其传递一个提供了更多定制化选择的TemporalAdjuster(暂时调整器)对象,更加灵活的处理日期。
LocalDate dd = LocalDate.of(2018,8,23);
LocalDate dd1 = dd.with(dayOfWeekInMonth(2,DayOfWeek.FRIDAY));
//同一个月中,第二个星期五 2019-03-8
LocalDate dd2 = dd.with(firstDayOfMonth());
//当月的第一天 2019-03-01
LocalDate dd3 = dd.with(firstDayOfNextMonth());
//下月的第一天 2019-04-01
LocalDate dd4 = dd.with(firstDayOfNextYear());
//明年的第一天 2020-01-01
LocalDate dd5 = dd.with(firstDayOfYear());
//当年的第一天 2019-01-01
LocalDate dd6 = dd.with(firstInMonth(DayOfWeek.MONDAY));
//当月第一个星期一 2019-03-04
LocalDate dd7 = dd.with(lastDayOfMonth());
//当月的最后一天 2019-03-31
LocalDate dd8 = dd.with(lastDayOfYear());
//当年的最后一天 2019-12-31
LocalDate dd9 = dd.with(lastInMonth(DayOfWeek.SUNDAY));
//当月最后一个星期日 2019-03-31
LocalDate dd10 = dd.with(previous(DayOfWeek.MONDAY));
//将日期向前调整到第一个符合星期一 2019-03-25
LocalDate dd11 = dd.with(next(DayOfWeek.MONDAY));
//将日期向后调整到第一个符合星期一 2019-04-01
LocalDate dd12 = dd.with(previousOrSame(DayOfWeek.FRIDAY));
//将日期向前调整第一个符合星期五,如果该日期已经符合,直接返回该对象 2019-03-29
LocalDate dd13 = dd.with(nextOrSame(DayOfWeek.FRIDAY));
//将日期向后调整第一个符合星期五,如果该日期已经符合,直接返回该对象 2019-03-29
//TemporalAdjuster可以进行复杂的日期操作,如果没有找到符合的预定义方法,可以自己创建一个,TemporalAdjuster接口只声明了一个方法所以他说一个函数式接口
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
作为人,我们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问, 这种方式对于计算机而言并不容易理解。
从计算机的角度来看,建模时间最自然的格式是表示一 个持续时间段上某个点的单一大整型数。
//旧版本:Timestamp
//Java 8:Instant
//基本上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的 秒数进行计算。
// 通过向静态工厂方法ofEpochSecond传递一个代表秒数的值创建一个该类的实例。
Instant instant1 = Instant.ofEpochSecond(3);//1970-01-01T00:00:03Z
// ofEpochSecond的重载增强版本,它接收第二个以纳秒为单位的参数值,对传入作为秒数的参数进行调整。
// 2秒之后再加上 100万纳秒(1秒)
Instant instant3 = Instant.ofEpochSecond(2, 1_000_000_000);
//1970-01-01T00:00:04Z
//4秒之前的100万纳秒(1秒)
Instant instant4 = Instant.ofEpochSecond(4, -1_000_000_000);
//1970-01-01T00:00:03Z
Duration类主要用于以秒和纳秒衡量时间的长短。 Period类以年、月或者日的方式对多个时间单位建模。
LocalTime time1 = LocalTime.of(13, 45, 20); // 13:45:20
LocalTime time2 = LocalTime.of(20, 45, 20); // 13:45:20
LocalDateTime dateTime1 = LocalDateTime.of(2019, Month.MARCH, 29, 13, 45, 20); // 2019-03-29T13:45
LocalDateTime dateTime2 = LocalDateTime.of(2020, Month.MARCH, 29, 13, 45, 20); // 2020-03-29T13:45
Instant instant1 = Instant.ofEpochSecond(3);//1970-01-01T00:00:03Z
Instant instant2 = Instant.ofEpochSecond(4);//1970-01-01T00:00:04Z
LocalDate localDate1 = LocalDate.of(2019, 3, 19);//2019-03-19
LocalDate localDate2 = LocalDate.of(2019, 3, 28);//2019-03-19
//Duration的创建方式
//通过两个LocalTimes对象、两个LocalDateTimes对象、或者两个Instant对象创建duration
Duration d1 = Duration.between(time1,time2);//PT7H(25200)
Duration d2 = Duration.between(dateTime1, dateTime2);//PT876
System.out.println(d1.getSeconds());//25200
System.out.println(d2.getSeconds());//31536000
Duration d3 = Duration.between(instant1, instant2);//PT1S
//通过工厂类,直接创建对应的实例;
Duration threeMinutes1 = Duration.ofMinutes(3);//PT3M(180)
Duration threeMinutes2 = Duration.of(3, ChronoUnit.MINUTES);//PT3M(180)
//Duration的创建方式
//1. 通过两个LocalDate对象创建duration
Period tenDays = Period.between(localDate1,localDate2);//P9D这两个相差多少秒 多少天
//2. 通过工厂类,直接创建对应的实例;
Period tenDays2 = Period.ofDays(10);//P10D
Period threeWeeks = Period.ofWeeks(4);//P21D 几周多少天 28天
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);//P2Y6M1D
由于Duration类主要用于以秒和纳 秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象做参数。同样,Period以年、月或者日的方式对多个时间单位建模,所以只能传递LocalDate对象作为参数。
新的 java.time.format 包就是格式化以及解析日期、时间对象的。这个包中最重要的是DateTimeFormatter。
LocalDate date = LocalDate.of(2019, 3, 29);//2019-03-29
//1. 从时间生成字符串
// 使用特定不同的格式器生成字符串
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);//20190329
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2019-03-29
//DateTimeFormatter类还支持静态工厂方法,它可以按 照某个特定的模式创建格式器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter chinaFormatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String s3 = date.format(formatter);//29/03/2019
String s5 = date.format(chinaFormatter2);//2019年3月29日 星期五
// 从字符串生成时间
//通过解析代表日期或时间的字符串重新创建该日期对象。
LocalDate date1 = LocalDate.parse("20190329", DateTimeFormatter.BASIC_ISO_DATE);//2019-03-29
LocalDate date2 = LocalDate.parse("2019-03-29", DateTimeFormatter.ISO_LOCAL_DATE);//2019-03-29
LocalDate date3 = LocalDate.parse("29/03/2019", formatter);//2019-03-29
LocalDate date5 = LocalDate.parse("2019年3月19日 星期五", chinaFormatter2);//2019年3月29日 星期五
旧版本:TimeZone
java 8:ZoneId
之前你看到的Java8中的日期和时间的种类都不包含时区信息。时区的处理是新版日期和时间API新增 加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。跟其他日期和时间类一 样,ZoneId类也是无法修改的。
时区是按照一定的规则将区域划分成的标准时间相同的区间。每个特定 的ZoneId对象都由一个地区ID标识;
ZoneId romeZone = ZoneId.of("Europe/Rome");//以欧洲为标识
//地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA) 的时区数据库提供。java
8中支持的所有地区集合可以通过以下语句打印出来:
Set zoneIds= ZoneId.getAvailableZoneIds();
for (String zone : zoneIds) {
//共计599个
System.out.println(zone);
}
一旦得到一个ZoneId对象,你就可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime 实例,它代表了相对于指定时区的时间点. 一旦得到一个ZoneId对象,你就可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime 实例,它代表了相对于指定时区的时间点.
ZoneId romeZone = ZoneId.of("Europe/Rome");//以欧洲为标示
LocalDate date = LocalDate.of(2019, Month.MARCH, 29);//2019-03-29
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.now();//获取现在时间2019-03-30T23:26:12.992
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
Instant instant = Instant.now();//2019-03-30T15:25:13.136Z
ZonedDateTime zdt3 = instant.atZone(romeZone);
System.out.println(zdt1);//2019-03-29T00:00+01:00[Europe/Rome]
System.out.println(zdt2);//2019-03-30T23:23:09.396+01:00[Europe/Rome]
System.out.println(zdt3);//2019-0330T16:23:09.397+01:00[Europe/Rome]
LocalDateTime dateTime2 = LocalDateTime.of(2018,7,21,18,46,0);//2019-03-29T18:46
ZoneId romeZone2 = ZoneId.systemDefault();//Asia/Shanghai获取时区
Instant instantFromDateTime = dateTime2.atZone(romeZone2).toInstant();
System.out.println(instantFromDateTime.getEpochSecond());//1553856360秒
Instant (时间戳是一个确切的点)
提供了 比较方法 between
localtime localDate 都是依赖于clock(这是一个时钟类,就是访问日期和时间 底层是timenes 获取的是国际时间)
LocalDate 是不可变的
period(周期)比较
plus增加
minus减少
zonedDatetime(带时区的时间不可变的)
上边的那个类表示时区的类可以获取这一年的最后一天
国际时间用zonnedDateTo这还少UTC的比较符合国家标准
普通方法用localDateTime