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

  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. 怎样获取当前的默认时区?
    答:

    1
    2
    TimeZone tz = TimeZone.getDefault();
    System.out.println(tz.getID());
  1. 怎样获取任意给定时区的实例?
    答:

    1
    2
    TimeZone tz = TimeZone.getTimeZone("US/Eastern");  // 获取美国东部时区
    TimeZone tz = TimeZone.getTimeZone("GMT+08:00"); // 获取 GMT 形式的北京时区
  1. 怎样理解 Locale
    答:

    • Locale 主要有国家(或地区)和语言两个参数,每个参数都有一个代码
    • CN: 中国内地;TW: 中国台湾地区;US: 美国;zh: 中文语言;en: 英文语言
  2. 怎样获取计算机默认语言?
    答:

    1
    2
    Locale locale = Locale.getDefault();
    System.out.println(locale.toString());
  1. 怎样理解 Calendar?
    答:

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

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

      1
      2
      3
      4
      5
      6
      7
      8
      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秒
  1. 怎样理解 Java 8 之前的日期与时间相关 API 的局限性?
    答:

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

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