Skip to content

Commit 7a5bf49

Browse files
committed
📝 add datetimeapi readme
1 parent 7d5967d commit 7a5bf49

File tree

14 files changed

+681
-52
lines changed

14 files changed

+681
-52
lines changed

java8-datetime-api/README.md

Lines changed: 186 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
# Java 8 新的日期时间 API
2+
3+
## ZoneId
4+
5+
Java 8中的时区操作被很大程度上简化了,新的时区类 `java.time.ZoneId` 是原有的 `java.util.TimeZone` 类的替代品。
6+
ZoneId对象可以通过 `ZoneId.of()` 方法创建,也可以通过 `ZoneId.systemDefault()` 获取系统默认时区:
7+
8+
```java
9+
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
10+
ZoneId systemZoneId = ZoneId.systemDefault();
11+
```
12+
13+
`of()` 方法接收一个“区域/城市”的字符串作为参数,你可以通过 `getAvailableZoneIds()` 方法获取所有合法的“区域/城市”字符串:
14+
15+
```java
16+
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
17+
```
18+
19+
对于老的时区类 `TimeZone`,Java 8也提供了转化方法:
20+
21+
```java
22+
ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();
23+
```
24+
25+
有了 `ZoneId`,我们就可以将一个 `LocalDate``LocalTime``LocalDateTime` 对象转化为 `ZonedDateTime` 对象:
26+
27+
```java
28+
LocalDateTime localDateTime = LocalDateTime.now();
29+
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);
30+
```
31+
32+
`ZonedDateTime` 对象由两部分构成,`LocalDateTime``ZoneId`,其中 `2018-03-03T15:26:56.147` 部分为 `LocalDateTime``+08:00[Asia/Shanghai]` 部分为ZoneId。
33+
34+
另一种表示时区的方式是使用 `ZoneOffset`,它是以当前时间和 **世界标准时间(UTC)/格林威治时间(GMT)** 的偏差来计算,例如:
35+
36+
```java
37+
ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
38+
LocalDateTime localDateTime = LocalDateTime.now();
39+
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);
40+
```
141

242
## Instant
343

@@ -43,6 +83,20 @@ Instant earlier = now.minusSeconds(3);
4383

4484
第一行获得了一个Instant对象,表示当前时间。第二行创建了一个Instant表示三秒后,第三行创建了一个Instant表示三秒前。
4585

86+
> seconds 表示从 `1970-01-01 00:00:00` 开始到现在的秒数,nanos 表示纳秒部分(nanos的值不会超过999,999,999)
87+
88+
## Clock
89+
90+
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。
91+
某一个特定的时间点也可以使用Instant类来表示,Instant 类也可以用来创建老的 `java.util.Date` 对象。
92+
93+
```java
94+
Clock clock = Clock.systemDefaultZone();
95+
long millis = clock.millis();
96+
Instant instant = clock.instant();
97+
Date legacyDate = Date.from(instant); // legacy java.util.Date
98+
```
99+
46100
## LocalDate
47101

48102
LocalDate类是Java 8中日期时间功能里表示一个本地日期的类,它的日期是无时区属性的。
@@ -61,7 +115,7 @@ LocalDate localDate = LocalDate.now();
61115
另一种方法是使用年月日信息构造出LocalDate对象:
62116

63117
```java
64-
LocalDate localDate2 = LocalDate.of(2015, 12, 31);
118+
LocalDate localDate2 = LocalDate.of(2018, 3, 3);
65119
```
66120

67121
LocalDate 的 `of()` 方法创建出一个指定年月日的日期,并且没有时区信息。
@@ -161,55 +215,6 @@ LocalTime localTimeEarlier = localTime.minusHours(3);
161215

162216
LocalTime类的对象也是不可变的,所以计算方法会返回一个新的LocalTime实例。
163217

