前言
LocalDate
,LocalTime
和LocalDateTime
是JDK1.8
新增日期处理类,能够以更快更优雅且线程安全
的方式实现日期处理。DateTimeFormatter
可以对基于LocalDate
,LocalTime
和LocalDateTime
获取到的日期的格式进行自定义,且线程安全
。本篇文章将对其常见用法进行介绍。
正文
一. LocalDate,LocalTime和LocalDateTime的日期操作
获取当前日期时间
LocalDate
,LocalTime
和LocalDateTime
提供了静态方法now()
用于获取当前日期时间。
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("date: " + date.toString());
System.out.println("time: " + time.toString());
System.out.println("date time: " + dateTime.toString());
日期时间格式化
使用DateTimeFormatter
可以对获取到的日期时间按照预置的格式进行格式化。
格式化LocalDate
。
DateTimeFormatter dateFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter dateFormatter2 = DateTimeFormatter.ofPattern("yyyyMMdd");
DateTimeFormatter dateFormatter3 = DateTimeFormatter.ofPattern("yyyy.MM.dd");
DateTimeFormatter dateFormatter4 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date = LocalDate.now();
System.out.println("date -> " + date.toString());
String formatDate1 = dateFormatter1.format(date);
String formatDate2 = dateFormatter2.format(date);
String formatDate3 = dateFormatter3.format(date);
String formatDate4 = dateFormatter4.format(date);
System.out.println("yyyy-MM-dd -> " + formatDate1);
System.out.println("yyyyMMdd -> " + formatDate2);
System.out.println("yyyy.MM.dd -> " + formatDate3);
System.out.println("yyyy年MM月dd日 -> " + formatDate4);
格式化LocalTime
。
DateTimeFormatter timeFormatter1 = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter timeFormatter2 = DateTimeFormatter.ofPattern("HHmmss");
DateTimeFormatter timeFormatter3 = DateTimeFormatter.ofPattern("HH mm ss");
DateTimeFormatter timeFormatter4 = DateTimeFormatter.ofPattern("HH时mm分ss秒");
LocalTime time = LocalTime.now();
System.out.println("time -> " + time.toString());
String formatTime1 = timeFormatter1.format(time);
String formatTime2 = timeFormatter2.format(time);
String formatTime3 = timeFormatter3.format(time);
String formatTime4 = timeFormatter4.format(time);
System.out.println("HH:mm:ss -> " + formatTime1);
System.out.println("HHmmss -> " + formatTime2);
System.out.println("HH mm ss -> " + formatTime3);
System.out.println("HH时mm分ss秒 -> " + formatTime4);
格式化LocalDateTime
。
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
DateTimeFormatter dateTimeFormatter3 = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss");
DateTimeFormatter dateTimeFormatter4 = DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒");
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("date time -> " + dateTime);
String formatDateTime1 = dateTimeFormatter1.format(dateTime);
String formatDateTime2 = dateTimeFormatter2.format(dateTime);
String formatDateTime3 = dateTimeFormatter3.format(dateTime);
String formatDateTime4 = dateTimeFormatter4.format(dateTime);
System.out.println("yyyy-MM-dd HH:mm:ss -> " + formatDateTime1);
System.out.println("yyyyMMddHHmmss -> " + formatDateTime2);
System.out.println("yyyy.MM.dd HH:mm:ss -> " + formatDateTime3);
System.out.println("yyyy年MM月dd日HH时mm分ss秒 -> " + formatDateTime4);
使用DateTimeFormatter
也可以读取预置格式的日期时间字符串。
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
TemporalAccessor parsedDateTime = dateTimeFormatter.parse("2021-07-01 18:00:00");
LocalDateTime dateTime = LocalDateTime.from(parsedDateTime);
日期时间之间的转换
LocalDate
,LocalTime
和LocalDateTime
之间可以方便的进行转换。
LocalDateTime dateTime = LocalDateTime.now();
LocalDate date = dateTime.toLocalDate();
LocalTime time = dateTime.toLocalTime();
LocalDateTime gettedDateTime = LocalDateTime.of(date, time);
System.out.println("date time: " + dateTime.toString());
System.out.println("date: " + date.toString());
System.out.println("time: " + time.toString());
System.out.println("getted date time: " + gettedDateTime.toString());
LocalDate
,LocalTime
和LocalDateTime
提供了静态方法of()
来构造自身,以LocalDateTime
为例,方法签名如下。
public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute)
public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second)
public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute)
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)
public static LocalDateTime of(LocalDate date, LocalTime time)
获取某天在年(月,周)的第几天
LocalDate
和LocalDateTime
可以方便的获取某天在年(月,周)的第几天。
LocalDate date = LocalDate.now();
LocalDateTime dateTime = LocalDateTime.now();
int dateDayOfYear = date.getDayOfYear();
int dateTimeDayOfYear = dateTime.getDayOfYear();
System.out.println("date day of year: " + dateDayOfYear);
System.out.println("date time day of year: " + dateTimeDayOfYear);
int dateDayOfMonth = date.getDayOfMonth();
int dateTimeDayOfMonth = dateTime.getDayOfMonth();
System.out.println("date day of month: " + dateDayOfMonth);
System.out.println("date time day of month: " + dateTimeDayOfMonth);
DayOfWeek dateDayOfWeek = date.getDayOfWeek();
DayOfWeek dateTimeDayOfWeek = dateTime.getDayOfWeek();
System.out.println("date day of week: " + dateDayOfWeek.getValue());
System.out.println("date time day of week: " + dateTimeDayOfWeek.getValue());
LocalDate
和LocalDateTime
可以获取某月一共有多少天,获取方式如下。
LocalDate date = LocalDate.now();
LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("days of month: " + lastDayOfMonth.getDayOfMonth());
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime lastDateTimeOfMonth = dateTime.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("days of month: " + lastDateTimeOfMonth.getDayOfMonth());
日期时间的加减
LocalDate
,LocalTime
和LocalDateTime
可以实现对日期时间的加减,以LocalDate
为例进行说明。
LocalDate date = LocalDate.of(2021, 1, 1);
LocalDate fiveDaysAgo = date.minus(5, ChronoUnit.DAYS);
LocalDate fiveDaysLater = date.plus(5, ChronoUnit.DAYS);
System.out.println("today: " + date.toString());
System.out.println("five days ago: " + fiveDaysAgo.toString());
System.out.println("five days later: " + fiveDaysLater.toString());
日期时间的比较
LocalDate
,LocalTime
和LocalDateTime
可以实现日期时间之间的比较,以LocalDate
为例进行说明。
LocalDate date = LocalDate.of(2021, 1, 1);
LocalDate fiveDaysAgo = date.minus(5, ChronoUnit.DAYS);
LocalDate fiveDaysLater = date.plus(5, ChronoUnit.DAYS);
System.out.println("compare result bwtween date and fiveDaysAgo: " + date.compareTo(fiveDaysAgo));
System.out.println("compare result bwtween date and fiveDaysLater: " + date.compareTo(fiveDaysLater));
System.out.println("compare result bwtween date and date: " + date.compareTo(date));
比较的结果为两个日期时间的差值,通过判断比较结果的正负,可以判断日期时间的先后。
二. 为什么线程安全
首先说明一下为什么SimpleDateFormat
线程不安全。已知SimpleDateFormat
是用于格式化和分析Date
的类,当SimpleDateFormat
对象被声明为静态变量时,SimpleDateFormat
对象的使用是线程不安全的。以SimpleDateFormat
的format()
方法为例,看一下其实现。
format()
方法最终会调用到如下实现。
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// 调用了线程不安全的Calendar对象
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
从父类DateFormat
继承的Calendar
对象,如果将SimpleDateFormat
对象声明为静态变量,则会存在多个线程同时使用SimpleDateFormat
对象,因此多个线程同时调用SimpleDateFormat
的format()
方法时,会导致多个线程同时调用Calendar
的setTime()
方法,线程之间会产生相互影响,线程不安全。下面以一个简单例子演示一下。
编写一个PrintDate
类并提供printDate()
方法用于打印日期,该类有一个SimpleDateFormat
类变量。
public class PrintDate {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
public void printDate(Date date) {
System.out.println(Thread.currentThread().getName() + " -> " + sdf.format(date));
}
}
测试方法。
class PrintDateTest {
private PrintDate printDate;
@BeforeEach
public void setUp() {
printDate = new PrintDate();
}
@Test
void givenMultiThreads_whenPrintDateBySimpleDateFormat_thenThreadUnsafe() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(15));
threadPool.execute(() -> {
Date date = new Date(121, Calendar.JANUARY, 1);
Thread.currentThread().setName(date.toString());
for (int i = 0; i < 50; i++) {
printDate.printDate(date);
}
});
threadPool.execute(() -> {
Date date = new Date(121, Calendar.JANUARY, 2);
Thread.currentThread().setName(date.toString());
for (int i = 0; i < 50; i++) {
printDate.printDate(date);
}
});
}
}
启用两个线程循环50次调用PrintDate
的printDate()
方法,结果如下所示。
发现存在传入日期和打印日期不一致的情况。表明了当SimpleDateFormat
对象被声明为静态变量时,SimpleDateFormat
对象的使用是线程不安全的
那么LocalDate
,LocalTime
和LocalDateTime
为什么线程安全呢。下面看一下它们的字段签名。
LocalDate
字段签名。
public final class LocalDate
implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {
...
private final int year;
private final short month;
private final short day;
...
}
LocalTime
字段签名。
public final class LocalTime
implements Temporal, TemporalAdjuster, Comparable<LocalTime>, Serializable {
...
private final byte hour;
private final byte minute;
private final byte second;
private final int nano;
...
}
LocalDateTime
字段签名。
public final class LocalDateTime
implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable {
...
private final LocalDate date;
private final LocalTime time;
...
}
LocalDate
,LocalTime
和LocalDateTime
的表示日期时间的字段签名为private final
,表示它们一经指定日期时间,便不能再被改变。其次,它们对日期时间的更改,都会返回一个新的对象。LocalDate
,LocalTime
和LocalDateTime
的线程安全的实现和String
的线程安全的实现是一样的,String
类中存储字符的数组的签名也是private final
,以及对字符串的操作会返回一个新的字符串对象。
总结
LocalDate
,LocalTime
和LocalDateTime
的使用比Date
,SimpleDateFormat
和Calendar
的使用更方便和安全。善用LocalDate
,LocalTime
和LocalDateTime
,可以使得对日期时间的处理更加优雅。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。