A Guide to SimpleDateFormat – SimpleDateFormat指南

最后修改: 2018年 10月 22日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll be taking an in-depth tour of the SimpleDateFormat class.

在本教程中,我们将对SimpleDateFormat类进行深入了解。

We’ll take a look at simple instantiation and formatting styles as well as useful methods the class exposes for handling locales and time zones.

我们将看看简单的实例化和格式化风格,以及该类为处理地区和时区所暴露的有用方法。

2. Simple Instantiation

2.简单的实例化

First, let’s look at how to instantiate a new SimpleDateFormat object.

首先,让我们看看如何实例化一个新的SimpleDateFormat对象。

There are 4 possible constructors – but in keeping with the name, let’s keep things simple. All we need to get started is a String representation of a date pattern we want.

4个可能的构造函数–但为了与名称保持一致,让我们保持简单的东西。我们所需要的是一个字符串代表我们想要的日期模式

Let’s start with a dash-separated date pattern like so:

让我们从一个破折号分隔的日期模式开始,像这样。

"dd-MM-yyyy"

This will correctly format a date starting with the current day of the month, current month of the year, and finally the current year. We can test our new formatter with a simple unit test. We’ll instantiate a new SimpleDateFormat object, and pass in a known date:

这将正确地格式化一个日期,从当前的月日开始,到当前的年月,最后是当前的年。我们可以通过一个简单的单元测试来测试我们的新格式化器。我们将实例化一个新的SimpleDateFormat对象,并传入一个已知日期。

SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
assertEquals("24-05-1977", formatter.format(new Date(233345223232L)));

In the above code, the formatter converts milliseconds as long into a human-readable date – the 24th of May, 1977.

在上面的代码中,格式化将毫秒作为long转换为人类可读的日期 – 1977年5月24日。

2.1. Factory Methods

2.1.工厂方法

Although SimpleDateFormat is a handy class to quickly build a date formatter, we’re encouraged to use the factory methods on the DateFormat class getDateFormat(), getDateTimeFormat(), getTimeFormat().

尽管SimpleDateFormat是一个方便的类,可以快速构建一个日期格式化器,我们被鼓励使用DateFormat类的工厂方法 getDateFormat()getDateTimeFormat()getTimeFormat()

The above example looks a little different when using these factory methods:

使用这些工厂方法时,上面的例子看起来有点不同。

DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT);
assertEquals("5/24/77", formatter.format(new Date(233345223232L)));

As we can tell from above, the number of formatting options is pre-determined by the fields on the DateFormat class. This largely restricts our available options for formatting which is why we’ll be sticking to SimpleDateFormat in this article.

从上面我们可以看出,格式化选项的数量是由DateFormat 类上的字段预先确定的。这在很大程度上限制了我们可用的格式化选项,这就是为什么我们将在本文中坚持使用SimpleDateFormat

2.2. Thread-Safety

2.2.线程安全

The JavaDoc for SimpleDateFormat explicitly states:

SimpleDateFormat JavaDoc明确指出。

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.

日期格式是不同步的。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,它必须在外部进行同步。

So SimpleDateFormat instances are not thread-safe, and we should use them carefully in concurrent environments.

因此SimpleDateFormat 实例不是线程安全的,我们应该在并发环境中谨慎使用它们。

The best approach to resolve this issue is to use them in combination with a ThreadLocal. This way, each thread ends up with its own SimpleDateFormat instance, and the lack of sharing makes the program thread-safe: 

解决这个问题的最佳方法是将它们与ThreadLocal结合使用。这样一来,每个线程最终都有自己的SimpleDateFormat 实例,而缺乏共享使得程序是线程安全的:

private final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal
  .withInitial(() -> new SimpleDateFormat("dd-MM-yyyy"));

The argument for the withInitial method is a supplier of SimpleDateFormat instances. Every time the ThreadLocal needs to create an instance, it will use this supplier.

withInitial方法的参数是一个SimpleDateFormat实例的供应商。每次ThreadLocal需要创建一个实例时,它将使用这个供应商。

Then we can use the formatter via the ThreadLocal instance:

然后我们可以通过ThreadLocal实例使用格式化器。

formatter.get().format(date)

The ThreadLocal.get() method initializes the SimpleDateFormat for the current thread at first and then reuses that instance.

ThreadLocal.get() 方法首先为当前线程初始化SimpleDateFormat ,然后重复使用该实例。

We call this technique thread confinement as we confine the use of each instance to one specific thread.

我们称这种技术为线程限制,因为我们将每个实例的使用限制在一个特定的线程中。

There are two other approaches to tackle the same problem:

还有另外两种方法来解决同样的问题。

  • Using synchronized blocks or ReentrantLocks
  • Creating throw away instances of SimpleDateFormat on-demand

Both of these approaches are not recommended: The former incurs a significant performance hit when the contention is high, and the latter creates a lot of objects, putting pressure on garbage collection.

这两种方法都不值得推荐。前者在竞争激烈的情况下会产生很大的性能冲击,而后者则会创建大量的对象,给垃圾回收带来压力。

It’s worthwhile to mention that, since Java 8, a new DateTimeFormatter class has been introduced. The new DateTimeFormatter class is immutable and thread-safe. If we’re working with Java 8 or later, using the new DateTimeFormatter class is recommended.

