心灵鸡汤
人是从挫折当中去奋进,从怀念中向往未来,从疾病当中恢复健康,从无知当中变得文明,从极度苦恼当中勇敢救赎,不停的自我救赎,并尽可能的帮助他人。人之优势所在,是必须充满精力自我悔改自我反省自我成长;并非一味的向人抱怨
# 一:基本用法
SimpleDateFormat是Java提供的一个格式化和解析日期的工具类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。
在Java中,可以使用SimpleDateFormat的format方法,将一个Date类型转化成String类型,并且可以指定输出格式。
// Date转String
Date data = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dataStr = sdf.format(data);
System.out.println(dataStr);
// String转Data
System.out.println(sdf.parse(dataStr));
2
3
4
5
6
7
8
日期和时间模式表达方法
字母 | 日期或时间元素 | 表示 | 示例 |
---|---|---|---|
G | Era 标志符 | Text | AD |
y | 年 | Year | 1996; 96 |
M | 年中的月份 | Month | July; Jul; 07 |
w | 年中的周数 | Number | 27 |
W | 月份中的周数 | Number | 2 |
D | 年中的天数 | Number | 189 |
d | 月份中的天数 | Number | 10 |
F | 月份中的星期 | Number | 2 |
E | 星期中的天数 | Text | Tuesday; Tue |
a | Am/pm 标记 | Text | PM |
H | 一天中的小时数(0-23) | Number | 0 |
k | 一天中的小时数(1-24) | Number | 24 |
K | am/pm 中的小时数(0-11) | Number | 0 |
h | am/pm 中的小时数(1-12) | Number | 12 |
m | 小时中的分钟数 | Number | 30 |
s | 分钟中的秒数 | Number | 55 |
S | 毫秒数 | Number | 978 |
z | 时区 | General time zone | Pacific Standard Time; PST; GMT-08:00 |
Z | 时区 | RFC 822 time zone | -0800 |
时区
默认情况下,如果不指明,在创建日期的时候,会使用当前计算机所在的时区作为默认时区,这也是为什么我们通过只要使用 new Date()
就可以获取中国的当前时间的原因。
那么,如何在Java代码中获取不同时区的时间呢?SimpleDateFormat可以实现这个功能。
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()));
2
3
# 二:非线程安全
下面例子将演示使用SimpleDateFormat类在多线程环境下处理日期得出的结果却是错误的情况,这也是在多线程环境开发中容易遇到的问题。
public class SimpleDateFormatTest1 extends Thread {
private SimpleDateFormat sdf;
private String dateString;
public SimpleDateFormatTest1(SimpleDateFormat sdf, String dateString) {
super();
this.sdf = sdf;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date dateRef = sdf.parse(dateString);
String newDateString = sdf.format(dateRef);
if (!newDateString.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + "报错了 日期字符串:" + dateString + " 转换成的日期为:" + newDateString);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String[] dateStringArray = new String[]{"2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05", "2000-01-06", "2000-01-07", "2000-01-08", "2000-01-09", "2000-01-10"};
for (int i = 0; i < 10; i++) {
new SimpleDateFormatTest1(sdf, dateStringArray[i]).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
从控制台输出的结果来看,使用单例的SimpleDateFormat类在多线程环境中处理日期极易出现日期转换错误的情况。
# 2.1 原因
查看SimpleDateFormat类中format方法的实现其实就能发现端倪。
SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。这其实就是问题的关键。
由于我们在声明SimpleDateFormat临时变量的时候,多个线程共享同一个SimpleDateFormat实例,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。
假设线程1刚刚执行完 calendar.setTime
把时间设置成 2000-01-01,还没等执行完,线程2又执行了 calendar.setTime
把时间改成了2000-01-02。这时候线程1继续往下执行,拿到的 calendar.getTime
得到的时间就是线程2改过之后的。
除了format方法以外,SimpleDateFormat的parse方法也有同样的问题。所以,不要把SimpleDateFormat作为一个共享变量使用。
# 2.2 解决方式一
public class DateTools1 {
public static Date parse(String formatPattern, String dateString) throws ParseException {
return new SimpleDateFormat(formatPattern).parse(dateString);
}
public static String format(String formatPattern, Date date) {
return new SimpleDateFormat(formatPattern).format(date).toString();
}
}
public class SimpleDateFormatTest2 extends Thread {
private String dateString;
public SimpleDateFormatTest2(String dateString) {
super();
this.dateString = dateString;
}
@Override
public void run() {
try {
Date dateRef = DateTools1.parse("yyyy-MM-dd", dateString);
String newDateString = DateTools1.format("yyyy-MM-dd", dateRef);
if (!newDateString.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + "报错了 日期字符串:" + dateString + " 转换成的日期为:" + newDateString);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String[] dateStringArray = new String[]{"2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05", "2000-01-06", "2000-01-07", "2000-01-08", "2000-01-09", "2000-01-10"};
for (int i = 0; i < 10; i++) {
new SimpleDateFormatTest2(dateStringArray[i]).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
处理错误的原理其实就是创建多个SimpleDateFormat类的实例。
# 2.3 解决方式二
可以对需要使用到 SimpleDateFormat 格式化和解析的地方使用 synchronized 代码块
# 2.4 解决方式三
ThreadLocal类能使线程绑定到指定对象,使用该类也可以解决多线程环境下SimpleDateFormat类处理错误的情况。在阿里巴巴Java开发手册的第一章第六节——并发处理中关于这一点也有明确说明:
public class DateTools2 {
private static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<>();
public static SimpleDateFormat getSimpleDateFormat(String datePattern) {
SimpleDateFormat sdf = null;
sdf = tl.get();
if (sdf == null) {
sdf = new SimpleDateFormat(datePattern);
tl.set(sdf);
}
return sdf;
}
}
public class SimpleDateFormatTest3 extends Thread {
private String dateString;
public SimpleDateFormatTest3(String dateString) {
super();
this.dateString = dateString;
}
@Override
public void run() {
try {
Date dateRef = DateTools2.getSimpleDateFormat("yyyy-MM-dd").parse(dateString);
String newDateString = DateTools2.getSimpleDateFormat("yyyy-MM-dd").format(dateRef);
if (!newDateString.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + "报错了 日期字符串:" + dateString + " 转换成的日期为:" + newDateString);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String[] dateStringArray = new String[]{"2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05", "2000-01-06", "2000-01-07", "2000-01-08", "2000-01-09", "2000-01-10"};
for (int i = 0; i < 10; i++) {
new SimpleDateFormatTest3(dateStringArray[i]).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
强烈推荐使用方式三
# 三:DateTimeFormatter
除了上述两种解决方式,如果使用JDK1.8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。可以只创建一个实例,到处引用。就像官方文档中说的,这个类 simple beautiful strong immutable thread-safe
。
# 3.1 常用 API
public static DateTimeFormatter ofPattern(String pattern)
:创建一个格式化程序使用指定的模式;public static DateTimeFormatter ofPattern(String pattern, Locale locale)
:创建一个格式化程序使用指定的模式和现场;public String format(TemporalAccessor temporal)
:使用此格式化程序格式的日期时间对象,TemporalAccessor
是一个接口,其实现类有LocalDate、LocalTime、LocalDateTime、ZonedDateTime
等。
public class DateTimeFormatterTest {
public static void main(String[] args) {
//自定义输出格式
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now())); // 2022/09/25 21:35:56
System.out.println("===================================");
//自定义格式解析
LocalDateTime localDateTime = LocalDateTime.parse("2001/07/27 22:22:22", dtf);
System.out.println(localDateTime); // 2001-07-27T22:22:22
System.out.println("===================================");
// 按照Locale默认习惯格式化
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime); // 2022-09-25T21:35:57.006+08:00[Asia/Shanghai]
System.out.println("===================================");
DateTimeFormatter formatter01 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ZZZZ");
System.out.println(formatter01.format(zonedDateTime)); // 2022-09-25T21:35:GMT+08:00
System.out.println("===================================");
DateTimeFormatter formatter02 = DateTimeFormatter.ofPattern("yyyy MMM dd EE:HH:mm", Locale.CHINA);
System.out.println(formatter02.format(zonedDateTime)); // 2022 九月 25 星期日:21:35
System.out.println("===================================");
DateTimeFormatter formatter03 = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
System.out.println(formatter03.format(zonedDateTime)); // Sun, September/25/2022 21:35
System.out.println("===================================");
// 过DateTimeFormatter预定义静态变量
LocalDateTime l2 = LocalDateTime.now();
System.out.println(l2); // 2022-09-25T21:35:57.047
System.out.println(DateTimeFormatter.ISO_DATE.format(l2)); // 2022-09-25
System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(l2)); // 2022-09-25T21:35:57.047
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
上面例子,有Java8新增的日期时间类,可以查看 时间日期
# 四:第三方类库
- 使用Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析;
- 使用Joda-Time类库来处理时间相关问题。