Java 常用基础类(五):剖析日期和时间

1. 怎样理解时区?

答:

  • 全球一共有 24 个时区,英国格林尼治是 0 时区,北京是东八区
  • 0 时区的时间也称为 GMT + 0 时间,GMT 是格林尼治标准时间,北京的时间就是 GMT + 8:00

2. 怎样理解时刻和纪元时?

答:

  • 所有计算机系统内部都用一个整数表示时刻,这个整数是距离格林尼治标准时间 1970 年 1 月 1 日 0 时 0 分 0 秒的毫秒数
  • 之所以用这个时间,更多的是历史原因
  • 格林尼治标准时间 1970 年 1 月 1 日 0 时 0 分 0 秒也被称为Epoch Time(纪元时)
  • 对于 1970 年以前的时间,使用负数表示

3. 怎样理解年历?

答:

  • 中国有公历农历之分。
  • 公历和农历都是年历,不同的年历,一年有多少月,每月有多少天,甚至一天有多少小时,这些可能都是不一样的。
  • 公历有闰年,闰年 2 月是 29 天,而其他年份则是 28 天。其他月份,有的是 30 天,有的是 31 天。
  • 农历有闰月,比如闰 7 月,一年就会有两个 7 月,一共 13 个月。
  • Java API 的设计思想是支持国际化的,支持多种年历,但没有直接支持中国的农历
  • 时刻是一个绝对时间,对时刻的解读则是相对的,与年历和时区相关

4. Java API 中关于日期和时间的主要的类有哪些?

答:日期和时间是一个比较复杂的概念,Java 8 之前的设计有一些不足,业界有一个广泛使用的第三方类库 Joda-TimeJava 8Joda-Time 影响,重新设计了日期和时间 API,新增了一个包 java.time。虽然 Java 8 之前的 API 有一些不足,但依然是被大量使用的。关于 Java 8API,它使用了 Lambda 表达式

  • Date: 表示时刻,即绝对时间,与年月日无关。
  • Calendar: 表示年历,是一个抽象类,其中表示公历的子类是 GregorianCalendar
  • DateFormat: 表示格式化作用是将日期和时间与字符串进行互相转换,也是一个抽象类,其中最常用的子类是 SimpleDateFormat
  • TimeZone: 表示时区,也是一个抽象类
  • Locale: 表示国家(或地区)和语言

5. 怎样理解 Date

答:

  • System.currentTimeMillis() 返回当前时刻距离纪元时的毫秒数。
  • Date 中的大部分方法都已经过时了。

6. 怎样获取当前的默认时区?

答:

TimeZone tz = TimeZone.getDefault();
System.out.println(tz.getID());

7. 怎样获取任意给定时区的实例?

答:

TimeZone tz = TimeZone.getTimeZone("US/Eastern");  // 获取美国东部时区
TimeZone tz = TimeZone.getTimeZone("GMT+08:00");  // 获取 GMT 形式的北京时区

8. 怎样理解 Locale

答:

  • Locale 主要有国家(或地区)和语言两个参数,每个参数都有一个代码
  • CN: 中国内地;TW: 中国台湾地区;US: 美国;zh: 中文语言;en: 英文语言

9. 怎样获取计算机默认语言?

答:

Locale locale = Locale.getDefault();
System.out.println(locale.toString());

10. 怎样理解 Calendar?

答:

  • getInstance() 方法会根据 TimeZoneLocale 创建对应的 Calendar 子类对象,在中文系统中,子类一般是表示公历的 GregorianCalendar
  • 这种隐藏对象创建细节的方式,是计算机程序中一种常见的设计模式,叫工厂方法
  • Calendar 做了一项非常繁琐的工作,根据 TimeZoneLocale,在绝对时间毫秒数和日历字段之间自动进行转换,且对不同日历字段的修改进行自动同步更新。

11. 怎样理解 SimpleDateFormat?

答:

  • SimpleDateFormatDateFormat 的子类。相比 DateFormat 它的一个主要不同是,它可以接受一个自定义的模式(pattern)作为参数,这个模式规定了 Date 的字符串形式。
  • 例子:

    Calendar calendar = Calendar.getInstance();
    calendar.set(2016, 07, 15, 14, 15, 20); // 2016-08-15 14:15:20
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒"); 
    // E 表示星期几;HH 表示 24 小时制的小时数,用两位数表示;hh 表示 12 小时制的小时数;a 表示是上午还是下午;SSS 表示三位的毫秒数
    System.out.println(sdf.format(calendar.getTime());
    
    输出:
    2016年08月15日,星期一,14时15分20秒
    

12. 怎样理解 Java 8 之前的日期与时间相关 API 的局限性?

答:

  • Date 中的过时方法Date 中的方法参数与常识不符合,过时方法标记容易被忽略,产生误用。比如,Date 构造方法中的 year 表示的是与 1900 年的差,month 是从 0 开始的。
  • Calendar 操作比较繁琐。一方面,一些简单的操作都需要多次方法调用,很臃肿;另一方面,Calendar 难以进行比较复杂的日期操作,比如,计算两个日期之间有多少个月;根据生日计算年龄;计算下个月的第一个周一等。
  • DateFormat 的线程安全性DateFormat/SimpleDateFormat 是线程不安全的,即多个线程同时使用一个 DateFormat 实例的时候会有问题,因为 DateFormat 内部使用了一个 Calendar 实例对象,多线程同时调用的时候,这个 Calendar 实例的状态可能就会紊乱。

13. 怎样解决 DateFormat/SimpleDateFormat 的线程安全问题

答:

  • 每次使用 DateFormat/SimpleDateFormat 都新建一个对象(简单粗暴)。
  • 使用线程同步(技术)。
  • 使用 ThreadLocal(技术)。
  • 使用 Joda-TimeJava 8API,它们是线程安全的(第三方)。
-------------本文结束感谢您的阅读-------------