java date time问题总结

 

问题描述

    最近因为要在工程中用到日期相关的东西,需要将一些表示日期的字符串转换成日期类型并计算该日期的前一天。粗看起来这个问题还是比较简单的,主要就是对于方法api的使用。主要的变换过程就是String--->日期类型---->运算---->String。这么一个过程。

    于是,在经过一番搜索之后,得到了一段如下的代码:

 

String yesterday(String checkOut) throws ParseException {
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-mm-dd");
    Date date = formatter.parse(checkOut);
    int dayInMills = 1000 * 60 * 60 * 24;
    return formatter.format(date.getTime() - dayInMills);
}

   在上述代码里,因为我们要输入如2015-12-23等这种格式的字符串,于是首先通过SimpleDateFormat将它解析成Date类型,然后再用这个Date对象减去一个整天的毫秒数。这样就可以得到我们想要的日期了。

    如果我们输入的数据为"2015-12-23",这个时候得到的结果是"2015-12-22"。嗯,这样结果看起来没有问题。实际上这种转换还是有问题的,比如说如果我们输入"2015-02-01",那么将出现什么结果呢?结果程序运行的结果是"2014-02-31"!

    可见,这种想当然的去用时间减毫秒数的办法并不可行,而且隐藏的问题不容易发觉。那么有没有什么简单易用的办法来解决呢?

 

Java8 time

     Java里面对日期的支持一直不理想,而且以前的Date类型还是mutable的,这意味着该类型还不是线程安全的。在java 8里引入的新的包对它有一个明显的改进。比如说对于上述的问题,在java 8里需要怎么做呢?

 

public String yesterday(String checkOut) {
    LocalDate date = LocalDate.parse(checkOut);
    return date.minusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
}

   针对我们前面的问题,这里只需要两行代码就解决了。这里引入了新的类型LocalDate, DateTimeFormatter等。那么上面这两行代码表示什么意思呢?首先,前面的LocalDate.parse()方法将输入的字符串转换成LocalDate类型。由于LocalDate对象的默认显示格式就是"yyyy-mm-dd"这种样式的,所以对于本文中的问题就可以直接转换过来。

    后续的LocalDate有一个minusDays方法,用它减去1则得到前一天的日期。然后再调用它的format方法得到新的日期的String类型。由于它默认的格式是我们期望的类型,我们返回它的toString()方法也会得到同样的结果。

    如果从解决前面这个问题的角度来说,可以说到这里就结束了。既然Java 8引入了新的date time类型,顺便也做一个小小的总结吧。

 

LocalDate

    我们可能接触的最多的类型就是LocalDate了,它和前面的Date类型不一样,本身是immutable的,这意味着它是线程安全的。每次我们调用一些方法,比如前面的minusDays()方法,它实际上是返回一个新的对象而不是修改原有对象本身。

 

创建LocalDate对象:

由于创建LocalDate对象没有构造函数方法,所有创建改对象必须通过工厂方法的方式,一种方法如下:

 

LocalDate date = LocalDate.of(2016, 2, 1);

  当然,还有一种就是我们前面通过parse方法解析字符串的方式,这种方式在后面会详细讨论。

 

访问年,月,日

int year = date.getYear();
Month month = date.getMonth();
int mon = date.getMonthValue();
int day = date.getDayOfMonth();
day = date.getDayOfYear();
DayOfWeek dow = date.getDayOfWeek();

    上述这些是一些比较常用的访问年月日的方法,比如我们需要访问某个月,如果期望是返回数字的话,可以使用date.getMonthValue()方法,而使用getMonth()方法返回的是一个Month类型,当然Month类型也提供有返回对应月份数值的方法。返回具体某一日的方法有getDayOfMonth, getDayOfYear()以及getDayOfWeek。

    由于LocalDate的api比较灵活,如果我们相用一个看起来更加统一的方式来访问年、月和日的话,也有如下的办法:

int year = date.get(ChronoField.YEAR);
int mon = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);

     其中的ChronoField是一个enum类型,里面列举了各种日期相关的值。

 

修改年、月、日

    和前面获取年月日相关,LocalDate里也有增加或者减少年月日的api,比如:

 

date.plusDays(10);
date.plusMonths(10);
date.plusWeeks(10);
date.plusYears(10);

    也有一种比较通用的写法就是:

 

