Featured image of post Java中常用的时间类及其操作

Java中常用的时间类及其操作

Dete类

Date类有两个构造方法,无参的构造方法得到的时间是当前时间,有参构造方法传入一个毫秒值。

1
2
3
4
5
6
7
	public Date() {
        this(System.currentTimeMillis());
    }

    public Date(long date) {
        fastTime = date;
    }

代码:

1
2
3
4
5
6
7
8
9
public class TimeDemo {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
    }
}

//输出结果:
Fri Apr 23 14:24:50 CST 2020

输出结果不是我们需要的,一般我们需要一些指定的格式例如:2020-05-20 12:00:00或2020/05/20 12:00:00

甚至是不要时分秒的格式。所以Date都是和SimpleDateFormat一起使用。

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class TimeDemo {
    public static void main(String[] args) throws ParseException {
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat simpleDateFormat3 = new SimpleDateFormat("yyyyMMdd HHmmss");
        System.out.println("===================Date格式换转String==================");
        System.out.println("格式化时间Date,返回类型String:" + simpleDateFormat.format(date));
        System.out.println("格式化时间Date,返回类型String:" + simpleDateFormat1.format(date));
        System.out.println("格式化时间Date,返回类型String:" + simpleDateFormat2.format(date));
        System.out.println("格式化时间Date,返回类型String:" + simpleDateFormat3.format(date));
        System.out.println("===================String格式化转Date==================");
        String str = "2020-12-12 12:00:00";
        Date parse = simpleDateFormat.parse(str);
        System.out.println(parse);

    }
}

但是SimpleDateFormat在多线程下,存在线程安全问题。

例如有下面这个工具类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class DateUtil {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static  String formatDate(Date date)throws ParseException {
        return sdf.format(date);
    }

    public static Date parse(String strDate) throws ParseException{

        return sdf.parse(strDate);
    }

}

