Java时间处理

JDK8 前后对时间处理方式有区别,下面将分别介绍。

基本概念

时区

时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。

世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。

为了照顾到各地区的使用方便,又使其他地方的人容易将本地的时间换算到别的地方时间上去。有关国际会议决定将地球表面按经线从东到西,划成一个个区域,并且规定相邻区域的时间相差1小时。在同一区域内的东端和西端的人看到太阳升起的时间最多相差不过1小时。当人们跨过一个区域,就将自己的时钟校正1小时(向西减1小时,向东加1小时),跨过几个区域就加或减几小时。这样使用起来就很方便。

现今全球共分为24个时区。由于实用上常常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。

格林威治时间

GMT,是Greenwich Mean Time的缩写,及格林尼治(格林威治)平时,是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。

自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。国际天文学联合会于1928年决定,将由格林威治平子夜起算的平太阳时作为世界时,也就是通常所说的格林威治时间
一般使用GMT+8表示中国的时间,是因为中国位于东八区,时间上比格林威治时间快8个小时。
北京时间还可以用CST表示,即China Standard Time,又名中国标准时间,是中国的标准时间。当格林威治时间为凌晨0:00时,中国标准时间正好为上午8:00。
所以,有等式:CST=GMT +8 小时

时间戳

时间戳(timestamp),一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。
时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。
有了时间戳,无论我们深处哪个时区,从格林威治时间1970年01月01日00时00分00秒到现在这一时刻的总秒数应该是一样的。所以说,时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。

1970-01-01
不知道大家有没有注意到一个比较特殊的时间,1970-01-01,相信每一个开发者对这个时间都并不陌生。一般如果软件系统中出现这个时间的时候,代表着出现了网络故障、线上bug等。

微信手机充值Bug

当有些计算机存储或者传输时间戳出错时,这个时间戳就会取默认值。而在计算机中,默认值通常是 0。
当 Timestamp 为 0,就表示时间(GMT)1970年1月1日0时0分0秒。中国使用北京时间,处于东 8 区,相应就是早上 8 点。因此在中国这边,时间出错了,就经常会显示成 1970年1月1日 08:00。
System.out.println(new Date(0));
//Thu Jan 01 08:00:00 CST 1970
当我们在Java代码中使用new Date(0)来创建时间的时候,得到的结果就是Thu Jan 01 08:00:00 CST 1970,既1970年1月1日 上午08点整。

JDK8之前

Date

在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类。、
它在易用性上许多问题,下面就谈谈这个类的缺点。

● 缺点一:易用性较差。
Date类年份的起始选择是1900年,月份的起始从0 开始。这意味着,如果你想要用Date表示Java 8的发布日期,即2014年3月18日,需要创建下面 这样的Date实例:
Date date = new Date(114, 2, 18);
它的打印输出效果为:
Tue Mar 18 00:00:00 CET 2014
Date类的toString方法返回的字符串也容易误导 人。以我们的例子而言,它的返回值中甚至还包含了JVM的默认时区CET,即中欧时间(Central Europe Time)。但这并不表示Date类在任何方面支持时区。

● 缺点二:Date类是可变的
能把2014年3月18日修改成4月18日意味着什么呢?这种设计会将你拖入维护的噩梦。

Calendar

java.util.Calendar类是为了替代Date类而出现的。很不幸的是,Calendar类中也有许多缺点,许多设计缺陷问题并未彻底解决。缺点如下:

  1. 月份依旧是从0开始计算(不过,至少Calendar 类拿掉了由1900年开始计算年份这一设计)。

  2. Calendar类也是可变的,使用起来不安全。

  3. 同时存在Date和Calendar这两个类,容易使程序员产生困惑。到底该使用哪一个类呢?此外,有的特性只在某一个类有提供,比如用 于以语言无关方式格式化和解析日期或时间的DateFormat方法就只在Date类里有。

Java 8

日期和时间:LocalDate 和 LocalTime

LocalDate类的实例是一个不 可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。

LocalTime用来表示一天中的时间,比如13:45:20。

创建LocalDate和LocalTime的两种方式

    //1.1 通过of重载的工厂方法创建

    LocalDate ofDate = LocalDate.of(2014, 3, 18);//2014-03-18

    LocalTime ofTime = LocalTime.of(13, 45, 20); // 13:45:20

    //1.2 使用静态方法parse来创建

    LocalDate parseDate = LocalDate.parse("2014-03-18");//2014-03-18

    LocalTime parseTime = LocalTime.parse("13:45:20");// 13:45:20

