时效提示:本文写于 2020 年,基于当时的 Java 8,部分 API、配置和工具版本可能已经变化,参考时请以官方最新文档为准。
为什么使用JDK8的时间
好用!
好用!
好用!
高大上,线程安全,计算方便等一些列的好处,你什么不使用它
JDK8时间
| 编号 | 类的名称 | 描述 |
|---|---|---|
| 1 | Instant | 时间戳 |
| 2 | Duration | 持续时间,时间差 |
| 3 | LocalDate | 只包含日期,比如:2018-02-05 |
| 4 | LocalTime | 只包含时间,比如:23:12:10 |
| 5 | LocalDateTime | 包含日期和时间,比如:2018-02-05 23:14:21 |
| 6 | Period | 时间段 |
| 7 | ZoneOffset | 时区偏移量,比如:+8:00 |
| 8 | ZonedDateTime | 带时区的时间 |
| 9 | Clock | 时钟,比如获取目前美国纽约的时间 |
| 10 | java.time.format.DateTimeFormatter | 时间格式化 |
e.g. 1. Java 8中处理特定日期
我们通过静态工厂方法now()非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法LocalDate.of()创建任意日期, 该方法需要传入年、月、日做参数,返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误,比如年度起始于1900,月份是从0开 始等等。
import java.time.LocalDate;
public class Demo03 {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2018,2,6);
System.out.println("自定义日期:"+date);
}
}
e.g. 2. Java 8中检查像生日这种周期性事件
import java.time.LocalDate;
import java.time.MonthDay;
public class Demo05 {
public static void main(String[] args) {
LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.of(2018,2,6);
MonthDay birthday = MonthDay.of(date2.getMonth(),date2.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(date1);
if(currentMonthDay.equals(birthday)){
System.out.println("是你的生日");
}else{
System.out.println("你的生日还没有到");
}
}
}
只要当天的日期和生日匹配,无论是哪一年都会打印出祝贺信息。你可以把程序整合进系统时钟,看看生日时是否会受到提醒,或者写一个单元测试来检测代码是否运行正确。
e.g. 3. Java 8的Clock时钟类
Java 8增加了一个Clock时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。以前用到System.currentTimeInMillis()和TimeZone.getDefault()的地方都可用Clock替换。
import java.time.Clock;
public class Demo10 {
public static void main(String[] args) {
// Returns the current time based on your system clock and set to UTC.
Clock clock = Clock.systemUTC();
System.out.println("Clock : " + clock.millis());
// Returns time based on system clock zone
Clock defaultClock = Clock.systemDefaultZone();
System.out.println("Clock : " + defaultClock.millis());
}
}
e.g. 4. Java 8中处理时区
Java 8不仅分离了日期和时间,也把时区分离出来了。现在有一系列单独的类如ZoneId来处理特定时区,ZoneDateTime类来表示某时区下的时间。这在Java 8以前都是 GregorianCalendar类来做的。下面这个例子展示了如何把本时区的时间转换成另一个时区的时间。
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Demo12 {
public static void main(String[] args) {
// Date and time with timezone in Java 8
ZoneId america = ZoneId.of("America/New_York");
LocalDateTime localtDateAndTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america );
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);
}
}
e.g. 5. 如何表示信用卡到期这类固定日期,答案就在YearMonth
与 MonthDay检查重复事件的例子相似,YearMonth是另一个组合类,用于表示信用卡到期日、FD到期日、期货期权到期日等。还可以用这个类得到 当月共有多少天,YearMonth实例的lengthOfMonth()方法可以返回当月的天数,在判断2月有28天还是29天时非常有用。
import java.time.*;
public class Demo13 {
public static void main(String[] args) {
YearMonth currentYearMonth = YearMonth.now();
System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2019, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
}
}
e.g. 6. 如何在Java 8中检查闰年
import java.time.LocalDate;
public class Demo14 {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
if(today.isLeapYear()){
System.out.println("This year is Leap year");
}else {
System.out.println("This year is not a Leap year");
}
}
}
e.g. 7. 计算两个日期之间的天数和月数
有一个常见日期操作是计算两个日期之间的天数、周数或月数。在Java 8中可以用java.time.Period类来做计算。
下面这个例子计算 Java 8 发布(2014-03-18)至今的时间。注意 Period.getMonths() 只返回“年月日”拆分后的月份部分(0~11),要算总月数或总天数用 ChronoUnit。
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class Demo15 {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate java8Release = LocalDate.of(2014, 3, 18);
Period period = Period.between(java8Release, today);
System.out.println("Java 8 发布至今:" + period.getYears() + " 年 " + period.getMonths() + " 个月 " + period.getDays() + " 天");
long totalMonths = ChronoUnit.MONTHS.between(java8Release, today);
long totalDays = ChronoUnit.DAYS.between(java8Release, today);
System.out.println("总月数:" + totalMonths + ",总天数:" + totalDays);
}
}e.g. 8. 在Java 8中获取当前的时间戳
Instant类有一个静态工厂方法now()会返回当前的时间戳,如下所示:
import java.time.Instant;
public class Demo16 {
public static void main(String[] args) {
Instant timestamp = Instant.now();
System.out.println("What is value of this instant " + timestamp.toEpochMilli());
}
}
时间戳信息里同时包含了日期和时间,这和java.util.Date很像。实际上Instant类确实等同于 Java 8之前的Date类,你可以使用Date类和Instant类各自的转换方法互相转换,例如:Date.from(Instant) 将Instant转换成java.util.Date,Date.toInstant()则是将Date类转换成Instant类。
e.g. 9. Java 8中如何使用预定义的格式化工具去解析或格式化日期
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Demo17 {
public static void main(String[] args) {
String dayAfterTommorrow = "20180205";
LocalDate formatted = LocalDate.parse(dayAfterTommorrow,
DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(dayAfterTommorrow+" 格式化后的日期为: "+formatted);
}
}
e.g. 10. 字符串互转日期类型
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Demo18 {
public static void main(String[] args) {
LocalDateTime date = LocalDateTime.now();
DateTimeFormatter format1 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
//日期转字符串
String str = date.format(format1);
System.out.println("日期转换为字符串:"+str);
DateTimeFormatter format2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
//字符串转日期
LocalDate date2 = LocalDate.parse(str,format2);
System.out.println("日期类型:"+date2);
}
}
LocalDateTime基础
时间类
LocalDate yyyy-MM-dd
LocalTime HH:mm:ss
LocalDateTime yyyy-MM-dd HH:mm:ss
时间格式
常见的格式是:
yyyy-MM-dd HH:mm:ss
对应结果
2018-01-23 15:02:43
一般情况如上,那么有些特殊的地方用到特殊的用法,记不住备用以后查看
/**
日期格式化类(必须掌握)
API:
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
*/
yyyy-MM-dd E a HH:mm:ss
2018-01-23 星期二 下午 15:05:27
LocalDateTime时间转换
LocalDateTime 本身不带时区,和 Date、时间戳互转时必须指定时区(下面用系统默认时区):
ZoneId zone = ZoneId.systemDefault();
// Date <-> LocalDateTime
LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), zone);
Date date = Date.from(ldt.atZone(zone).toInstant());
// 毫秒时间戳 <-> LocalDateTime
LocalDateTime ldt2 = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), zone);
long millis2 = ldt.atZone(zone).toInstant().toEpochMilli();
// String <-> LocalDateTime(DateTimeFormatter 是线程安全的,可以定义成常量)
DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String text = ldt.format(FMT);
LocalDateTime parsed = LocalDateTime.parse("2020-01-12 10:30:00", FMT);
// LocalDate <-> LocalDateTime
LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); // 当天 00:00:00
LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); // 当天 23:59:59.999999999
LocalDate day = ldt.toLocalDate();
// java.sql.Timestamp <-> LocalDateTime(JDBC 场景)
Timestamp ts = Timestamp.valueOf(ldt);
LocalDateTime fromTs = ts.toLocalDateTime();旧代码里的
SimpleDateFormat不是线程安全的,不能做成静态共享变量;换成DateTimeFormatter可以直接共享。数据库时区和应用时区不一致时,入库时间会差 8 小时,JDBC 连接串里显式指定
serverTimezone=Asia/Shanghai。
Spring MVC
Form表单请求 格式化时间
Form表单请求指的是,以QueryString方式传递参数或者Form-Data传递时的请求
- 字符串转LocalDateTime
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

