目录
localDate 设值自定义日期 根据指定日期/时间创建对象
SimpleDateFormat问题
一、前言
日期的转换与格式化在项目中应该是比较常用的了
一个问题:项目中的日期转换怎么用的?SimpleDateFormat 用过吗?能说一下 SimpleDateFormat 线程安全问题吗,以及如何解决?
回答:平时就是在全局定义一个 static的 SimpleDateFormat,然后在业务处理方法(controller)中直接使用,至于线程安全… 这个… 倒是没遇到过线程安全问题。
下面讲解一下。
二、概述
SimpleDateFormat 类主要负责日期的转换与格式化等操作,在多线程的环境中,使用此类容易造成数据转换及处理的不正确,因为 SimpleDateFormat 类并不是线程安全的,但在单线程环境下是没有问题的。
SimpleDateFormat 在类注释中也提醒大家不适用于多线程场景:
Date formats are not synchronized.
It is recommended to create separate format instances for each thread.
If multiple threads access a format concurrently, it must be synchronized
externally.
日期格式不同步。
建议为每个线程创建单独的格式实例。
如果多个线程同时访问一种格式,则必须在外部同步该格式。
来看看阿里巴巴 java 开发规范是怎么描述 SimpleDateFormat 的:
三、模拟线程安全问题
无码无真相,接下来我们创建一个线程来模拟 SimpleDateFormat 线程安全问题:
创建 MyThread.java 类:
public class MyThread extends Thread{
private SimpleDateFormat simpleDateFormat;
/* 要转换的日期字符串 */
private String dateString;
public MyThread(SimpleDateFormat simpleDateFormat, String dateString){
this.simpleDateFormat = simpleDateFormat;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date date = simpleDateFormat.parse(dateString);
String newDate = simpleDateFormat.format(date).toString();
if(!newDate.equals(dateString)){
System.out.println("ThreadName=" + this.getName()
+ " 报错了,日期字符串:" + dateString
+ " 转换成的日期为:" + newDate);
}
}catch (ParseException e){
e.printStackTrace();
}
}
}
创建执行类 Test.java 类:
public class Test {
// 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-dd");
public static void main(String[] args) {
String[] dateStringArray = new String[] { "2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};
MyThread[] myThreads = new MyThread[5];
// 创建线程
for (int i = 0; i < 5; i++) {
myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
}
// 启动线程
for (int i = 0; i < 5; i++) {
myThreads[i].start();
}
}
}
执行截图如下:
从控制台打印的结果来看,使用单例的 SimpleDateFormat 类在多线程的环境中处理日期转换,极易出现转换异常(java.lang.NumberFormatException:multiple points)以及转换错误的情况。
四、线程不安全的原因
这个时候就需要看看源码了,format() 格式转换方法:
// 成员变量 Calendar
protected Calendar calendar;
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
我们把重点放在 calendar ,这个 format 方法在执行过程中,会操作成员变量 calendar 来保存时间 calendar.setTime(date) 。
但由于在声明 SimpleDateFormat 的时候,使用的是 static 定义的,那么这个 SimpleDateFormat 就是一个共享变量,SimpleDateFormat 中的 calendar 也就可以被多个线程访问到,所以问题就出现了,举个例子:
假设线程 A 刚执行完 calendar.setTime(date) 语句,把时间设置为 2020-09-01,但线程还没执行完,线程 B 又执行了 calendar.setTime(date) 语句,把时间设置为 2020-09-02,这个时候就出现幻读了,线程 A 继续执行下去的时候,拿到的 calendar.getTime 得到的时间就是线程B改过之后的。
除了 format() 方法以外,SimpleDateFormat 的 parse 方法也有同样的问题。
至此,我们发现了 SimpleDateFormat 的弊端,所以为了解决这个问题就是不要把 SimpleDateFormat 当做一个共享变量来使用。
五、如何解决线程安全
1、每次使用就创建一个新的 SimpleDateFormat
创建全局工具类 DateUtils.java
public class DateUtils {
public static Date parse(String formatPattern, String dateString) throws ParseException {
return new SimpleDateFormat(formatPattern).parse(dateString);
}
public static String format(String formatPattern, Date date){
return new SimpleDateFormat(formatPattern).format(date);
}
}
这种解决处理错误的原理就是创建了多个 SimpleDateFormat 类的实例,在需要用到的地方创建一个新的实例,就没有线程安全问题,不过也加重了创建对象的负担,会频繁地创建和销毁对象,效率较低。
2、synchronized 锁
synchronized 就不展开介绍了
变更一下 DateUtils.java
public class DateUtils {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String formatPattern, String dateString) throws ParseException {
synchronized (simpleDateFormat){
return simpleDateFormat.parse(dateString);
}
}
public static String format(String formatPattern, Date date) {
synchronized (simpleDateFormat){
return simpleDateFormat.format(date);
}
}
}
简单粗暴,synchronized 往上一套也可以解决线程安全问题,缺点自然就是并发量大的时候会对性能有影响,因为使用了 synchronized 加锁后的多线程就相当于串行,线程阻塞,执行效率低。
3、ThreadLocal(最佳MVP)
ThreadLocal 是 java 里一种特殊的变量,ThreadLocal 提供了线程本地的实例,它与普通变量的区别在于,每个使用该线程变量的线程都会初始化一个完全独立的实例副本。
继续改造 DateUtils.java
public class DateUtils {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static Date parse(String formatPattern, String dateString) throws ParseException {
return threadLocal.get().parse(dateString);
}
public static String format(String formatPattern, Date date) {
return threadLocal.get().format(date);
}
}
ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么就不会存在竞争问题。
如果项目中还在使用 SimpleDateFormat 的话,推荐这种写法,但这样就结束了吗?
显然不是…
六、项目中推荐的写法
上边提到的阿里巴巴 java 开发手册给出了说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
日期转换,SimpleDateFormat 固然好用,但是现在我们已经有了更好地选择,Java 8 引入了新的日期时间 API,并引入了线程安全的日期类,一起来看看。
Instant:瞬时实例。
LocalDate:本地日期,不包含具体时间 例如:2014-01-14 可以用来记录生日、纪念日、加盟日等。
LocalTime:本地时间,不包含日期。
LocalDateTime:组合了日期和时间,但不包含时差和时区信息。
ZonedDateTime:最完整的日期时间,包含时区和相对UTC或格林威治的时差。
新API还引入了 ZoneOffSet 和 ZoneId 类,使得解决时区问题更为简便。
解析、格式化时间的 DateTimeFormatter 类也进行了全部重新设计。
例如,我们使用 LocalDate 代替 Date,使用 DateTimeFormatter 代替 SimpleDateFormat,如下所示:
public class DateUtils {
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static LocalDate parse(String dateString){
return LocalDate.parse(dateString, DATE_TIME_FORMATTER);
}
public static String format(LocalDate target) {
return target.format(DATE_TIME_FORMATTER);
}
}
七、最后总结
SimpleDateFormart 继承自 DateFormart,在 DataFormat 类内部有一个 Calendar 对象引用,SimpleDateFormat 转换日期都是靠这个 Calendar 对象来操作的,比如 parse(String),format(date) 等类似的方法,Calendar 在用的时候是直接使用的,而且是改变了 Calendar 的值,这样情况在多线程下就会出现线程安全问题,如果 SimpleDateFormart 是静态的话,那么多个 thread 之间就会共享这个 SimpleDateFormart,同时也会共享这个 Calendar 引用,那么就出现数据赋值覆盖情况,也就是线程安全问题。(现在项目中用到日期转换,都是使用的 java 8 中的 LocalDate,或者 LocalDateTime,本质是这些类是不可变类,不可变一定程度上保证了线程安全)。
1.解决方式:
在多线程下可以使用 ThreadLocal 修饰 SimpleDateFormart,ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么就不会存在竞争问题。
2.推荐方式:
java 8 中引入新的日期类 API,这些类是不可变的,且线程安全的。
介绍1.8新的date
LocalDate使用
localDate时间创建方式
/**
* localDate时间创建方式
* 获取当前时间
*
* @author hq
* Created in 2020/10/16 9:40
*/
@Test
public void localDateCreate() {
LocalDate yyyyMMdd = LocalDate.now();
LocalTime HHmmssSSS = LocalTime.now();
LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.now();
System.out.println("年月日: " + yyyyMMdd);
System.out.println("时分秒毫秒: " + HHmmssSSS);
System.out.println("年月日时分秒毫秒: " + yyyyMMddHHmmssSSS);
//输出:
// 年月日: 2020-10-16
// 时分秒毫秒: 09:55:49.448
// 年月日时分秒毫秒: 2020-10-16T09:55:49.448
}
localDate 设值自定义日期 根据指定日期/时间创建对象
/**
* localDate 设值自定义日期
* 根据指定日期/时间创建对象
*
* @author hq
* Created in 2020/10/16 9:40
*/
@Test
public void setDate() {
LocalDate yyyyMMdd = LocalDate.of(2020, 10, 15);
LocalTime HHmmssSSS = LocalTime.of(10, 10, 10);
LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.of(2020, 10, 15, 10, 10);
System.out.println("自定义的年月日: " + yyyyMMdd);
System.out.println("自定义时分秒毫秒: " + HHmmssSSS);
System.out.println("自定义年月日时分秒毫秒: " + yyyyMMddHHmmssSSS);
//输出:
// 自定义的年月日: 2020-10-15
// 自定义时分秒毫秒: 10:10:10
// 自定义年月日时分秒毫秒: 2020-10-15T10:10
}
日期时间的加减
/**
* 日期时间的加减
*
* @author hq
* Created in 2020/10/16 9:58
*/
@Test
public void lessOrAddDate() {
LocalDate yyyyMMdd = LocalDate.now();
LocalDate addOneDay = yyyyMMdd.plusDays(1L); //添加一日
LocalDate addOneMonths = yyyyMMdd.plusMonths(1L);//添加一月
LocalDate addOneYears = yyyyMMdd.plusYears(1L);//添加一年
LocalDate addOneWeeks = yyyyMMdd.plusWeeks(1L);//添加一周
LocalDate delOneDay = yyyyMMdd.minusDays(1L); //减去一日
LocalDate delOneMonths = yyyyMMdd.minusMonths(1L);//减去一月
LocalDate delOneYears = yyyyMMdd.minusYears(1L);//减去一年
LocalDate delOneWeeks = yyyyMMdd.minusWeeks(1L);//减去一周
System.out.println("LocalDate yyyyMMdd 当前时间: " + yyyyMMdd);
System.out.println("*********LocalDate yyyyMMdd 添加 操作*********");
System.out.println("LocalDate yyyyMMdd 添加一日: " + addOneDay);
System.out.println("LocalDate yyyyMMdd 添加一月: " + addOneMonths);
System.out.println("LocalDate yyyyMMdd 添加一年: " + addOneYears);
System.out.println("LocalDate yyyyMMdd 添加一周: " + addOneWeeks);
System.out.println("*********LocalDate yyyyMMdd 减 操作*********");
System.out.println("LocalDate yyyyMMdd 减去一日: " + delOneDay);
System.out.println("LocalDate yyyyMMdd 减去一月: " + delOneMonths);
System.out.println("LocalDate yyyyMMdd 减去一年: " + delOneYears);
System.out.println("LocalDate yyyyMMdd 减去一周: " + delOneWeeks);
System.out.println("####################################################################");
LocalTime HHmmssSSS = LocalTime.now();
LocalTime addOneHours = HHmmssSSS.plusHours(1L); //添加1小时
LocalTime addOneMinutes = HHmmssSSS.plusMinutes(1L);//添加1分钟
LocalTime addOneSeconds = HHmmssSSS.plusSeconds(1L);//添加1秒
LocalTime addOneNanos = HHmmssSSS.plusNanos(1L);//添加1纳秒
LocalTime delOneHours = HHmmssSSS.minusHours(1L); //减去1小时
LocalTime delOneMinutes = HHmmssSSS.minusMinutes(1L);//减去1分钟
LocalTime delOneSeconds = HHmmssSSS.minusSeconds(1L);//减去1秒
LocalTime delOneNanos = HHmmssSSS.minusNanos(1L);//减去1纳秒
System.out.println("LocalTime HHmmssSSS 当前时间: " + HHmmssSSS);
System.out.println("*********LocalTime HHmmssSSS 添加 操作*********");
System.out.println("LocalTime HHmmssSSS 添加1小时: " + addOneHours);
System.out.println("LocalTime HHmmssSSS 添加1分钟: " + addOneMinutes);
System.out.println("LocalTime HHmmssSSS 添加1秒: " + addOneSeconds);
System.out.println("LocalTime HHmmssSSS 添加1纳秒: " + addOneNanos);
System.out.println("*********LocalTime HHmmssSSS 减 操作*********");
System.out.println("LocalTime HHmmssSSS 减去1小时: " + delOneHours);
System.out.println("LocalTime HHmmssSSS 减去1分钟: " + delOneMinutes);
System.out.println("LocalTime HHmmssSSS 减去1秒: " + delOneSeconds);
System.out.println("LocalTime HHmmssSSS 减去1纳秒: " + delOneNanos);
System.out.println("####################################################################");
LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.now();
LocalDateTime localDateTimeaddOneDay = yyyyMMddHHmmssSSS.plusDays(1L); //添加一日
LocalDateTime localDateTimeaddOneMonths = yyyyMMddHHmmssSSS.plusMonths(1L);//添加一月
LocalDateTime localDateTimeaddOneYears = yyyyMMddHHmmssSSS.plusYears(1L);//添加一年
LocalDateTime localDateTimeaddOneWeeks = yyyyMMddHHmmssSSS.plusWeeks(1L);//添加一周
LocalDateTime localDateTimeaddOneHours = yyyyMMddHHmmssSSS.plusHours(1L); //添加1小时
LocalDateTime localDateTimeaddOneMinutes = yyyyMMddHHmmssSSS.plusMinutes(1L);//添加1分钟
LocalDateTime localDateTimeaddOneSeconds = yyyyMMddHHmmssSSS.plusSeconds(1L);//添加1秒
LocalDateTime localDateTimeaddOneNanos = yyyyMMddHHmmssSSS.plusNanos(1L);//添加1纳秒
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 当前时间: " + yyyyMMddHHmmssSSS);
System.out.println("*********LocalDateTime yyyyMMddHHmmssSSS 添加 操作*********");
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一日: " + localDateTimeaddOneDay);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一月: " + localDateTimeaddOneMonths);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一年: " + localDateTimeaddOneYears);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一周: " + localDateTimeaddOneWeeks);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1小时: " + localDateTimeaddOneHours);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1分钟: " + localDateTimeaddOneMinutes);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1秒: " + localDateTimeaddOneSeconds);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1纳秒: " + localDateTimeaddOneNanos);
LocalDateTime localDateTimedelOneDay = yyyyMMddHHmmssSSS.minusDays(1L); //减去一日
LocalDateTime localDateTimedelOneMonths = yyyyMMddHHmmssSSS.minusMonths(1L);//减去一月
LocalDateTime localDateTimedelOneYears = yyyyMMddHHmmssSSS.minusYears(1L);//减去一年
LocalDateTime localDateTimedelOneWeeks = yyyyMMddHHmmssSSS.minusWeeks(1L);//减去一周
LocalDateTime localDateTimedelOneHours = yyyyMMddHHmmssSSS.minusHours(1L); //减去1小时
LocalDateTime localDateTimedelOneMinutes = yyyyMMddHHmmssSSS.minusMinutes(1L);//减去1分钟
LocalDateTime localDateTimedelOneSeconds = yyyyMMddHHmmssSSS.minusSeconds(1L);//减去1秒
LocalDateTime localDateTimedelOneNanos = yyyyMMddHHmmssSSS.minusNanos(1L);//减去1纳秒
System.out.println("*********LocalDateTime yyyyMMddHHmmssSSS 减 操作*********");
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一日: " + localDateTimedelOneDay);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一月: " + localDateTimedelOneMonths);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一年: " + localDateTimedelOneYears);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一周: " + localDateTimedelOneWeeks);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1小时: " + localDateTimedelOneHours);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1分钟: " + localDateTimedelOneMinutes);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1秒: " + localDateTimedelOneSeconds);
System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1纳秒: " + localDateTimedelOneNanos);
/*
输出:
LocalDate yyyyMMdd 当前时间: 2020-10-16
*********LocalDate yyyyMMdd 添加 操作*********
LocalDate yyyyMMdd 添加一日: 2020-10-17
LocalDate yyyyMMdd 添加一月: 2020-11-16
LocalDate yyyyMMdd 添加一年: 2021-10-16
LocalDate yyyyMMdd 添加一周: 2020-10-23
*********LocalDate yyyyMMdd 减 操作*********
LocalDate yyyyMMdd 减去一日: 2020-10-15
LocalDate yyyyMMdd 减去一月: 2020-09-16
LocalDate yyyyMMdd 减去一年: 2019-10-16
LocalDate yyyyMMdd 减去一周: 2020-10-09
####################################################################
LocalTime HHmmssSSS 当前时间: 10:20:22.164
*********LocalTime HHmmssSSS 添加 操作*********
LocalTime HHmmssSSS 添加1小时: 11:20:22.164
LocalTime HHmmssSSS 添加1分钟: 10:21:22.164
LocalTime HHmmssSSS 添加1秒: 10:20:23.164
LocalTime HHmmssSSS 添加1纳秒: 10:20:22.164000001
*********LocalTime HHmmssSSS 减 操作*********
LocalTime HHmmssSSS 减去1小时: 09:20:22.164
LocalTime HHmmssSSS 减去1分钟: 10:19:22.164
LocalTime HHmmssSSS 减去1秒: 10:20:21.164
LocalTime HHmmssSSS 减去1纳秒: 10:20:22.163999999
####################################################################
LocalDateTime yyyyMMddHHmmssSSS 当前时间: 2020-10-16T10:20:22.165
*********LocalDateTime yyyyMMddHHmmssSSS 添加 操作*********
LocalDateTime yyyyMMddHHmmssSSS 添加一日: 2020-10-17T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 添加一月: 2020-11-16T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 添加一年: 2021-10-16T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 添加一周: 2020-10-23T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 添加1小时: 2020-10-16T11:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 添加1分钟: 2020-10-16T10:21:22.165
LocalDateTime yyyyMMddHHmmssSSS 添加1秒: 2020-10-16T10:20:23.165
LocalDateTime yyyyMMddHHmmssSSS 添加1纳秒: 2020-10-16T10:20:22.165000001
*********LocalDateTime yyyyMMddHHmmssSSS 减 操作*********
LocalDateTime yyyyMMddHHmmssSSS 减去一日: 2020-10-15T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 减去一月: 2020-09-16T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 减去一年: 2019-10-16T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 减去一周: 2020-10-09T10:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 减去1小时: 2020-10-16T09:20:22.165
LocalDateTime yyyyMMddHHmmssSSS 减去1分钟: 2020-10-16T10:19:22.165
LocalDateTime yyyyMMddHHmmssSSS 减去1秒: 2020-10-16T10:20:21.165
LocalDateTime yyyyMMddHHmmssSSS 减去1纳秒: 2020-10-16T10:20:22.164999999
*/
}
将年、月、日等修改为指定的值,并返回新的日期(时间)对象
/**
* 将年、月、日等修改为指定的值,并返回新的日期(时间)对象
* 析: 其效果与时间日期相加减差不多,如今天是2018-01-13,要想变为2018-01-20有两种方式
* a. localDate.plusDays(20L) -> 相加指定的天数
* b. localDate.withDayOfYear(20) -> 直接指定到哪一天
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*
* @author hq
* Created in 2020/10/16 10:34
*/
@Test
public void directDesignationTime() {
LocalDate yyyyMMdd = LocalDate.now();
System.out.println("LocalDate yyyyMMdd 当前时间: " + yyyyMMdd);
LocalDate addDay = yyyyMMdd.plusDays(4);
System.out.println("LocalDate yyyyMMdd 添加4天后的日期: " + addDay);
LocalDate directDesignationDate = yyyyMMdd.withDayOfMonth(20);
System.out.println("LocalDate yyyyMMdd 直接指定到当月第几号: " + directDesignationDate);
LocalDate directDesignationYearDate = yyyyMMdd.withDayOfYear(20);
System.out.println("LocalDate yyyyMMdd 直接指定到当年第几天: " + directDesignationYearDate);
LocalDate directDesignationYear = yyyyMMdd.withYear(2000);
System.out.println("LocalDate yyyyMMdd 当前时间直接指定年份: " + directDesignationYear);
LocalDate directDesignationMonth = yyyyMMdd.withMonth(6);
System.out.println("LocalDate yyyyMMdd 当前时间直接指定月份: " + directDesignationMonth);
}
获取日期的年月日周时分秒
/**
* 获取日期的年月日周时分秒
*
* @author hq
* Created in 2020/10/16 10:48
*/
@Test
public void getDateInfo() {
LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.now();
//本年当中第多少天
int dayOfYear = yyyyMMddHHmmssSSS.getDayOfYear();
//本月当中第多少天
int dayOfMonth = yyyyMMddHHmmssSSS.getDayOfMonth();
DayOfWeek dayOfWeek = yyyyMMddHHmmssSSS.getDayOfWeek();
//本周中星期几
int value = dayOfWeek.getValue();
System.out.println("今天是" + yyyyMMddHHmmssSSS + "\n"
+ "本年当中第" + dayOfYear + "天" + "\n"
+ "本月当中第" + dayOfMonth + "天" + "\n"
+ "本周中星期" + value + "-即" + dayOfWeek + "\n");
//年
int year = yyyyMMddHHmmssSSS.getYear();
//月
Month month = yyyyMMddHHmmssSSS.getMonth();
//直接获取
int monthValue = yyyyMMddHHmmssSSS.getMonthValue();
//日
int dayOfMonth1 = yyyyMMddHHmmssSSS.getDayOfMonth();
//时
int hour = yyyyMMddHHmmssSSS.getHour();
//分
int minute = yyyyMMddHHmmssSSS.getMinute();
//秒
int second = yyyyMMddHHmmssSSS.getSecond();
//纳秒
int nano = yyyyMMddHHmmssSSS.getNano();
System.out.println("今天是" + yyyyMMddHHmmssSSS + "\n"
+ "年 : " + year + "\n"
+ "月 : " + monthValue + "-即 " + month + "\n"
+ "日 : " + dayOfMonth1 + "\n"
+ "时 : " + hour + "\n"
+ "分 : " + minute + "\n"
+ "秒 : " + second + "\n"
+ "纳秒 : " + nano + "\n"
);
}
时间日期前后的比较与判断
/**
* 时间日期前后的比较与判断
*
* @author hq
* Created in 2020/10/16 11:08
*/
@Test
public void isBefore() {
LocalDate now = LocalDate.now();
LocalDate of = LocalDate.of(2020, 10, 15);
//判断of 是否在 now 时间之前
boolean before = of.isBefore(now);
System.out.println("判断of 是否在 now 时间之前 " + before);
//判断of 是否在 now 时间之后
boolean after = of.isAfter(now);
System.out.println("判断of 是否在 now 时间之后 " + after);
//判断两个时间是否相等
boolean equal = of.isEqual(now);
System.out.println("判断两个时间是否相等 " + equal);
//判断是否为闰年
boolean leapYear = now.isLeapYear();
System.out.println("判断是否为闰年 " + leapYear);
/*
判断of 是否在 now 时间之前true
判断of 是否在 now 时间之后false
判断两个时间是否相等false
判断是否为闰年true
*/
}
java8时钟 : clock()
/**
* java8时钟 : clock()
*
* @author hq
* Created in 2020/10/16 11:16
*/
@Test
public void clock() {
//返回当前时间,根据系统时间和UTC
Clock clock = Clock.systemUTC();
System.out.println(clock);
// 运行结果: SystemClock[Z]
}
时间戳
/**
* 时间戳
* 事实上Instant就是java8以前的Date,
* 可以使用以下两个类中的方法在这两个类型之间进行转换,
* 比如Date.from(Instant)就是用来把Instant转换成java.util.date的,
* 而new Date().toInstant()就是将Date转换成Instant的
*
* @author hq
* Created in 2020/10/16 11:17
*/
@Test
public void instant() {
Instant instant = Instant.now();
System.out.println(instant);
Date date = Date.from(instant);
Instant instant2 = date.toInstant();
System.out.println(date);
System.out.println(instant2);
}
计算时间、日期间隔
/**
* 计算时间、日期间隔
* Duration:用于计算两个“时间”间隔
* Period:用于计算两个“日期”间隔
*
* @author hq
* Created in 2020/10/16 14:28
*/
@Test
public void durationOrPeriod() {
LocalDateTime now = LocalDateTime.now();
System.out.println("duration当前时间:" + now);
LocalDateTime of = LocalDateTime.of(2020, 10, 15, 10, 24);
System.out.println("duration自定义时间:" + of);
//Duration:用于计算两个“时间”间隔
Duration duration = Duration.between(now, of);
System.out.println(now + " 与 " + of + " 间隔 " + "\n"
+ " 天 :" + duration.toDays() + "\n"
+ " 时 :" + duration.toHours() + "\n"
+ " 分 :" + duration.toMinutes() + "\n"
+ " 毫秒 :" + duration.toMillis() + "\n"
+ " 纳秒 :" + duration.toNanos() + "\n"
);
LocalDate nowDate = LocalDate.now();
System.out.println("period当前时间:" + now);
LocalDate OfDate = LocalDate.of(2020, 10, 15);
System.out.println("period自定义时间:" + of);
//Period:用于计算两个“日期”间隔
Period period = Period.between(nowDate, OfDate);
System.out.println("Period相差年数 : " + period.getYears());
System.out.println("Period相差月数 : " + period.getMonths());
System.out.println("Period相差日数 : " + period.getDays());
//还可以这样获取相差的年月日
System.out.println("-------------------------------");
long years = period.get(ChronoUnit.YEARS);
long months = period.get(ChronoUnit.MONTHS);
long days = period.get(ChronoUnit.DAYS);
System.out.println("Period相差的年月日分别为 : " + years + "," + months + "," + days);
//注意,当获取两个日期的间隔时,并不是单纯的年月日对应的数字相加减,而是会先算出具体差多少天,在折算成相差几年几月几日的
/*
输出:
duration当前时间:2020-10-16T14:41:40.235
duration自定义时间:2020-10-15T10:24
2020-10-16T14:41:40.235 与 2020-10-15T10:24 间隔
天 :-1
时 :-28
分 :-1697
毫秒 :-101860235
纳秒 :-101860235000000
period当前时间:2020-10-16T14:41:40.235
period自定义时间:2020-10-15T10:24
Period相差年数 : 0
Period相差月数 : 0
Period相差日数 : -1
-------------------------------
Period相差的年月日分别为 : 0,0,-1
*/
}
日期格式化
将时间对象转化为日期字符串
/**
* 将时间对象转化为日期字符串
* 时间日期的格式化(格式化后返回的类型是String) 自定格式 使用 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
* 注:自定义转化的格式一定要与日期类型对应
* LocalDate只能设置仅含年月日的格式
* LocalTime只能设置仅含时分秒的格式
* LocalDateTime可以设置含年月日时分秒的格式
*
* @author hq
* Created in 2020/10/16 14:42
*/
@Test
public void formatter1() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
String format = dtf.format(now);
System.out.println(format);
//输出: 2020-10-16 14:41:01:086
}
将时间字符串形式转化为日期对象
/**
* 将时间字符串形式转化为日期对象
* <p>
* 注:格式的写法必须与字符串的形式一样
* 2018-01-13 21:27:30 对应 yyyy-MM-dd HH:mm:ss
* 20180113213328 对应 yyyyMMddHHmmss
* 否则会报运行时异常!
* <p>
* 但要记住:得到的最终结果都是类似2018-01-13T21:27:30的格式
* 因为在输出LocalDateTime对象时,会调用其重写的toString方法。
*
* @author hq
* email [email protected]
* Created in 2020/10/16 14:44
*/
@Test
public void formatter2() {
String dateStr = "2020-11-12";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate formatterDate = LocalDate.parse(dateStr, dtf);
System.out.println(formatterDate);
//输出 2020-11-12
}
将时间日期对象转为格式化后的时间日期对象
/**
* 将时间日期对象转为格式化后的时间日期对象
*
* @author hq
* Created in 2020/10/16 14:49
*/
@Test
public void formatter3() {
//新的格式化API中,格式化后的结果都默认是String,有时我们也需要返回经过格式化的同类型对象
LocalDateTime ldt1 = LocalDateTime.now();
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String temp = dtf1.format(ldt1);
LocalDateTime formatedDateTime = LocalDateTime.parse(temp, dtf1);
System.out.println(formatedDateTime);
}
转换
localDate 转 date
/**
* localDate 转 date
* localDateTime 转 date
*
* @author hq
* email [email protected]
* Created in 2020/10/16 15:05
*/
@Test
public void localDateToDate() {
LocalDate now = LocalDate.now();
Date from = Date.from(now.atStartOfDay(ZoneOffset.systemDefault()).toInstant());
LocalDateTime localDateTime = LocalDateTime.now();
Date date = Date.from(localDateTime.atZone(ZoneOffset.ofHours(8)).toInstant());
System.out.println(from);
System.out.println(date);
}
date 转 localDate
/**
* date 转 localDate
* date 转 localDateTime
*
* @author hq
* Created in 2020/10/16 15:05
*/
@Test
public void dateToLocalDate() {
Date date = new Date();
LocalDate localDate = date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDate();
System.out.println(localDate);
LocalDateTime localDateTime = date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDateTime();
System.out.println(localDateTime);
}
localDate 转 时间戳
/**
* LocalDate 转 时间戳
* LocalDateTime 转 时间戳
* Created in 2020/10/16 15:28
*/
@Test
public void localDateToInstant() {
LocalDate localDate = LocalDate.now();
long instant = localDate.atStartOfDay(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
System.out.println(instant);
LocalDateTime now = LocalDateTime.now();
long instant1 = now.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
System.out.println(instant1);
}
时间戳 转 localDate
/**
* 时间戳 转 LocalDate
* 时间戳 转 LocalDateTime
* Created in 2020/10/16 15:28
*/
@Test
public void instantToLocalDate() {
long time = new Date().getTime();
LocalDateTime localDateTime = Instant.ofEpochMilli(time).atZone(ZoneOffset.systemDefault()).toLocalDateTime();
System.out.println(localDateTime);
LocalDate localDate = Instant.ofEpochMilli(time).atZone(ZoneOffset.systemDefault()).toLocalDate();
System.out.println(localDate);
}