Time is often used in daily development, and there are many ways to capture time in Java code. However, different methods can obtain the time in different formats, so you need a formatting tool to display the time in the format you want.

The most common method is to use the SimpleDateFormat class. This is a seemingly simple class, but it can cause a lot of problems if used incorrectly.

In the Java Development manual of Alibaba, there are the following clear provisions:

So, this article around SimpleDateFormat usage, principle and so on in-depth analysis of how to use it in the right posture.

SimpleDateFormat usage

SimpleDateFormat is a Java utility class that formats and parses dates. It allows formatting (date -> text), parsing (text -> date), and normalization. SimpleDateFormat makes it possible to select any user-defined date-time format schema.

In Java, you can convert a Date type to a String using the format method of SimpleDateFormat, and you can specify the output format.

// Date to String Date data = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dataStr = sdf.format(data); System.out.println(dataStr);Copy the code

The result of the conversion is: 2018-11-25 13:00:00, the date and time format is specified by the “date and time mode” string. If you want to convert to another format, just specify a different time mode.

In Java, you can convert a String type to Date using SimpleDateFormat’s parse method.

Println (sdf.parse(dataStr)); // String to Data system.out.println (sdf.parse(dataStr));Copy the code

Date and time pattern expression method

When using SimpleDateFormat, you use letters to describe the time elements and assemble them into the desired date and time patterns. The corresponding table of commonly used time elements and letters is as follows:

Pattern letters are usually repeated and their number determines their exact representation. The following table is a common representation of the output format.

Output the time of different time zones

Time zones are regions of the earth that use the same definition of time. In the past, people determined time by looking at the position of the sun (hour Angle), which made it different in places with different longitudes (local time). The concept of time zones was first used in 1863. Time zones partly solve this problem by setting a standard time for a region.

Different countries in the world are located in different parts of the earth, so the time of sunrise and sunset is bound to vary from country to country, especially those with a large east-west span. These deviations are known as jet lag.

There are 24 time zones in the world today. For administrative convenience, a country or province is often grouped together because it is practically common for one country or province to straddle two or more time zones simultaneously. So time zones are not divided strictly by north-south lines, but by natural conditions. China, for example, is so vast that it spans almost five time zones, but in order to make it easy to use, it actually only uses the standard time of e8, which is Beijing Time.

Because time varies from time zone to time zone, and even from city to city in the same country, it is important to pay attention to time zones when trying to capture time in Java.

By default, if not specified, the current computer’s time zone is used as the default time zone when creating a Date, which is why we can get the current time in China by simply using new Date().

So how do you get different time zones in Java code? SimpleDateFormat can do this.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(Calendar.getInstance().getTime()));
Copy the code

The result of the conversion is: 2018-11-24 21:00:00. Since China’s time is 13:00 on November 25th, Los Angeles time is 16 hours behind Beijing time (this is also related to summer and winter time, I won’t elaborate on it).

If you’re interested, you can also try printing New York Time (America/New_York). New York time is November 25, 2018. New York is 13 hours ahead of Beijing, China.

Of course, this isn’t the only way to display other time zones, but this article will cover only SimpleDateFormat.

SimpleDateFormat Thread safety

Because SimpleDateFormat is commonly used and the time display mode is generally the same in an application, many people prefer to define SimpleDateFormat as follows:

