在开发过程中,经常会遇到记录时间的场景,例如:创建时间createTime,抄表时间戳timestamp。

Java中有java.util.Datejava.time.LocalDateTime等类,还有Long型时间戳,什么场景用什么类型就存在一定的差异性了。

1. Java类

1.1 java.util.Date

Date类是我们最常用到的时间类了,常见用法如下:

Date date = new Date();

// 本质上也是调用了new Date(long timestamp)
Calendar.getInstance().getTime()

构造函数如下:

public class Date implements java.io.Serializable, Cloneable, Comparable<Date> {

    private transient long fastTime;

		public Date() {
        this(System.currentTimeMillis());
    }

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

可以看出,Date对象本质上也是持有了一个时间戳long fastTime,表示自格林威治时间( GMT)1970年1月1日0点至Date所表示时刻所经过的毫秒数。

Date类本身时没有时区概念的,所以在格式化为字符串或者存储到数据库中时,需要指定一个时区。一般接口中格式化时,会默认采用服务器时区,而存储到数据库时,会使用服务器的时区设置,例如:MongoDB默认时区为UTC,也就是+00:00,所以我们在数据库中看到的createTime都比实际时间要向前推8小时。

1.1.1 SimpleDateFormat

说到Date类,不得不提的就是java.text.SimpleDateFormat了,这个类非常坑——它是线程不安全的

网上非常常见的做法是写一个DateUtils类,把SimpleDateFormat按照不同的格式化需求,实例化为多个常量,然后在其他地方调用SDF.parse()等方法。

public class SimpleDateFormatTest extends Thread {
		private static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		private String name;
		private String date;
		
		public SimpleDateFormatTest(String name, String date) {
		    this.name = name;
		    this.date = date;
		}
		
		@Override
		public void run() {
		    try {
		        Date d = SDF.parse(date);
		        System.out.println(name + ": date:" + d);
		    } catch (Exception e) {
		        e.printStackTrace();
		    }
		}

		public static void main(String[] args) {
				ExecutorService es = Executors.newFixedThreadPool(3);
				es.execute(new SimpleDateFormatTest("A", "2023-01-01 08:00:00"));
		    es.execute(new SimpleDateFormatTest("B", "2022-01-01 10:00:00"));
		}
}

在多线程环境下执行,经常会抛出异常,有时候还会看到如下错误结果。

第一次
A: date:Sun Jan 01 08:00:00 CST 2023
B: date:Sun Jan 01 08:00:00 CST 2023

第二次
B: date:Fri Nov 14 08:00:00 CST 2200
A: date:Fri Nov 14 08:00:00 CST 2200

而且这种多线程并发的问题还极难复现和排查,由此可以看出,整个Date类的使用过程中存在许多坑。

1.2 java.time

曾经有个最烂Java类投票,Date/Calendar类排第二(第一是XML/DOM类),所以,在Java8中引入了JSR 310日期时间,都包括在java.time包中。这里面有3个常用时间类,分别代表日期、时间、日期+时间。