java.text.SimpleDateFormat
是常用的时间日期格式化和解析的Java类。
SimpleDateFormat
支持到毫秒精度。
SimpleDateFormat
最常用的方法是格式化日期时间和解析日期时间。
public class SimpleDateFormat extends DateFormat {
//格式化日期时间
public final String format(Date date) {......}
//解析日期时间
public Date parse(String source) throws ParseException {......}
......
}
public static void main(String[] args) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");
//格式化日期时间
System.out.println(format.format(new Date()));
System.out.println(format.format(new Date()));
//精度只到毫秒
System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS SSS").format(new Date()));
try {
//解析日期时间
System.out.println(format.parse("2020-01-02 03:04:05 066"));
System.out.println(format.parse("2020-01-02 03:04:05"));
} catch (ParseException e) {
//解析目标日期时间字符串格式不匹配
e.printStackTrace();
}
}
输出:
2021-06-01 08:24:08 404
2021-06-01 08:24:08 405
2021-06-01 08:24:08 405 405
Thu Jan 02 03:04:05 CST 2020
java.text.ParseException: Unparseable date: "2020-01-02 03:04:05"
at java.text.DateFormat.parse(DateFormat.java:366)
at com.wkw.study.jdk.SimpleDateFormatTest.main(SimpleDateFormatTest.java:26)
追踪日期格式化方法String format(Date date)
,发现
这里使用了全局变量Calendar
/**
* The {@link Calendar} instance used for calculating the date-time fields
* and the instant of time. This field is used for both formatting and
* parsing.
*
* Subclasses should initialize this field to a {@link Calendar}
* appropriate for the {@link Locale} associated with this
* DateFormat
.
* @serial
*/
protected Calendar calendar;
这个变量没有做线程安全控制,通常时间日期工具类中会有静态static
方法,显然如果在静态时间日期工具类中使用了SimpleDateFormat
,多线程并发调用时,就会争用calendar
,比如 A 线程在执行设置的时候,刚好被 B 线程抢先设置了,日期时间格式化就会出现问题:
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String originDate1 = "2020-01-02 03:04:05";
String originDate2 = "2021-11-22 13:14:15";
Date date1 = format.parse(originDate1);
Date date2 = format.parse(originDate2);
new Thread(() -> {
while (true) {
String format1 = format.format(date1);
if (!originDate1.equals(format1)) {
System.out.println(originDate1 + " <> " + format1);
}
}
}).start();
new Thread(() -> {
while (true) {
String format2 = format.format(date2);
if (!originDate2.equals(format2)) {
System.out.println(originDate2 + " <> " + format2);
}
}
}).start();
}
输出:
2020-01-02 03:04:05 <> 2021-11-22 13:14:15
2021-11-22 13:14:15 <> 2021-11-22 13:14:05
2020-01-02 03:04:05 <> 2020-01-22 13:14:15
2021-11-22 13:14:15 <> 2021-11-02 03:04:05
2020-01-02 03:04:05 <> 2020-01-22 13:14:15
2021-11-22 13:14:15 <> 2021-11-02 03:04:05
2021-11-22 13:14:15 <> 2020-01-02 03:04:05
2020-01-02 03:04:05 <> 2020-01-22 13:14:15
......
可以看到,就两个线程争用calendar
,日期格式化结果就变的乱七八糟。
日期时间解析Date parse(String source)
同样用到了calendar
,一样的问题:
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String originDate1 = "2020-01-02 03:04:05";
String originDate2 = "2021-11-22 13:14:15";
Date date1 = format.parse(originDate1);
Date date2 = format.parse(originDate2);
new Thread(() -> {
while (true) {
try {
Date date = format.parse(originDate1);
if (date1.compareTo(date) != 0) {
System.out.println(date1 + " <> " + date);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
while (true) {
try {
Date date = format.parse(originDate2);
if (date2.compareTo(date) != 0) {
System.out.println(date2 + " <> " + date);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
输出:
Thu Jan 02 03:04:05 CST 2020 <> Thu May 14 10:04:05 CST 2020
Mon Nov 22 13:14:15 CST 2021 <> Sun May 13 03:30:48 CST 190728634
Mon Nov 22 13:14:15 CST 2021 <> Wed Sep 22 13:14:15 CST 2021
Exception in thread "Thread-0" java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.wkw.study.jdk.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:24)
at java.lang.Thread.run(Thread.java:748)
多线程下甚至都会抛异常。
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String originDate1 = "2020-01-02 03:04:05";
String originDate2 = "2021-11-22 13:14:15";
Date date1 = format.parse(originDate1);
Date date2 = format.parse(originDate2);
new Thread(() -> {
//线程内定义局部变量,不与其他线程争用SimpleDateFormat
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while (true) {
String format1 = localFormat.format(date1);
if (!originDate1.equals(format1)) {
System.out.println(originDate1 + " <> " + format1);
}
}
}).start();
new Thread(() -> {
//线程内定义局部变量,不与其他线程争用SimpleDateFormat
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while (true) {
String format2 = localFormat.format(date2);
if (!originDate2.equals(format2)) {
System.out.println(originDate2 + " <> " + format2);
}
}
}).start();
}
线程内定义局部变量,不与其他线程争用SimpleDateFormat
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String originDate1 = "2020-01-02 03:04:05";
String originDate2 = "2021-11-22 13:14:15";
Date date1 = format.parse(originDate1);
Date date2 = format.parse(originDate2);
new Thread(() -> {
while (true) {
String format1 = SimpleDateFormatTest.dateFormat(format, date1);
if (!originDate1.equals(format1)) {
System.out.println(originDate1 + " <> " + format1);
}
}
}).start();
new Thread(() -> {
while (true) {
String format2 = SimpleDateFormatTest.dateFormat(format, date2);
if (!originDate2.equals(format2)) {
System.out.println(originDate2 + " <> " + format2);
}
}
}).start();
}
//日期格式化加锁
public static synchronized String dateFormat(SimpleDateFormat format, Date date) {
return format.format(date);
}
public class SimpleDateFormatTest {
private static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String originDate1 = "2020-01-02 03:04:05";
String originDate2 = "2021-11-22 13:14:15";
Date date1 = format.parse(originDate1);
Date date2 = format.parse(originDate2);
new Thread(() -> {
while (true) {
String format1 = SimpleDateFormatTest.dateFormat(date1);
if (!originDate1.equals(format1)) {
System.out.println(originDate1 + " <> " + format1);
}
}
}).start();
new Thread(() -> {
while (true) {
String format2 = SimpleDateFormatTest.dateFormat(date2);
if (!originDate2.equals(format2)) {
System.out.println(originDate2 + " <> " + format2);
}
}
}).start();
}
public static String dateFormat(Date date) {
//使用ThreadLocal进行线程隔离
SimpleDateFormat format = (SimpleDateFormat)threadLocal.get();
if (format == null) {
format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
threadLocal.set(format);
}
return format.format(date);
}
}
public static void main(String[] args) throws ParseException {
// String --> LocalTime
LocalTime localTime = LocalTime.parse("07:43:53");
System.out.println(localTime);
// String --> LocalDate
DateTimeFormatter formatter12 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); // 12小时
LocalDate localDate = LocalDate.parse("2019-12-07 07:43:53", formatter12);
System.out.println(localDate);
// String --> LocalDateTime
DateTimeFormatter formatter24 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 24小时
LocalDateTime localDateTime = LocalDateTime.parse("2019-12-07 07:43:53", formatter24);
System.out.println(localDateTime);
// LocalDateTime --> String
String dateTime = localDateTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
System.out.println(dateTime);
// LocalDateTime --> String
System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd HHmmss")));
}
输出
07:43:53
2019-12-07
2019-12-07T07:43:53
2019年12月07日
20191207 074353
以LocalDateTime
为例(LocalDate
和LocalTime
同理)
**String
解析为 LocalDateTime
**可以使用LocalDateTime.parse
方法:
/**
* Obtains an instance of {@code LocalDateTime} from a text string using a specific formatter.
*
* The text is parsed using the formatter, returning a date-time.
*
* @param text the text to parse, not null
* @param formatter the formatter to use, not null
* @return the parsed local date-time, not null
* @throws DateTimeParseException if the text cannot be parsed
*/
public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) { ...... }
其中,DateTimeFormatter
可以用DateTimeFormatter.ofPattern
方法实例化:
/**
* pattern为格式化 or 解析模式串
*/
public static DateTimeFormatter ofPattern(String pattern) { ...... }
LocalDateTime
格式化为String
可以使用format
方法:
/**
* Formats this date-time using the specified formatter.
*
* This date-time will be passed to the formatter to produce a string.
*
* @param formatter the formatter to use, not null
* @return the formatted date-time string, not null
* @throws DateTimeException if an error occurs during printing
*/
public String format(DateTimeFormatter formatter) { ...... }
参考:
SimpleDateFormat 为什么不是线程安全的?
SimpleDateFormat 性能问题