实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class TimeDemo {
    public static void main(String[] args) throws ParseException {
        String dateStr = "2020-11-03 10:02:47";
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    System.out.println(DateUtil.parse(dateStr));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

报错:

 1
 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
50
51
52
53
54
Exception in thread "Thread-5" Exception in thread "Thread-2" Exception in thread "Thread-8" Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "E.21111"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	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:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at org.example.log.DateUtil.parse(DateUtil.java:17)
	at org.example.log.TimeDemo.lambda$main$0(TimeDemo.java:13)
	at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "1111E.21111"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	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:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at org.example.log.DateUtil.parse(DateUtil.java:17)
	at org.example.log.TimeDemo.lambda$main$0(TimeDemo.java:13)
	at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "1111E.21111E2"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	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:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at org.example.log.DateUtil.parse(DateUtil.java:17)
	at org.example.log.TimeDemo.lambda$main$0(TimeDemo.java:13)
	at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "E.21111E2"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	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:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at org.example.log.DateUtil.parse(DateUtil.java:17)
	at org.example.log.TimeDemo.lambda$main$0(TimeDemo.java:13)
	at java.lang.Thread.run(Thread.java:748)
Tue Nov 03 10:02:47 CST 2020
Tue Nov 03 10:22:47 CST 2020
Tue Nov 03 10:22:47 CST 2020
Tue Nov 03 10:02:47 CST 2020
Tue Nov 03 10:02:47 CST 2020
Tue Nov 03 10:02:47 CST 2020

parse方法的调用情况:

先调用DateFormat对象的public Date parse(String source) throws ParseException

DateFormat对象的parse方法调用SimpleDateFormat对象的public Date parse(String text, ParsePosition pos)

SimpleDateFormat对象的parse方法调用 CalendarBuilder 对象的 Calendar establish(Calendar cal)

在 establish()中,做了cal.clear();把calendar清空且没有设置新值。如果此时线程A将calendar清空且没有设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题

解决方法:

1.创建局部变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class DateUtil {
	private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static  String formatDate(Date date)throws ParseException {
        
        return sdf.format(date);
    }

    public static Date parse(String strDate) throws ParseException{
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }

}

2.使用同步代码块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class DateUtil {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String formatDate(Date date) throws ParseException {
        synchronized (sdf) {
            return sdf.format(date);
        }
    }

    public static Date parse(String strDate) throws ParseException {
        synchronized (sdf) {
            return sdf.parse(strDate);
        }
    }

}

3.使用ThreadLocal线程独享

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class DateUtil {
    private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static String formatDate(Date date) throws ParseException {
            return threadLocal.get().format(date);
    }

    public static Date parse(String strDate) throws ParseException {
            return threadLocal.get().parse(strDate);
    }

}

LocalDate类

从Java 8之后,Java里面添加了许多的新特性,其中一个最常见也是最实用的便是日期处理的类——LocalDate。

java.time.LocalDate ->只对年月日做出处理

java.time.LocalTime ->只对时分秒纳秒做出处理

java.time.LocalDateTime ->同时可以处理年月日和时分秒

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class LocalDateDemo {
    public static void main(String[] args) {
        // 当前日期:yyyy-MM-dd
        LocalDate localDate = LocalDate.now();
        // 年份
        int year = localDate.getYear();
        // 月份
        int month = localDate.getMonthValue();
        // 日期
        int dayOfMonth = localDate.getDayOfMonth();
        System.out.println(localDate);
        System.out.println(year);
        System.out.println(month);
        System.out.println(dayOfMonth);
    }
}

LocalDate的使用比Date方便很多,格式化也不需要用SimpleDateFormat。注意LocalDate不包含时分秒,想要时分秒使用LocalDateTime这个类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class LocalDateDemo {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.now();
        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter formatters1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter formatters2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String str1 = localDate.format(formatters1);
        String str2 = localDateTime.format(formatters2);
        System.out.println(str1);
        System.out.println(str2);
    }
}

LocalDate/LocalDateTime时间的加减修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class LocalDateDemo {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.of(2020,12,12);
        LocalDateTime localDateTime = LocalDateTime.of(2020,11,1,16,12,12);
        System.out.println(localDate);
        System.out.println(localDateTime);
        LocalDate result = localDate.plusYears(1);
        LocalDateTime result2 = localDateTime.plusYears(2);
        System.out.println(result);
        System.out.println(result2);
    }
}

LocalDate的时间相加相关的方法:

  • public LocalDateTime plus(TemporalAmount amountToAdd)
  • public LocalDateTime plus(long amountToAdd, TemporalUnit unit)
  • public LocalDateTime plusYears(long years)
  • public LocalDateTime plusMonths(long months)
  • public LocalDateTime plusWeeks(long weeks)
  • public LocalDateTime plusDays(long days)

LocalDateTime的时间相加相关的方法:

  • public LocalDateTime plus(TemporalAmount amountToAdd)
  • public LocalDateTime plus(long amountToAdd, TemporalUnit unit)
  • public LocalDateTime plusYears(long years)
  • public LocalDateTime plusMonths(long months)
  • public LocalDateTime plusWeeks(long weeks)
  • public LocalDateTime plusDays(long days)
  • public LocalDateTime plusHours(long hours)
  • public LocalDateTime plusMinutes(long minutes)
  • public LocalDateTime plusNanos(long nanos)

LocalDateTime就是比LocalDate多了对于时分秒的操作。减法的操作就是把plus换成minus就是时间减法操作的函数了。

一顿操作下来LocalDate/LocalDateTime比Date好用太多了,也方便很多。最重要的是在格式化Date的时候使用到的SimpleDateFormat是线程不安全的。

而在LocalDate/LocalDateTime源码的注解中写道:This class is immutable and thread-safe。LocalDate/LocalDateTime是不可变类,是线程安全的。

Licensed under CC BY-NC-SA 4.0