读取LocalDate和LocalTime常用值的两种方式

    读取LocalDate和LocalTime常用值的两种方式

    //2.1 LocalDate 和 LocalTime 类提供了多种方法来 读取常用的值,比如年份、月份、星期几等

    int hour = ofTime.getHour(); // 13

    int minute = ofTime.getMinute(); // 45

    int second = ofTime.getSecond(); // 20

    System.out.println(ofTime);



    int year = ofDate.getYear(); // 2014

    Month month = ofDate.getMonth(); // MARCH

    int day = ofDate.getDayOfMonth(); // 18

    DayOfWeek dow = ofDate.getDayOfWeek(); // TUESDAY

    int len = ofDate.lengthOfMonth(); // 31 (days in March)

    boolean leap = ofDate.isLeapYear(); // false (判断是否为为闰年)

    System.out.println(ofDate);



    2.1 通过传递一个TemporalField参数给get方法拿到同样的信息。

    int y = ofDate.get(ChronoField.YEAR);//2014

    int m = ofDate.get(ChronoField.MONTH_OF_YEAR);//3

    int d = ofDate.get(ChronoField.DAY_OF_MONTH);//18

    int dow2 = ofDate.get(ChronoField.DAY_OF_WEEK);//2



    int hour2 = ofTime.get(ChronoField.HOUR_OF_DAY);//13

    int minute2 = ofTime.get(ChronoField.MINUTE_OF_HOUR);//45

    int second2 = ofTime.get(ChronoField.SECOND_OF_MINUTE);//20

合并日期与时间LocalDateTime

LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息。

    3. 创建LocalDateTime的两种方式

    //3.1 通过重载的of工厂方法创建

    LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);

    LocalDateTime dt2 = LocalDateTime.of(ofDate, ofTime);

    //3.2 通过合并日期和时间的方式创建

    LocalDateTime dt3 = ofDate.atTime(13, 45, 20);

    LocalDateTime dt4 = ofDate.atTime(ofTime);

    LocalDateTime dt5 = ofTime.atDate(ofDate);
    

    4. 从LocalDateTime中提取LocalDate或者LocalTime 组件

    LocalDate date1 = dt1.toLocalDate();//2014-03-18

    LocalTime time1 = dt1.toLocalTime();//13:45:20

机器时间处理

作为人,我们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问, 这种方式对于计算机而言并不容易理解。从计算机的角度来看,建模时间最自然的格式是 表示一个持续时间段上某个点的单一大整型数。

旧版本:Timestamp
Java 8:Instant

java.time.Instant类对时间建模的方式,基本上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的 秒数 进行计算。

  1. 通过向静态工厂方法ofEpochSecond传递一个代表秒数的值创建一个该类的实例。

    Instant instant1 = Instant.ofEpochSecond(3);
    
  2. ofEpochSecond的重载增强版本,它接收第二个以纳秒为单位的参数值,
    // 对传入作为秒数的参数进行调整。

     // 2秒之后再加上 100万纳秒(1秒)
    
     Instant instant3 = Instant.ofEpochSecond(2, 1_000_000_000);
    
     //4秒之前的100万纳秒(1秒)
    
     Instant instant4 = Instant.ofEpochSecond(4, -1_000_000_000);
    

时间区间:Duration和Period

Duration类主要用于以秒和纳秒衡量时间的长短。

Period类以年、月或者日的方式对多个时间单位建模。

	private static void userDurationAndPeriod(){

    LocalTime time1 = LocalTime.of(13, 45, 20); // 13:45:20

    LocalTime time2 = LocalTime.of(20, 45, 20); // 13:45:20

    LocalDateTime dateTime1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); // 2014-03-18T13:45

    LocalDateTime dateTime2 = LocalDateTime.of(2015, Month.MARCH, 18, 13, 45, 20); // 2015-03-18T13:45

    Instant instant1 = Instant.ofEpochSecond(3);

    Instant instant2 = Instant.ofEpochSecond(4);

    LocalDate localDate1 = LocalDate.of(2014, 3, 8);

    LocalDate localDate2 = LocalDate.of(2014, 3, 18);



    //Duration的创建方式

    //1. 通过两个LocalTimes对象、两个LocalDateTimes对象、或者两个Instant对象创建duration

    Duration d1 = Duration.between(time1,time2);

    Duration d2 = Duration.between(dateTime1, dateTime2);

    Duration d3 = Duration.between(instant1, instant2);



    //2. 通过工厂类,直接创建对应的实例;

    Duration threeMinutes1 = Duration.ofMinutes(3);

    Duration threeMinutes2 = Duration.of(3, ChronoUnit.MINUTES);





    //Duration的创建方式

    //1. 通过两个LocalDate对象创建duration

    Period tenDays = Period.between(localDate1,localDate2);



    //2. 通过工厂类,直接创建对应的实例;

    Period tenDays2 = Period.ofDays(10);

    Period threeWeeks = Period.ofWeeks(3);

    Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);



    System.out.println(d1.getSeconds());

    System.out.println(d2.getSeconds());

}

