Java中的时间和日期处理

本文主要讲解Java 8的时间处理方式和Java8之前版本的时间处理方式的区别。笔者将Java8之前的jdk版本统称为旧版本。

一、日期处理

旧版本

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常用值的两种方式
        //2. 读取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());

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

为什么跳槽加薪会比内部调薪要高?

之后的若干年加薪都是遵循企业内部晋升通道,如果企业加薪幅度赶不上同岗位市场薪酬回报的上涨幅度,就会出现题主所说的现象。

8610
来自专栏Grace development

“生于忧患,死于安乐”之程序员人生

没错,大多人的经历都是如此!这样艰苦的奋斗,不断的努力,使我们在这个行业立足。正是这份兴趣、这份毅力、这份坚持支撑着我们,才让我们走到了现在。

7810
来自专栏养码场

面试想拿 25K,HR 却说只值 15K,技术人该如何反驳?

知乎上看到这样一个问题:面试的时候想拿xx,HR却说我只值xx,这种情况下应该怎么办?

21820
来自专栏java一日一条

IT 已成为最疯狂的加班行业,没有之一

夜幕降临,当IT大楼里依然灯火通明时,那一刻,我仿佛王进喜、石传翔等劳模灵魂附体,我知道我不是一个在加班,我不是一个人!连续9个通宵加班都不是事,一点不夸张,这...

12620
来自专栏java一日一条

为什么你应该学习编程

很多人,从HR专业人员到市场营销专业人员,都在硅谷工作,但硅谷对编程专业人士的需求更多,也更重视这个行业。大多数人往往更偏爱一个收入更高的工作。

9030
来自专栏葡萄城控件技术团队

生产制造MES系统中,如何应用报表分析?

中国制造业产业结构逐步从低附加值传统加工制造业和资源密集型制造业向高附加值新型制造业转型升级。生产制造类企业为了监控项目进度和产品生产情况,会需要制作大量的报表...

34730
来自专栏java一日一条

程序员的走与留?

初级开发人员在他们的职业生涯早期必须考虑的一个大问题就是,他是该离开还是留下。我在我职业生涯的早期发现,相对于我的同事我所得的报酬过低,而这仅仅是因为他们工作的...

7120
来自专栏java一日一条

程序员的走与留?

初级开发人员在他们的职业生涯早期必须考虑的一个大问题就是,他是该离开还是留下。我在我职业生涯的早期发现,相对于我的同事我所得的报酬过低,而这仅仅是因为他们工作的...

7320
来自专栏理论坞

鸟笼逻辑

6930
来自专栏理论坞

破窗效应

8720

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励