- Long转LocalDateTime
增加一个Converter
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* @author wangqimeng
* @date 2020/1/13 14:52
*/
@Component
public class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String source) {
if (!NumberUtils.isDigits(source)) {
return null;
}
Long milli = NumberUtils.createLong(source);
return LocalDateTime.ofEpochSecond(milli, 0, ZoneOffset.UTC);
}
}

注意 上下两种方式不能兼容使用 Convert级别高
Json 格式化时间
Json解析前提
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
or 已经有jackson 缺少
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
相关基础注解
基础注解
@DateTimeFormat(pattern = "yyyy-MM-dd\'T\'HH:mm:ss.SSS")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd\'T\'HH:mm:ss.SSS")
描述:
关于Long与LocalDateTime相互转换
- Long转LocalDateTime
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* @author <a href="mailto:boommanpro@gmail.com">boommanpro</a>
* @date 2019/7/19 14:20
*/
public class Long2LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String time=jsonParser.getText();
Instant instant = Instant.ofEpochMilli(Long.parseLong(time));
ZoneId zone = ZoneId.systemDefault();
return LocalDateTime.ofInstant(instant, zone);
}
}
- LocalDateTime2Long
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
/**
* @author <a href="mailto:boommanpro@gmail.com">boommanpro</a>
* @date 2019/7/19 14:20
*/
public class LocalDateTime2LongSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(String.valueOf(value.toInstant(ZoneOffset.of("+8")).toEpochMilli()));
}
}
Spring Data Jpa
Jpa语法查询
JPA可以根据你写的一个函数名来生成对应的SQL语句,当然函数名是要符合特定规则的。
这里介绍between和in的用法
@Query(nativeQuery = true,
value = "select * from z_cashier_data" +
"where abstract_code = ?1 " +
"and time BETWEEN ?2 and ?3")
Page<CashierData> findAllabcd(String code, LocalDateTime start, LocalDateTime end, Pageable pageable);//函数名随意
相当于下面这条
Page<CashierData> findAllByAbstractCodeAndTimeBetween(String code, LocalDateTime start, LocalDateTime end, Pageable pageable);
Specification语法
LocalDateTime start, LocalDateTime end
criteriaBuilder.between(root.get("workDay"),start,end)
Es LocalDateTime 相关注解
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Field(index = false, type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")