164-
**创建一个LocalTime实例**
165-
166-
有多种方式可以新建LocalTime实例。比如使用当前时间作为值新建对象:
167-
168-
```java
169-
LocalTime localTime = LocalTime.now();
170-
```
171-
172-
另一种方式是使用指定的时分秒和纳秒来新建对象:
173-
174-
```java
175-
LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);
176-
```
177-
178-
也有另一种版本的 `of()` 方法只需要小时分钟两项,或时分秒三项值作为参数。
179-
180-
**访问LocalTime对象的时间**
181-
182-
你可以通过这些方法访问其时、分、秒、纳秒:
183-
184-
- `getHour()`
185-
- `getMinute()`
186-
- `getSecond()`
187-
- `getNano()`
188-
189-
**LocalTime的计算**
190-
191-
LocalTime类包含一系列方法,能帮你完成时间计算:
192-
193-
- `plusHours()`
194-
- `plusMinutes()`
195-
- `plusSeconds()`
196-
- `plusNanos()`
197-
- `minusHours()`
198-
- `minusMinutes()`
199-
- `minusSeconds()`
200-
- `minusNanos()`
201-
202-
以下举一个例子:
203-
204-
```java
205-
LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);
206-
LocalTime localTimeLater = localTime.plusHours(3);
207-
LocalTime localTimeEarlier = localTime.minusHours(3);
208-
```
209-
210-
1. 第一行新建一个LocalTime实例,表示21:30:50的第11001纳秒。
211-
2. 第二行新建了一个LocalTime实例表示这个时间的三小时后,第三行表示三小时前。
212-
213218
## LocalDateTime
214219

215220
LocalDateTime类是Java 8中日期时间功能里,用于表示当地的日期与时间的类,它的值是无时区属性的。你可以将其视为Java 8中LocalDate与LocalTime两个类的结合。
@@ -227,7 +232,7 @@ LocalDateTime localDateTime = LocalDateTime.now();
227232
另一种方式是使用指定的年月日、时分秒、纳秒来新建对象:
228233

229234
```java
230-
LocalDateTime localDateTime2 = LocalDateTime.of(2015, 11, 26, 13, 55, 36, 123);
235+
LocalDateTime localDateTime2 = LocalDateTime.of(2018, 11, 26, 13, 55, 36, 123);
231236
```
232237

233238
**访问LocalDateTime对象的时间**
@@ -480,4 +485,133 @@ Duration subtracted = start.minusDays(3);
480485
1. 第一行创建了一个Duration对象叫start,具体怎么创建可以参考前面的代码。
481486
2. 第二三行样例创建了两个新的Duration,通过调用start的加减操作,使得added对象表示的时间比start多三天,而substracted则少三天。
482487

