Java日期和时间

Java日期和时间

在我们的日常开发中对日期和时间的处理是不可避免的,几乎每种编程语言都提供了处理日期和时间的API。同样Java也提供了强大的日期和时间API。但是由于Java出现的时间比较早,早期的日期和时间API存在很多弊病。从Java8开始Java提供了全新的日期和时间API。在学习Java时间和日期之前,我们有必要对时间有一个宏观的认识。

时间( UTC、GMT)与时间戳

可能很多人对时间的概念并不是很重视,就像对待空气一样,没有什么感觉,因为他们实在是太常见了。恰好如此他们都死极其重要的存在。我们不管在世界的哪个角落,世界的时间都是一样的,都存在一个世界时。在世界时提出的年代,正值英国强大,所以也就取得了定义时间的权利。那个时候定义的世界标准时就是格林尼治时间(GMT)。

格林尼治标准时间(Greenwich Mean Time)是指当太阳横穿格林尼治子午线(本初子午线)时的时间。

由于原子钟(非常精确的计时工具)的出现,就产生了世界时(UT)。

世界时(Universal Time)。根据原子钟计算出来的时间。

但是随着时间的推移,人们发现地球自转越来越慢,这就意味着GMT时间每年会多出零点几秒。照这样算的话若干年以后我们一天可能就不止24小时了。为了解决这个问题,就又提出了一个协调世界时(UTC)。它既与世界时(UT)接近又不像GMT那样每年会增加零点几秒。使世界时间达到一个较为平衡的地步。

协调世界时(Coordinated Universal Time),是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。它是现在世界最主要的世界标准时间。

UTC可以保证与GMT的时间误差不超过0.9秒,所以我们可以近似的认为UTC=GMT 。这是因为UT与GMT随着时间推移当它们的差距超过0.9秒时,这个时候就会将UTC向前或者向后拨动一秒,以此来保证UTC在UT与GMT之间的协调平衡。而这改变的一秒就称之为闰秒。然而我们在计算机中时间的概念确实时间戳。

时间戳指的就是Unix时间戳(Unix timestamp)。是一种时间表示方式,UTC/GMT时间1970年01月01日00时00分00秒起至现在的总秒数。

在程序中我们保存的时间都是时间戳,然后根据时间戳转换成对应时区的本地时间。

Java的时间和日期处理

我们在实际开发过程中可能会经常遇到有人还在使用旧版Java时间日期API,或者是需要维护旧代码。这样都不可避免需要与旧版Java时间日期API打交道,但是我们都知道旧版Java时间日期API存在很多弊端,所以我们需要弄清楚旧版与新版的区别,既要兼顾了解旧版,也要积极推行新版的Java时间日期API。这样我们才能游刃有余地处理日期和时间。

旧版本

  • Date
    缺点:
    1. Date类年份的起始选择是1900年,月份的起始从0 开始。

    A year y is represented by the integer y - 1900.
    A month is represented by an integer from 0 to 11; 0 is January, 1 is February, and so forth; thus 11 is December.
    A date (day of month) is represented by an integer from 1 to 31 in the usual manner.
    An hour is represented by an integer from 0 to 23. Thus, the hour from midnight to 1 a.m. is hour 0, and the hour from noon to 1 p.m. is hour 12.
    A minute is represented by an integer from 0 to 59 in the usual manner.
    A second is represented by an integer from 0 to 61; the values 60 and 61 occur only for leap seconds and even then only in Java implementations that actually track leap seconds correctly. Because of the manner in which leap seconds are currently introduced, it is extremely unlikely that two leap seconds will occur in the same minute, but this specification follows the date and time conventions for ISO C.
    年份y用整数y - 1900表示。
    一个月用一个从0到11的整数表示;0是一月,1是二月,以此类推;因此11日就是12月。
    按照通常的方式,日期(月中的日)由一个从1到31的整数表示。
    一小时用从0到23的整数表示。因此,从午夜到凌晨1点的一小时是0小时,从中午到下午1点的一小时是12小时。
    按照通常的方式,一分钟由一个从0到59的整数表示。
    一秒由0到61的整数表示;值60和61只在闰秒中出现,甚至只有在真正正确跟踪闰秒的Java实现中才会出现。由于目前引入闰秒的方式,在同一分钟内出现两个闰秒的可能性极小,但本规范遵循ISO C的日期和时间约定。

    1. Date是可变的,多线程下不安全。
  • Calendar
    java.util.Calendar类是为了替代Date类而出现的。很不幸的是,Calendar类中也有许多缺点,许多设计缺陷问题并未彻底解决。
    缺点:
    1. 月份依旧是从0开始计算(不过,至少Calendar 类拿掉了由1900年开始计算年份这一设计)。
    2. Calendar类也是可变的,使用起来不安全。
    3. 同时存在Date和Calendar这两个类,容易使程序员产生困惑。到底该使用哪一个类呢?此外,有的特性只在某一个类有提供,比如用 于以语言无关方式格式化和解析日期或时间的DateFormat方法就只在Date类里有。
  • SimpleDateFormat

    SimpleDateFormat is a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting (date text), parsing (text date), and normalization.
    可以轻松的在时间日期字符串与对应时间日期类之间转换。但是多线程下不安全,需要手动同步操作或者使用ThreadLocal。推荐使用Java8的DateTimeFormatter。