public class Main { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) { simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York")); System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime())); }}Copy the code

This way of definition, there are big security risks.

Problem reproduction

Let’s look at a piece of code that uses a thread pool to perform time output.

/** * @author Hollis */ public class Main {/** * define a global SimpleDateFormat */ private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); private static ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); */ private static CountDownLatch CountDownLatch = new CountDownLatch(100); Public static void main (String [] args) {/ / define a thread-safe HashSet Set < String > dates. = the Collections synchronizedSet (new HashSet<String>()); for (int i = 0; i < 100; Calendar = calendar.getInstance (); int finalI = i; Pool.execute (() -> {// add calendar. Add (calendar. DATE, finalI); DateString = simpleDateformat.format (calendar.getTime()); // Put the string in Set dates.add(dateString); //countDown countDownLatch.countDown(); }); } // block until countDown counts to 0 countdownlatch.await (); System.out.println(dates.size()); }}Copy the code

The above code is actually quite simple and easy to understand. It loops a hundred times, adding a day to the current time each time (the number of days varies with the number of loops), putting all the dates into a thread-safe Set with deduplication, and printing the number of elements in the Set.

The examples above are intentionally a little more complicated, but I’ve annotated almost all of them. This covers thread pool creation, countdownlatches, lambda expressions, thread-safe Hashsets, and more. Interested friends can know one by one.

Normally, the output should be 100. But the actual result is a number less than 100.

The reason is that because SimpleDateFormat is a non-thread-safe class, it is used as a shared variable in multiple threads, which creates thread-safe issues.

This is also clearly stated in the Alibaba Java Development Manual, Chapter 1, Section 6 — Concurrent Processing:

So, let’s take a look at why and how to fix it.

The thread is unsafe

From the above code, we found that using SimpleDateFormat in a concurrent scenario has thread-safety issues. In fact, the JDK documentation explicitly states that SimpleDateFormat should not be used in multithreaded scenarios:

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.

SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat

For a clue, look at the format method implementation in the SimpleDateFormat class.

The format method in SimpleDateFormat uses a member variable, Calendar, to hold the time during execution. That’s actually the point.

Because we declared SimpleDateFormat static. This SimpleDateFormat is a shared variable, and the Calendar in SimpleDateFormat can be accessed by multiple threads.

Let’s assume that thread 1 has just finished executing calendar.setTime and set the time to 2018-11-11, and thread 2 has executed calendar.setTime and changed the time to 2018-12-12 before it finished executing. Thread 1 continues to execute, and the calendar.getTime gets the same time that thread 2 changed.

In addition to the format method, SimpleDateFormat’s parse method has the same problem.

So don’t use SimpleDateFormat as a shared variable.

How to solve

Now that YOU’ve seen the problems with SimpleDateFormat and why, what can you do to fix them?

There are many solutions. Here are three common ones.

Using local variables

for (int i = 0; i < 100; Calendar = calendar.getInstance (); int finalI = i; Pool.execute (() -> {// SimpleDateFormat specifies a local variable. SimpleDateFormat SimpleDateFormat = new SimpleDateFormat(" YYYY-MM-DD HH:mm:ss"); Calendar. add(calendar. DATE, finalI); DateString = simpleDateformat.format (calendar.getTime()); // Put the string in Set dates.add(dateString); //countDown countDownLatch.countDown(); }); }Copy the code

SimpleDateFormat becomes a local variable so it can’t be accessed by multiple threads at the same time, thus avoiding thread-safety issues.

Add synchronization lock

In addition to changing local variables, there is another method that you may be more familiar with, which is to lock shared variables.

for (int i = 0; i < 100; Calendar = calendar.getInstance (); int finalI = i; Pool.execute (() -> {// synchronized (simpleDateFormat) {// add(calendar.date, finalI); DateString = simpleDateformat.format (calendar.getTime()); // Put the string in Set dates.add(dateString); //countDown countDownLatch.countDown(); }}); }Copy the code

By locking, multiple threads are queued for sequential execution. Thread safety issues caused by concurrency are avoided.

In fact, the above code can be improved, that is, you can set the granularity of the lock a little bit smaller, you can only lock the simpleDateformat. format line, so that more efficient.

Using ThreadLocal

The third way is to use ThreadLocal. ThreadLocal ensures that each thread gets a single SimpleDateFormat object, so there are no race issues.

/ * * * using ThreadLocal define a global SimpleDateFormat * / private static ThreadLocal < SimpleDateFormat > simpleDateFormatThreadLocal =  new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }}; / / usage String dateString = simpleDateFormatThreadLocal. The get (). The format (calendar. GetTime ());Copy the code

Using ThreadLocal is a bit like caching in that each thread has its own object, avoiding frequent object creation and multi-threaded competition.

Of course, there is room for improvement in the above code, namely, the creation process of SimpleDateFormat can be changed to lazy loading. I won’t go into the details here.

Using DateTimeFormatter

For Java8 applications, you can replace SimpleDateFormat with DateTimeFormatter, which is a thread-safe formatting utility class. As stated in the official documentation, this class is simple Beautiful Strong IMmutable Thread-safe.

// Parse date String dateStr= "October 25, 2016 "; DateTimeFormatter formatter = DateTimeFormatter. OfPattern (" on dd MM yyyy years "); LocalDate date= LocalDate.parse(dateStr, formatter); LocalDateTime now = localDatetime.now (); DateTimeFormatter format = DateTimeFormatter. OfPattern (" on dd MM yyyy years hh: MM a "); String nowStr = now .format(format); System.out.println(nowStr);Copy the code

conclusion

SimpleDateFormat can convert between String and Date. It can also convert time to different time zones. It is also mentioned that SimpleDateFormat is not thread-safe in concurrent scenarios and requires the developer to ensure its safety.

The main tools are changing to local variables, using synchronized locking, and creating a separate Threadlocal for each thread.

Hopefully this article has made you a little more comfortable with SimpleDateFormat.