date.plus(10, ChronoUnit.DAYS);
date.plus(10, ChronoUnit.MONTHS);

    相应的,减少日期的api有如下:

 

date.minusDays(10);
date.minusMonths(10);
date.minusWeeks(10);
date.minusYears(10);
date.minus(10, ChronoUnit.YEARS);

 

字符串解析

    在前面的示例里已经看到过一种解析的办法,就是LocalDate.parse()。这种方法默认的输入String类型是"yyyy-mm-dd"样式的。但是对于我们不同的格式需要,比如2015/02/11这种类型的该怎么处理呢?实际上这里还有一个方法就是LocalDate.parse(String, DateTimeFormatter)。由DateTimeFormatter来设定字符串的格式。

    所以对于上述格式的解析可以采用如下的方式:

 

LocalDate.parse("2016/02/01", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    实际上,因为国际上有不同表示日期的格式,在DateTimeFormatter里也提供了很多种类型的参数,比如BASIC_ISO_DATE, ISO_LOCAL_DATE, ISO_DATE等。因为这里定义了不同日期类型的格式,所以对于有些字符串的解析可以采用类似如下的方式:
date1 = LocalDate.parse("20140319", DateTimeFormatter.BASIC_ISO_DATE);

    既然是牵涉到日期和字符串之间的解析转换,这里肯定就牵涉到他们格式的设定。从String类型到LocalDate类型的转换需要 设定DateTimeFormatter,而从LocalDate转换成字符串呢?前面已经说到过,LocalDate默认的输出字符串类型是"yyyy-mm-dd"格式的。那么要将它的输出设定为我们指定的格式,也需要用到DateTimeFormatter。

    

String str = date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd");

   出了上述定义的DateTimeFormatter来定义日期的格式,还有一种办法就是使用DateTimeFormatterBuilder。它构建一个DateTimeFormatter的方式类似于一个builder模式。一个典型的用法如下:

DateTimeFormatter formatter1 = new DateTimeFormatterBuilder()
    			.appendText(ChronoField.DAY_OF_MONTH)
    			.appendLiteral(".")
    			.appendText(ChronoField.MONTH_OF_YEAR)
    			.appendLiteral(" ")
    			.appendText(ChronoField.YEAR)
    			.parseCaseInsensitive()
    			.toFormatter(Locale.CHINA);

System.out.println(date1.format(formatter1));

   运行程序的话将得到如下的输出: 

    

20.三月 2014

   

LocalTime

    有了前面对LocalDate的总结之后,我们再来看LocalTime的部分就很简单了。它默认的格式是"hh:mm:ss"。所以一些典型的方法如下:

 

LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
time = LocalTime.parse("13:45:20");
time = LocalTime.parse("21/45/20", DateTimeFormatter.ofPattern("HH/mm/ss"));

 

JodaTime

    在最开始的问题中,如果采用java 8的LocalDate的话,需要将系统升级到jdk8才行。对于有的存在具体困难的情况下,也可以用第三方的库。最典型的就是JodaTime。实际上java8里面time包的很多实现就是基于这个库的移植。

    JodaTime库的依赖定义如下:

    

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.9.1</version>
</dependency>

    采用它的实现和我们前面的方式非常接近,具体的实现方法如下:

String yesterday(String checkOut) {
    DateTimeFormatter formatter =
           ISODateTimeFormat.yearMonthDay().withZoneUTC();
    DateTime time = DateTime.parse(checkOut, formatter).minusDays(1);
    return time.toString(formatter);
}

    由于比较简单,就不详细解释了。

 

总结

     Java 从1.1开始对日期和时间的类库支持就不够好,一直到java 8的出现才有所好转。在新的java 8的类库里,重点的两个类分别就是LocalDate, LocalTime。他们分别表示日期和时间。一个包含的元素有年月日,一个表示的是时分秒。他们都是immutable的类型。构造的方式都是采用of方法或者采用parse方法解析字符串。而且现在对于时间的各种加减操作也非常方便了。里面提供的plusxx, minusxx就是专门为这些服务。对于具体解析和格式转换他们都是通过DateTimeFormatter,里面定义的各种日期和时间格式都是为了方便我们的使用。

 

参考材料

java 8 in action

http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

猜你喜欢

转载自shmilyaw-hotmail-com.iteye.com/blog/2268659