总之Java旧版的时间日期API设计的相当糟糕,具体的抽象比较杂乱。相比之下,Java8的时间日期API更加值得学习和借鉴。

Java8

相对于旧版本的日期和时间处理类来说,新版本的时间和日期处理类设计的更加完整逻辑更清晰,而不是像旧版那样笼统的一个类解决问题。

  • Clock(时间源)

    Clock抽象类主要是用来获取当前准确的时间戳、日期和时间,同时包含时区(保存了ZoneId)。Clock就相当于是一个时钟服务器一样,能够给程序提供准确的时间。

  • Instant(时间戳)

    Instant是对时间戳的抽象描述。分别用一个long值保存时间戳的秒数和一个int值保存纳秒值。它无疑是时间戳的更加精确的描述。

  • LocalDate(日期)

    A date without a time-zone in the ISO-8601 calendar system.This class does not store or represent a time or time-zone.This class is immutable and thread-safe.

  • LocalTime(时间)

    A time without a time-zone in the ISO-8601 calendar system.This class does not store or represent a date or time-zone.This class is immutable and thread-safe.

  • LocalDateTime(日期和时间)

    A date-time without a time-zone in the ISO-8601 calendar system.This class does not store or represent a time-zone.This class is immutable and thread-safe.

LocalDate、LocalTime、LocalDateTime这几个类都是不包含时区的日期和时间表示方式。他们都是不可变的,多线程安全的日期时间类。他们就像是对日期和时间直观的描述,他们可以直观地对日期和时间进行操作,当然如果要获取当前的时间那么也是需要从Clock中获取。

  • ZoneId(时区)

    之前看到的Java8中的日期和时间的种类都不包含时区信息。时区的处理是新版日期和时间API新增加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。跟其他日期和时间类一 样,ZoneId类也是无法修改的。时区是按照一定的规则将区域划分成的标准时间相同的区间。每个特定 的ZoneId对象都由一个地区ID标识。地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA) 的时区数据库提供。

  • ZoneDateTime

    一旦得到一个ZoneId对象,你就可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点。

  • DateTimeFormatter

    Formatter for printing and parsing date-time objects.
    This class provides the main application entry point for printing and parsing and provides common implementations of DateTimeFormatter:

    • Using predefined constants, such as ISO_LOCAL_DATE
    • Using pattern letters, such as uuuu-MMM-dd
    • Using localized styles, such as FormatStyle.FULL
      DateTimeFormatter可以将日期时间转换成特定的格式(预定义的ISO_LOCAL_DATE、“uuuu-MMM-dd”字符串模式、FormatStyle)

新旧转换

  • Date转LocalDateTime

    public static void main(String[] args) {
            Date date = new Date();
            //先从date中获取到时间戳,理论上来说有了时间戳,不管什么类型都能转换过来。
            Instant instant = date.toInstant();
            //然后获取到包含时区的ZonedDateTime,ZonedDateTime才是关键角色,它可以转成LocalDate、LocalTime、LocalDateTime,Instant
            ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
            LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
            System.out.println(localDateTime);
        }
    
  • LocalDateTime转Date

    public static void main(String[] args) {
            LocalDateTime localDateTime = LocalDateTime.now();
            ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
            Date date = Date.from(zonedDateTime.toInstant());
        }
    

虽然说Java8的时间和日期API可能我们用到的这是其中那么几个类就能满足我们的需求了。但是我们不能止步于会用的阶段,我们还需要看到更深层次的东西。这其中蕴含的设计与抽象思路是如此清晰明朗,这才是软件该有的样子。有一种美感蕴含在其中。

posted @ 2021-02-03 23:54  HuNanHank  阅读(296)  评论(0编辑  收藏  举报