0%

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-Time,Java 8 受 Joda-Time 影响,重新设计了日期和时间 API,新增了一个包 java.time
  • 虽然 Java 8 之前的 API 有一些不足,但依然是被大量使用的。关于 Java 8 的 API,它使用了 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());

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

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

8. 怎样理解 Locale

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

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

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

10. 怎样理解 Calendar

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

11. 怎样理解 SimpleDateFormat

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

  • Demo:

    1
    2
    3
    4
    5
    6
    7
    8
    Calendar calendar = Calendar.getInstance();
    calendar.set(2016, 08, 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());

    输出:
    20160815日,星期一,141520

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

  • Date 中的过时方法

    • Date 中的方法参数与常识不符合,过时方法标记容易被忽略,产生误用
    • 比如,Date 构造方法中的 year 表示的是与 1900 年的差,month 是从 0 开始的
  • Calendar 操作比较繁琐

    • 一方面,一些简单的操作都需要多次方法调用,很臃肿
    • 另一方面,Calendar 难以进行比较复杂的日期操作,比如,计算两个日期之间有多少个月;根据生日计算年龄;计算下个月的第一个周一等
  • DateFormat 的线程安全性

    • DateFormat/SimpleDateFormat 是线程不安全的,即多个线程同时使用一个 DateFormat 实例的时候会有问题
    • 因为 DateFormat 内部使用了一个 Calendar 实例对象,多线程同时调用的时候,这个 Calendar 实例的状态可能就会紊乱

13. 怎样解决 DateFormatSimpleDateFormat 的线程安全问题

  • 每次使用 DateFormat/SimpleDateFormat新建一个对象
  • 使用线程同步
  • 使用 ThreadLocal
  • 使用 Joda-Time 或 Java 8 的 API,它们是线程安全的
-------------------- 本文结束感谢您的阅读 --------------------