4

前言

LocalDateLocalTimeLocalDateTimeJDK1.8新增日期处理类,能够以更快更优雅且线程安全的方式实现日期处理。DateTimeFormatter可以对基于LocalDateLocalTimeLocalDateTime获取到的日期的格式进行自定义,且线程安全。本篇文章将对其常见用法进行介绍。

正文

一. LocalDate,LocalTime和LocalDateTime的日期操作

获取当前日期时间

LocalDateLocalTimeLocalDateTime提供了静态方法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);

日期时间之间的转换

LocalDateLocalTimeLocalDateTime之间可以方便的进行转换。

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());

LocalDateLocalTimeLocalDateTime提供了静态方法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)

获取某天在年(月,周)的第几天

LocalDateLocalDateTime可以方便的获取某天在年(月,周)的第几天。

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());

LocalDateLocalDateTime可以获取某月一共有多少天,获取方式如下。

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());

日期时间的加减

LocalDateLocalTimeLocalDateTime可以实现对日期时间的加减,以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());

日期时间的比较

LocalDateLocalTimeLocalDateTime可以实现日期时间之间的比较,以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对象的使用是线程不安全的。以SimpleDateFormatformat()方法为例,看一下其实现。

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;
}

calendarSimpleDateFormat从父类DateFormat继承的Calendar对象,如果将SimpleDateFormat对象声明为静态变量,则会存在多个线程同时使用SimpleDateFormat对象,因此多个线程同时调用SimpleDateFormatformat()方法时,会导致多个线程同时调用CalendarsetTime()方法,线程之间会产生相互影响,线程不安全。下面以一个简单例子演示一下。

编写一个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次调用PrintDateprintDate()方法,结果如下所示。

发现存在传入日期和打印日期不一致的情况。表明了当SimpleDateFormat对象被声明为静态变量时,SimpleDateFormat对象的使用是线程不安全的

那么LocalDateLocalTimeLocalDateTime为什么线程安全呢。下面看一下它们的字段签名。

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;

    ...

}

LocalDateLocalTimeLocalDateTime的表示日期时间的字段签名为private final,表示它们一经指定日期时间,便不能再被改变。其次,它们对日期时间的更改,都会返回一个新的对象。LocalDateLocalTimeLocalDateTime的线程安全的实现和String的线程安全的实现是一样的,String类中存储字符的数组的签名也是private final,以及对字符串的操作会返回一个新的字符串对象。

总结

LocalDateLocalTimeLocalDateTime的使用比DateSimpleDateFormatCalendar的使用更方便和安全。善用LocalDateLocalTimeLocalDateTime,可以使得对日期时间的处理更加优雅。


半夏之沫
65 声望31 粉丝