Java 时间、日期与时区的关系

简介

本文主要是对Java中常用的时间类做一下梳理,包括Java 8中新增的日期/时间类等,以及它们和时区的关联性

Date / Calendar

java.util.Date 是最常用的类之一,它的精度为毫秒,因为它的有些地方不合理,后来出现了Calendar来替代。java.util.Date 本身存储的是时间戳(1970年1月1日 00:00:00 GMT以来此对象表示的毫秒数 ),所以它和时区地域是无关的,只是在显示的时候加载了TimeZone来调整了时间的显示。

Calendar的getInstance()方法有参数为TimeZone和Locale的重载,可以使用指定时区和语言环境获得一个日历。无参则使用默认时区和语言环境获得日历。

public static Calendar getInstance(){
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
// 重载的带有时区的方法
public static Calendar getInstance(TimeZone zone){
    return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}

我们来看下如下的代码

public class Te {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 这边只是指定了这个SimpleDateFormat的时区为GMT时区,系统本身的时区还是默认的时区(中国时区)
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        
        Date date = new Date(1525518893403L);// 中国时间:2018-5-5 19:14:53
        System.out.println(dateFormat.format(date));
        // 字符串没有时区信息
        String dateStr = "2018-05-05 19:14:53";
        Date newDate = dateFormat.parse(dateStr);
        System.out.println(newDate);
        System.out.println(dateFormat.format(newDate));
    }
}

output:
2018-05-05 11:14:53
Sun May 06 03:14:53 CST 2018  (对应的值为:2018-05-06 03:14:53)
2018-05-05 19:14:53

从上面的结果可以知道,Date是不带时区信息,所以在dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 后,输出的时间就是GMT时区的时间。

字符串的值被认为是GMT时间,所以newDate显示的时间是字符串时间 +8小时后的时间,从第三行输出也印证了这一点。

可以看到,上面的输出结果可能和我们预想的并不一致,Calendar 类作为新的类,没有那么复杂

public class Te {
    public static void main(String[] args) throws ParseException {
        Date date = new Date(1525518893403L);// 中国时间:2018-5-5 19:14:53
        System.out.println(date);
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTime(date);
        System.out.println(calendar.getTime());

        System.out.println(calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH) + "-" +
            calendar.get(Calendar.DAY_OF_MONTH) + " " +
            calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" +
            calendar.get(Calendar.SECOND));
    }
}

output:
Sat May 05 19:14:53 CST 2018
Sat May 05 19:14:53 CST 2018
2018-4-5 11:14:53

可以看到,我们在使用Calendar的时候,获取时间不能直接使用getTime(),因为其返回的是一个Date,而Date是没有时区信息的,应该使用calendar.get()方法。

java.sql.Date / java.sql.Timestamp

这两个类都和数据库有关系,看报名就知道了。且他们都是java.util.Date 的子类。因此,类似的,他们也都没有时区信息。

java.sql.Date 是只保留年月日,而没有时分秒信息,对应数据库(eg. mysql)中的date类型.

java.sql.Timestamp 精确到纳秒,对应数据库(eg.mysql)中的timestamp类型

java8 新增的日期时间类

  • Instant:瞬时实例。
  • LocalDate:本地日期,不包含具体时间。例如:2014-01-14可以用来记录生日、纪念日、加盟日等。
  • LocalTime:本地时间,不包含日期。
  • LocalDateTime:组合了日期和时间,但不包含时差和时区信息。
  • ZonedDateTime:最完整的日期时间,包含时区和相对UTC或格林威治的时差。

Instant

Instant 以Unix时间戳的形式存储日期时间 ,精确到纳秒

// 获取当前的时间戳
Instant timestamp = Instant.now();
System.out.println(timestamp);
// 类似的,从指定的时间戳生成 Instant
Instant specificTime = Instant.ofEpochMilli(System.currentTimeMillis());
System.out.println(specificTime);

output:
2018-05-05T12:44:54.037Z
2018-05-05T12:44:54.195Z

Instant 和Date类似,没有时区信息(也可以认为是0时区,即GMT时区)

ZonedDateTime

java8 把时区也分离出来了,现在转换时区很方便。如果要显示指定的时区,可以使用 ZonedDateTime

ZonedDateTime result=ZonedDateTime.ofInstant(Instant.now(),ZoneId.systemDefault());
System.out.println(result);
// ZonedDateTime 底层就是用Instant存储的
System.out.println(result.toInstant());

output:
2018-05-05T20:52:04.096+08:00[Asia/Shanghai]
2018-05-05T12:52:04.096Z

下面这个例子是时区的转换

//获取当前时区的日期时间
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
System.out.println(now.withZoneSameInstant(ZoneId.of("America/New_York")));

//获取美国洛杉矶时区的日期时间,写这段代码的时候,北京时间是 2018-05-05 21:53:26,下面等于是生成了了纽约时间 2018-05-05 21:53:26。因为ZonedDateTime底层是用Instant存储的,所以USANow内的Instant的值和上面now的Instant值是不相等的。
ZonedDateTime USANow = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("America/New_York"));
System.out.println(USANow);
System.out.println(USANow.withZoneSameInstant(ZoneId.systemDefault()));

output:
2018-05-05T21:53:26.322+08:00[Asia/Shanghai]
2018-05-05T09:53:26.322-04:00[America/New_York]
2018-05-05T21:53:26.356-04:00[America/New_York]
2018-05-06T09:53:26.356+08:00[Asia/Shanghai]

相比与之前的时区转换,ZonedDateTime的操作是简便了很多,更便于理解

LocalDate / LocalTime

如上面介绍的所示,一个只有日期,一个只有时间,两者都是没有指定时区的
​```java
LocalDate today = LocalDate.now();
System.out.println(today);

LocalTime now = LocalTime.now();
System.out.println(now);

output:
2018-05-05
21:00:27.979

LocalDate还提供了很多有用的方法
​```java
// 构造日期
LocalDate date = LocalDate.of(2016, 4, 18);  
LocalDate date2 = LocalDate.of(2016, 5, 18); 
//比较两者相等
if (!date.equals(date2)) {  
    System.out.printf("date %s 和 date2 %s 不是同一天!%n", date, date2);
}

LocalDateTime是两者的聚合体,这里就不写了,这些类都有一些很好用的方法,查API文档即可,这里就不浪费时间记述了,浪费大家的时间

posted @ 2018-09-19 09:43  hinsy  阅读(2183)  评论(0编辑  收藏  举报