java old bird teaches you how to format time efficiently and gracefully

preface

In the process of daily project development, I believe everyone must often encounter the scene of time formatting. Many people may feel very simple, but is your time formatting method really elegant and efficient?

1, Common time formatting methods

    public static void main(String[] args) {
        Date now = new Date(); // Create a Date object to get the current time
        String strDateFormat = "yyyy-MM-dd HH:mm:ss";

        //New rookie implementation
        SimpleDateFormat f = new SimpleDateFormat(strDateFormat);
        System.out.println("SimpleDateFormat:" + f.format(now)); // Formalize the current time into the specified format

        //Advanced implementation of Java 8
        LocalDateTime localDateTime = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        String result =  localDateTime.format(DateTimeFormatter.ofPattern(strDateFormat));
        System.out.println("DateTimeFormatter:"+result);

        //Common lang3 old bird implementation
        result  = DateFormatUtils.format(now,strDateFormat);
        System.out.println("DateFormatUtils:"+result);

    }

2, Analysis

Method 1: newcomers and rookies

Many newcomers like to use SimpleDateFormat for time formatting, which is also the time formatting implementation method provided by jdk by default before java8. Its biggest problem is non thread safety.

reason:
In a multithreaded environment, when multiple threads use the same SimpleDateFormat object (such as static modification) at the same time, for example, when calling the format method, multiple threads will call calender at the same time The setTime method causes the time to be modified by another thread, so the thread is unsafe.

/**
 * SimpleDateFormat Thread safety test
 */
public class SimpleDateFormatTest {
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));


    public void test() {
        while (true) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    String dateString = simpleDateFormat.format(new Date());
                    try {
                        Date parseDate = simpleDateFormat.parse(dateString);
                        String dateString2 = simpleDateFormat.format(parseDate);
                        System.out.println(dateString.equals(dateString2));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

Output:

true
false
true
true
false

false appears, indicating that the thread is unsafe

format method source code analysis:

protected Calendar calendar;

 // Called from Format after creating a FieldDelegate
    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;
    }

You can see that multiple threads share the variable calendar and modify the calendar. Therefore, in a multithreaded environment, when multiple threads use the same SimpleDateFormat object (such as static modification) at the same time, such as calling the format method, multiple threads will call calender at the same time The setTime method causes the time to be modified by another thread, so the thread is unsafe.

In addition, the parse method is also thread unsafe. The parse method actually calls the establish of CalenderBuilder for parsing. The main step in its method is not atomic operation.

Solution:
1. Define SimpleDateFormat as a local variable
2. Add a thread synchronization lock: synchronized(lock)
3. Using ThreadLocal, each thread has its own copy of SimpleDateFormat object.

Mode 2: advanced implementation of java8

After Java 8, it is recommended to use DateTimeFormatter instead of SimpleDateFormat.
DateTimeFormatter is thread safe and provides many formatting methods by default. You can also create custom formatting methods through ofPattern method.

        //Advanced implementation of Java 8
        LocalDateTime localDateTime = LocalDateTime.now();
        String result =  localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("DateTimeFormatter:"+result);

It should be noted that the time formatting of java8 is for LocalDate and LocalDateTime objects. LocalDateTime contains hours, minutes and seconds, while LocalDate only contains year, month and day without hours, minutes and seconds.

Java8 datetime API, adding thread safety classes such as LocalDate, LocalDateTime and LocalTime:

LocalDate: only date, such as July 13, 2019
Local time: only time, such as 08:30
LocalDateTime: date + time, such as: 2019-07-13 08:30

Since the formatting API s in Java 8 are for LocalDate and LocalDateTime objects, the mutual conversion of date objects and LocalDate and LocalDateTime objects is very important.

1. Convert Date to LocalDate

public static LocalDate date2LocalDate(Date date) {
    if(null == date) {
        return null;
    }
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}

2. Convert Date to LocalDateTime

public static LocalDateTime date2LocalDateTime(Date date) {
    if(null == date) {
        return null;
    }
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}

3. Convert LocalDate to Date

public static Date localDate2Date(LocalDate localDate) {
    if(null == localDate) {
        return null;
    }
    ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
    return Date.from(zonedDateTime.toInstant());
}

4. Convert LocalDateTime to Date

   public static Date localDateTime2Date(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

Method 3: common-lang3 old bird implementation

I believe you have used some API s in common-lang3 in the process of project development, which are very simple and practical. In terms of time formatting, it also provides us with a very useful tool class DateFormatUtils.
This is also my most recommended way.

The dependency of common-lang3 needs to be introduced into the project.

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>

DateFormatUtils usage example:

Date now = new Date(); // Create a Date object to get the current time
String strDateFormat = "yyyy-MM-dd HH:mm:ss";
String result  = DateFormatUtils.format(now,strDateFormat);
System.out.println("DateFormatUtils:"+result);

Benefits:
1. Thread safety
2. Simple and efficient
3. Use less memory

DateFormatUtils. In the internal implementation of format, the time is formatted through FastDateFormat, and the FastDateFormat object is cached to ensure that the FastDateFormat object is not generated repeatedly under the format type of the same mode.

FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale);

Recommended usage:

/**
 * @Description   Time formatting tool class
 * @Date 2021/5/26 11:40 morning
 * @Version 1.0
 * @Copyright 2019-2021
 */
public class DateUtils extends DateFormatUtils {
      //Inherit DateFormatUtils

      //Custom time-dependent methods
}

summary

1. This paper introduces three common methods of java time formatting.
2. The main problem of SimpleDateFormat time format is non thread safety. Problems will occur in multi-threaded situations. This paper explains the reasons for non thread safety of SimpleDateFormat by tracking the source code, and provides corresponding solutions.
3. This paper introduces the recommended use of DateTimeFormatter for time formatting under Java 8, and provides the conversion mode from date to LocalDateTime and LocalDate.
4. Using DateFormatUtils in common-lang3 to realize time formatting is my most recommended. It is thread safe, simple and efficient API and occupies the bottom of memory. It also recommends that you encapsulate your own time tool class DateUtils by inheriting DateFormatUtils object.

Keywords: Java java8

Added by lesmckeown on Tue, 08 Feb 2022 07:34:12 +0200