注意:由于Duration类主要用于以秒和纳 秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象做参数。同样,Period以年、月或者日的方式对多个时间单位建模,所以只能传递LocalDate对象作为参数。

格式化日期

旧版本:DateFormat
Java 8:DateTimeFormatter
新的 java.time.format 包就是格式化以及解析日期、时间对象的。这个包中最重要的是DateTimeFormatter。

private static void useDateFormatter() {
    LocalDate date = LocalDate.of(2014, 3, 18);

    //1. 从时间生成字符串

    //1.1 使用特定不同的格式器生成字符串

    String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);//20140318

    String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2014-03-18



    //1.2 DateTimeFormatter类还支持静态工厂方法,它可以按 照某个特定的模式创建格式器

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

    DateTimeFormatter chinaFormatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);



    String s3 = date.format(formatter);//18/03/2014

    String s5 = date.format(chinaFormatter2);//2014年3月18日 星期二



    //2. 从字符串生成时间

    //2.1 通过解析代表日期或时间的字符串重新创建该日期对象。

    LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE);

    LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);

    LocalDate date3 = LocalDate.parse("18/03/2014", formatter);

    LocalDate date5 = LocalDate.parse("2014年3月18日 星期二", chinaFormatter2);



    //3. 自定义DateTimeFormatter

    DateTimeFormatter complexFormatter = new DateTimeFormatterBuilder()

            .appendText(ChronoField.DAY_OF_MONTH)

            .appendLiteral(". ")

            .appendText(ChronoField.MONTH_OF_YEAR)

            .appendLiteral(" ")

            .appendText(ChronoField.YEAR)

            .parseCaseInsensitive()

            .toFormatter(Locale.ITALIAN);

}

文档地址:DateTimeFormatter

处理时区
旧版本:TimeZone
java 8:ZoneId
之前你看到的Java8中的日期和时间的种类都不包含时区信息。时区的处理是新版日期和时间API新增 加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。跟其他日期和时间类一 样,ZoneId类也是无法修改的。

时区是按照一定的规则将区域划分成的标准时间相同的区间。每个特定 的ZoneId对象都由一个地区ID标识,比如:

ZoneId romeZone = ZoneId.of(“Europe/Rome”);
地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA) 的时区数据库提供。java 8中支持的所有地区集合可以通过以下语句打印出来:

    Set<String> zoneIds= ZoneId.getAvailableZoneIds();

    for (String zone : zoneIds) {

        //共计599个

        System.out.println(zone);

    }

ZoneDateTime

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

private static void useZoneId(){

    ZoneId romeZone = ZoneId.of("Europe/Rome");

    LocalDate date = LocalDate.of(2014, Month.MARCH, 18);

    ZonedDateTime zdt1 = date.atStartOfDay(romeZone);



    LocalDateTime dateTime = LocalDateTime.now();

    ZonedDateTime zdt2 = dateTime.atZone(romeZone);



    Instant instant = Instant.now();

    ZonedDateTime zdt3 = instant.atZone(romeZone);



    System.out.println(zdt1);

    System.out.println(zdt2);

    System.out.println(zdt3);

}

下图对ZonedDateTime的组成部分进行了说明,相信能够帮助你理解LocaleDate、 LocalTime、LocalDateTime以及ZoneId之间的差异。

通过ZoneId,你还可以将LocalDateTime转换为Instant:

LocalDateTime dateTime2 = LocalDateTime.of(2018,7,21,18,46,0);

    ZoneId romeZone2 = ZoneId.systemDefault();

    Instant instantFromDateTime = dateTime2.atZone(romeZone2).toInstant();

    System.out.println(instantFromDateTime.getEpochSecond());
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值