值得一提的是,从 Java 8 开始,一个新的 DateTimeFormatter 类已经被引入。新的DateTimeFormatter 类是不可变和线程安全的。如果我们正在使用Java 8或更高版本,建议使用新的DateTimeFormatter 类。

3. Parsing Dates

3.解析日期

SimpleDateFormat and DateFormat not only allow us to format dates – but we can also reverse the operation. Using the parse method, we can input the String representation of a date and return the Date object equivalent:

SimpleDateFormatDateFormat不仅允许我们对日期进行格式化 – 而且我们还可以进行反向操作。使用parse方法,我们可以输入一个日期的String代表,并返回Date对象的等价物。

SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
Date myDate = new Date(233276400000L);
Date parsedDate = formatter.parse("24-05-1977");
assertEquals(myDate.getTime(), parsedDate.getTime());

It’s important to note here that the pattern supplied in the constructor should be in the same format as the date parsed using the parse method.

这里需要注意的是,构造函数中提供的模式应该与使用parse方法解析的日期格式相同

4. Date-Time Patterns

4.日期-时间模式

SimpleDateFormat supplies a vast array of different options when formatting dates. While the full list is available in the JavaDocs, let’s explore some of the more commonly used options:

SimpleDateFormat在格式化日期时提供了大量不同的选项。虽然完整的列表可以在JavaDocs中找到,但让我们来探索一些更常用的选项。

Letter Date Component Example
M Month 12; Dec
y year 94
d day 23; Mon
H hour 03
m minute 57

The output returned by the date component also depends heavily on the number of characters used within the String. For example, let’s take the month of June. If we define the date string as:

日期组件返回的输出也在很大程度上取决于String内使用的字符数。例如,让我们以6月份为例。如果我们将日期字符串定义为。

"MM"

Then our result will appear as the number code – 06. However, if we add another M to our date string:

那么我们的结果将显示为数字代码–06。然而,如果我们将另一个M添加到我们的日期字符串中。

"MMM"

Then our resulting formatted date appears as the word Jun.

然后,我们产生的格式化日期显示为Jun一词。

5. Applying Locales

5.应用当地语言

The SimpleDateFormat class also supports a wide range of locales which is set when the constructor is called.

SimpleDateFormat类还支持广泛的地域性,这在调用构造函数时被设置。

Let’s put this into practice by formatting a date in French. We’ll instantiate a SimpleDateFormat object whilst supplying Locale.FRANCE to the constructor.

让我们通过用法语格式化一个日期来实践它。我们将实例化一个SimpleDateFormat对象,同时向构造函数提供Locale.FRANCE

SimpleDateFormat franceDateFormatter = new SimpleDateFormat("EEEEE dd-MMMMMMM-yyyy", Locale.FRANCE);
Date myFriday = new Date(1539341312904L);
assertTrue(franceDateFormatter.format(myFriday).startsWith("vendredi"));

By supplying a given date, a Wednesday afternoon, we can assert that our franceDateFormatter has correctly formatted the date. The new date correctly starts with Vendredi – French for Friday!

通过提供一个给定的日期,一个星期三的下午,我们可以断言我们的franceDateFormatter已经正确地格式化了日期。新的日期正确地以Vendredi开始–法语中的星期五

It’s worth noting a little gotcha in the Locale version of the constructor – whilst many locales are supported, full coverage is not guaranteed. Oracle recommends using the factory methods on DateFormat class to ensure locale coverage.

值得注意的是,在构造函数的Locale版本中出现了一个小问题虽然支持许多语言,但并不保证完全覆盖。Oracle 建议使用 DateFormat class 上的工厂方法,以确保地域覆盖。

6. Changing Time Zones

6.改变时区

Since SimpleDateFormat extends the DateFormat class, we can also manipulate the time zone using the setTimeZone method. Let’s take a look at this in action:

由于SimpleDateFormat扩展了DateFormat类,我们也可以使用setTimeZonemethod操纵时区。让我们来看看这个动作。

Date now = new Date();

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEEE dd-MMM-yy HH:mm:ssZ");

simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"));
logger.info(simpleDateFormat.format(now));

simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
logger.info(simpleDateFormat.format(now));

In the above example, we supply the same Date to two different time zones on the same SimpleDateFormat object. We’ve also added the ‘Z’ character to the end of the pattern String to indicate the time zone differences. The output from the format method is then logged for the user.

在上面的例子中,我们在同一个SimpleDateFormat对象上向两个不同的时区提供了相同的Date。我们还在模式String的末尾添加了‘Z’字符,以表示时区差异。然后,来自format 方法的输出将为用户记录下来。

Hitting run, we can see the current times relative to the two time zones:

点击运行,我们可以看到相对于两个时区的当前时间。

INFO: Friday 12-Oct-18 12:46:14+0100
INFO: Friday 12-Oct-18 07:46:14-0400

7. Summary

7.总结

In this tutorial, we’ve taken a deep dive into the intricacies of SimpleDateFormat.

在本教程中,我们深入探讨了SimpleDateFormat的错综复杂之处。

We’ve looked at how to instantiate SimpleDateFormat as well as how the pattern String impacts how the date is formatted.

我们已经研究了如何实例化SimpleDateFormat以及模式String如何影响日期的格式化

We played around with changing the locales of the output String before finally experimenting with using time zones.

我们玩了一下改变输出字符串的地域性,最后尝试了使用时区

As always the complete source code can be found over on GitHub.

一如既往,完整的源代码可以在GitHub上找到