483-
所有的计算方法都会返回一个新的Duration,以保证Duration的不可变属性。
488+
所有的计算方法都会返回一个新的Duration,以保证Duration的不可变属性。
489+
490+
```java
491+
long days = duration.toDays(); // 这段时间的总天数
492+
long hours = duration.toHours(); // 这段时间的小时数
493+
long minutes = duration.toMinutes(); // 这段时间的分钟数
494+
long seconds = duration.getSeconds(); // 这段时间的秒数
495+
long milliSeconds = duration.toMillis(); // 这段时间的毫秒数
496+
long nanoSeconds = duration.toNanos(); // 这段时间的纳秒数
497+
```
498+
499+
## 其他操作
500+
501+
### 增加和减少日期
502+
503+
Java 8中的日期/时间类都是不可变的,这是为了保证线程安全。当然,新的日期/时间类也提供了方法用于创建对象的可变版本,比如增加一天或者减少一天:
504+
505+
```java
506+
LocalDate date = LocalDate.of(2017, 1, 5); // 2017-01-05
507+
508+
LocalDate date1 = date.withYear(2016); // 修改为 2016-01-05
509+
LocalDate date2 = date.withMonth(2); // 修改为 2017-02-05
510+
LocalDate date3 = date.withDayOfMonth(1); // 修改为 2017-01-01
511+
512+
LocalDate date4 = date.plusYears(1); // 增加一年 2018-01-05
513+
LocalDate date5 = date.minusMonths(2); // 减少两个月 2016-11-05
514+
LocalDate date6 = date.plus(5, ChronoUnit.DAYS); // 增加5天 2017-01-10
515+
```
516+
517+
上面例子中对于日期的操作比较简单,但是有些时候我们要面临更复杂的时间操作,比如将时间调到下一个工作日,
518+
或者是下个月的最后一天,这时候我们可以使用 `with()` 方法的另一个重载方法,它接收一个TemporalAdjuster参数,
519+
可以使我们更加灵活的调整日期:
520+
521+
```java
522+
LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY)); // 返回下一个距离当前时间最近的星期日
523+
LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY)); // 返回本月最后一个星期六
524+
```
525+
526+
要使上面的代码正确编译,你需要使用静态导入 `TemporalAdjusters` 对象:
527+
528+
`import static java.time.temporal.TemporalAdjusters.*;`
529+
530+
`TemporalAdjusters` 类中包含了很多静态方法可以直接使用,下面的表格列出了一些方法:
531+
532+
| 方法名 | 描述 |
533+
|:-----:|:-------|
534+
| `dayOfWeekInMonth` | 返回同一个月中每周的第几天 |
535+
| `firstDayOfMonth` | 返回当月的第一天 |
536+
| `firstDayOfNextMonth` | 返回下月的第一天 |
537+
| `firstDayOfNextYear` | 返回下一年的第一天 |
538+
| `firstDayOfYear` | 返回本年的第一天 |
539+
| `firstInMonth` | 返回同一个月中第一个星期几 |
540+
| `lastDayOfMonth` | 返回当月的最后一天 |
541+
| `lastDayOfNextMonth` | 返回下月的最后一天 |
542+
| `lastDayOfNextYear` | 返回下一年的最后一天 |
543+
| `lastDayOfYear` | 返回本年的最后一天 |
544+
| `lastInMonth` | 返回同一个月中最后一个星期几 |
545+
| `next / previous` | 返回后一个/前一个给定的星期几 |
546+
| `nextOrSame / previousOrSame` | 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回 |
547+
548+
如果上面表格中列出的方法不能满足你的需求,你还可以创建自定义的 `TemporalAdjuster` 接口的实现,
549+
`TemporalAdjuster` 也是一个函数式接口,所以我们可以使用Lambda表达式:
550+
551+
```java
552+
@FunctionalInterface
553+
public interface TemporalAdjuster {
554+
Temporal adjustInto(Temporal temporal);
555+
}
556+
```
557+
558+
比如给定一个日期,计算该日期的下一个工作日(不包括星期六和星期天):
559+
560+
```java
561+
LocalDate date = LocalDate.of(2017, 1, 5);
562+
date.with(temporal -> {
563+
// 当前日期
564+
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
565+
566+
// 正常情况下,每次增加一天
567+
int dayToAdd = 1;
568+
569+
// 如果是星期五,增加三天
570+
if (dayOfWeek == DayOfWeek.FRIDAY) {
571+
dayToAdd = 3;
572+
}
573+
574+
// 如果是星期六,增加两天
575+
if (dayOfWeek == DayOfWeek.SATURDAY) {
576+
dayToAdd = 2;
577+
}
578+
579+
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
580+
});
581+
```
582+
583+
### 其他历法
584+
585+
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。闰年的定义是:非世纪年,能被4整除;世纪年能被400整除。为了计算的一致性,公元1年的前一年被当做公元0年,以此类推。
586+
587+
588+
此外Java 8还提供了4套其他历法(很奇怪为什么没有汉族人使用的农历),每套历法都包含一个日期类,分别是:
589+
590+
- `ThaiBuddhistDate`:泰国佛教历
591+
- `MinguoDate`:中华民国历
592+
- `JapaneseDate`:日本历
593+
- `HijrahDate`:伊斯兰历
594+
595+
每个日期类都继承 `ChronoLocalDate` 类,所以可以在不知道具体历法的情况下也可以操作。不过这些历法一般不常用,除非是有某些特殊需求情况下才会使用。
596+
597+
这些不同的历法也可以用于向公历转换:
598+
599+
```java
600+
LocalDate date = LocalDate.now();
601+
JapaneseDate jpDate = JapaneseDate.from(date);
602+
```
603+
604+
由于它们都继承ChronoLocalDate类,所以在不知道具体历法情况下,可以通过ChronoLocalDate类操作日期:
605+
606+
```java
607+
Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE);
608+
ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow();
609+
```
610+
611+
我们在开发过程中应该尽量避免使用 `ChronoLocalDate`,尽量用与历法无关的方式操作时间,因为不同的历法计算日期的方式不一样,
612+
比如开发者会在程序中做一些假设,假设一年中有12个月,如果是中国农历中包含了闰月,一年有可能是13个月,
613+
但开发者认为是12个月,多出来的一个月属于明年的。
614+
再比如假设年份是累加的,过了一年就在原来的年份上加一,但日本天皇在换代之后需要重新纪年,所以过了一年年份可能会从1开始计算。
615+
616+
在实际开发过程中建议使用 `LocalDate`,包括存储、操作、业务规则的解读;除非需要将程序的输入或者输出本地化,
617+
这时可以使用 `ChronoLocalDate` 类。
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package io.github.biezhi.datetime;
2+
3+
import java.time.*;
4+
import java.util.Date;
5+
6+
/**
7+
* 日期转换示例
8+
*
9+
* @author biezhi
10+
* @date 2018/3/3
11+
*/
12+
public class ConvertExample {
13+
14+
/**
15+
* LocalDate -> Date
16+
*
17+
* @param localDate
18+
* @return
19+
*/
20+
public static Date toDate(LocalDate localDate) {
21+
ZoneId zone = ZoneId.systemDefault();
22+
Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
23+
return Date.from(instant);
24+
}
25+
26+
/**
27+
* LocalDateTime -> Date
28+
*
29+
* @param localDateTime
30+
* @return
31+
*/
32+
public static Date toDate(LocalDateTime localDateTime) {
33+
ZoneId zone = ZoneId.systemDefault();
34+
Instant instant = localDateTime.atZone(zone).toInstant();
35+
return Date.from(instant);
36+
}
37+
38+
/**
39+
* LocalTime -> Date
40+
*
41+
* @param localTime
42+
* @return
43+
*/
44+
public static Date toDate(LocalTime localTime) {
45+
LocalDate localDate = LocalDate.now();
46+
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
47+
ZoneId zone = ZoneId.systemDefault();
48+
Instant instant = localDateTime.atZone(zone).toInstant();
49+
return Date.from(instant);
50+
}
51+
52+
/**
53+
* Date -> LocalDate
54+
*
55+
* @param date
56+
* @return
57+
*/
58+
public static LocalDate toLocalDate(Date date) {
59+
Instant instant = date.toInstant();
60+
ZoneId zone = ZoneId.systemDefault();
61+
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
62+
return localDateTime.toLocalDate();
63+
}
64+
65+
/**
66+
* Date -> LocalDateTime
67+
*
68+
* @param date
69+
* @return
70+
*/
71+
public static LocalDateTime toLocalDateTime(Date date) {
72+
Instant instant = date.toInstant();
73+
ZoneId zone = ZoneId.systemDefault();
74+
return LocalDateTime.ofInstant(instant, zone);
75+
}
76+
77+
/**
78+
* Date -> LocalTime
79+
*
80+
* @param date
81+
* @return
82+
*/
83+
public static LocalTime toLocalTime(Date date) {
84+
Instant instant = date.toInstant();
85+
ZoneId zone = ZoneId.systemDefault();
86+
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
87+
return localDateTime.toLocalTime();
88+
}
89+
90+
91+
}

0 commit comments

Comments
 (0)