又又又一次拖更,原因就是出去玩耍了,游客照附上,大家也要注意劳逸结合,马上清明节了出去玩耍的朋友们还是要注意好防护。
别问我为什么歪头,我也不知道。
好了,开始扯正文。相信大家在开发过程中都有过要处理日期/时间的情况,比如createTime、updateTime啥的应该都有过吧。在处理这些字段的时候用new Date()、System.currentTimeMillis()还有SimpleDateFormat比较多吧。但是当用阿里巴巴规范插件检测的时候就会有warning存在(特指:new Date().getTime()),会提示使用System.currentTimeMillis()方法来获取时间。用惯了某一种方式遇到问题就会使用那种方式来处理,但是当有新的方式出现时,我们应该去尝试去对比,而不是直接摒弃,java8(JDK8)补充了对日期时间的处理类LocalDate、LocalTime、LocalDateTime以及Clock等,那么就让我们一起看看日期时间处理类相对于之前的有什么特别之处。
首先来谈一下为什么需要LocalDate、LocalTime、LocalDateTime而不继续沿用之前的Date等
Date如果不进行格式化,打印出的日期可读性差
public static void main(String[] args) {
System.out.println(new Date());
}
// 输出结果: Tue Mar 23 13:49:38 CST 2021
看上边的输出结果应该很清楚了吧,所以通常情况下我们会将new Date得到的时间使用SimpleDateFormat进行格式化,但是SimpleDateFormat是线程不安全的,知道你们不知道,所以我们继续看代码Code is Everything
// SimpleDateFormat格式化代码
public static void main(String[] args) {
System.out.println(new Date());
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
}
可以看到格式化主要是使用SimpleDateFormat的format方法,所以我们只要进入format方法就会很清楚了,Click
/**
* Formats a Date into a date/time string.
* 格式化Date格式日期->日期/时间字符串
* @param date the time value to be formatted into a time string.
* @return the formatted time string.
*/
public final String format(Date date)
{
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
不要到这里就停止,因为在这里我们还不能准确判断,所以继续深入。
/**
* Formats a Date into a date/time string.
* @param date a Date to be formatted into a date/time string.
* @param toAppendTo the string buffer for the returning date/time string.
* @param fieldPosition keeps track of the position of the field
* within the returned string.
* On input: an alignment field,
* if desired. On output: the offsets of the alignment field. For
* example, given a time text "1996.07.10 AD at 15:08:56 PDT",
* if the given fieldPosition is DateFormat.YEAR_FIELD, the
* begin index and end index of fieldPosition will be set to
* 0 and 4, respectively.
* Notice that if the same time field appears
* more than once in a pattern, the fieldPosition will be set for the first
* occurrence of that time field. For instance, formatting a Date to
* the time string "1 PM PDT (Pacific Daylight Time)" using the pattern
* "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD,
* the begin index and end index of fieldPosition will be set to
* 5 and 8, respectively, for the first occurrence of the timezone
* pattern character 'z'.
* @return the string buffer passed in as toAppendTo, with formatted text appended.
*/
public abstract StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition);
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是共享变量,并且这个共享变量没有做线程安全控制。当多个线程同时使用相同的SimpleDateFormat对象【如用static修饰的SimpleDateFormat】调用format方法时,多个线程会同时调用calendar.setTime方法,可能一个线程刚设置好time值另外的一个线程马上把time值给修改了导致返回的格式化时间是错误的。在多并发情况下使用SimpleDateFormat需格外注意SimpleDateFormat除了format是线程不安全的以外,parse方法也是线程不安全的。parse方法实际调用了alb.establish(calendar).getTime方法来解析,alb.establish(calendar).getTime方法主要完成了
1、重置日期对象cal的属性值
2、使用calb中属性设置cal
3、返回设置好的cal对象
划重点: 上述三步操作不是原子操作。
除了上边讲述的以外,如果你想获取某年、某月、某星期,或者n天之后的时间,用Date来处理实在过于难,因为Date类的getYear、getMonth这些方法都被弃用了。
划重点,划重点⬇⬇⬇
接下来就来看看java8新引入的LocalXXX等这些日期时间处理类有什么特别之处。
·LocalDate:只会获取年月日
// 获取当前年月日
LocalDate now = LocalDate.now();
// 构造指定年月日
LocalDate target = LocalDate.of(2021,3,24);
// 获取年、月、日、星期几
int year = now.getYear();
int year = now.get(ChronoField.YEAR);
int month = now.getMonth();
int month = now.get(ChronoField.MONTH_OF_YEAR);
int day = now.getDayOfMonth();
int day = now.get(ChronoField.DAY_OF_MONTH);
DayOfWeek dayOfWeek = now.getDayOfWeek();
int dayOfWeek = now.get(ChronoField.DAY_OF_WEEK);
·LocalTime:只会获取时分秒
// 获取当前时间或者指定时间
LocalTime now = LocalTime.now();
LocalTime target = LocalTime.of(11,58,11);
// 获取时分秒
int hour = now.getHour();
int hour = now.get(ChronoField.HOUR_OF_DAY);
int min = now.getMinute();
int min = now.get(ChronoField.MINUTE_OF_HOUR);
int sec = now.getSecond();
int sec = now.get(ChronoField.SECOND_OF_MINUTE);
·LocalDateTime:获取年月日时分秒,等于LocalDate+LocalTime
// 获取当前日期时间或者指定日期时间
LocalDateTime now = LocalDateTime.now();
LocalDateTime target = LocalDateTime.of(2021,Month.MARCH,24,12,02,11);
LocalDateTime target = LocalDateTime.of(nowDate,nowTime);
LocalDateTime target = LocalDate.atTime(nowTime);
LocalDateTime target = LocalTime.atDate(nowDate);
// 根据LocalDateTime获取LocalDate
LocalDate localDate = now.toLocalDate();
// 根据LocalDateTime获取LocalTime
LocalTime localTime = now.toLocalTime();
·Instant:获取秒数
// 获取当前Instant对象
Instant now = Instant.now();
// 获取秒数
long nowSec = instant.getEpochSecond();
// 获取毫秒数
long nowMilli = instant.toEpochMilli();
// tips: 获取秒数还可以使用System.currentTimeMillis()或者使用Clock时钟类来实现,如下方。
·Clock时钟类
https://www.matools.com/api/java8
里边讲的很清楚,就不再赘述,记住是在java.time包下
接下来讲一下常见操作
如果你有心进入这些类的源码中看应该不难发现,这些类都是不可变对象,修改这些对象会返回一个副本。
// 增加、减少年数、月数、天数等(LocalDateTime为例)
LocalDateTime localDateTime = LocalDateTime.of(2021,Month.MARCH,24,12,22,22);
// 增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1,ChronoUnit.YEARS);
// 减少一个月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1,ChronoUnit.MONTHS);
// 通过with修改某些值
// 修改年
localDateTime = localDateTime.withYear(2020);
// TODO: 同理还可以修改月、日等
除了上述操作以外,还可以进行日期计算,比如想要知道这个月的最后一天是几号,下个周末是几号,通过事件和日期API可以很快得到结果
// 得到这一年的第一天的日期
LocalDate localDate = LocalDate.now();
LocalDate firstDayOfYear = localDate.with(firstDayOfYear());
上述的操作我们只是得到了LocalDate、LocalDateTime、LocalTime类型的变量,但是我们通常需要得到的是日期的字符串,在得到日期字符串的时候就避免不了进行日期格式化,日期格式化就少不了下面的DateFormatter、DateTimeFormatter等类
// for example
LocalDate now = LocalDate.now();
String s1 = now.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = now.format(DateTimeFormatter.ISO_LOCAL_DATE);
// 自定义格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy);
String s3 = now.format(formatter);
DateTimeFormatter默认提供了多种样式的格式化方式,如果默认不能满足,可以通过DateTimeFormatter的ofPattern方法创建自定义格式化方式
当我们得到时间字符串要进行运算时,字符串格式就会对操作产生很大影响,所以我们需要对字符串格式进行解析,解析成LocalDate或者LocalTime等格式,格式化的操作我们可以借助这些类的parse方法,如下:
LocalDate localDate1 = LocalDate.parse("20210324", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate localDate2 = LocalDate.parse("2021-03-24", DateTimeFormatter.ISO_LOCAL_DATE);
相比较于SimpleDateFormat,DateTimeFormatter是线程安全的
另外的话,有时候还需要对时间进行时区约束等,我们都可以在java.time包下找到解决方案,这里就不再赘述了,有一个博主还总结了一些使用时间和日期类的小栗子,链接放下方,如果正好匹配你们的需求可以借鉴。
小栗子
结语:知道的越多,不知道的越多。