本文工作
本文记录了如何使用JDK8基于DateTimeFormatter和LocalDateTime的工具类JDK8DateUtils取代旧版的基于SimpleDateFormat和Date的工具类DateUtils,在此过程中使用JUnit和Assert作为工具进行平滑过渡升级,将代码中调用DateUtils方法之处全部更换为JDK8DateUtils中的同功能方法。
为什么要使用JDK8的日期特性
引用《码出高效-阿里巴巴开发手册V1.4.0》中的解释:
来自廖雪峰的点评:
Java8新增了LocalDate和LocalTime接口,为什么要搞一套全新的处理日期和时间的API?因为旧的java.util.Date实在是太难用了。
java.util.Date月份从0开始,一月是0,十二月是11,变态吧!java.time.LocalDate月份和星期都改成了enum,就不可能再用错了。
java.util.Date和SimpleDateFormatter都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,如果你只想用java.util.Date存储日期,或者只存储时间,那么,只有你知道哪些部分的数据是有用的,哪些部分的数据是不能用的。在新的Java 8中,日期和时间被明确划分为LocalDate和LocalTime,LocalDate无法包含时间,LocalTime无法包含日期。当然,LocalDateTime才能同时包含日期和时间。
新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。
Old DateUtils
package com.jake.manager.util;
import com.jake.manager.constant.DateConstants;
import com.jake.manager.constant.TimeConstants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* only for testing whether JDK8DateUtils works
* @deprecated
*/
class DateUtils {
private DateUtils() {
}
private static final Logger logger = LoggerFactory.getLogger(DateUtils.class);
/**
* @return 获取当前日期yyyy-MM-dd
*/
static String getCurrentDateStr() {
return new SimpleDateFormat(DateConstants.YEAR_MONTH_DAY).format(new Date());
}
/**
* @return 获取当前时间的整点时间字符串yyyy-MM-dd HH:00
*/
static String getCurrentZeroMinuteDateTimeStr() {
String currentMinuteTime = new SimpleDateFormat(DateConstants.YEAR_MONTH_DAY_HOUR_MINUTE)
.format(new Date());
String[] splitTime = StringUtils.split(currentMinuteTime, ":");
return splitTime[0] + ":00";
}
/**
* @param time 传入时间参数yyyy-MM-dd HH:mm
* @return 返回传入时间的整点时间
*/
static String getZeroMinuteDateTimeStr(String time) {
return getHourByAddHour(time, 0);
}
/**
* @param time 传入时间为查询起始时间,参数yyyy-MM-dd HH:mm
* @return yyyy-MM-dd HH:00 返回查询起始时间对应的合理的整点时间:若起始时间为整点,则返回该整点时间;
* 若不为整点,则返回下一个整点时间。
*/
static String getStartZeroMinuteDateTimeStr(String time) {
return getHourByAddHour(time, 1);
}
private static String getHourByAddHour(String time, int add) {
if (!StringUtils.endsWith(time, "00")) {
String[] splitTime = StringUtils.split(time, " ");
String[] splitTime2 = StringUtils.split(splitTime[1], ":");
int addedHour = Integer.parseInt(splitTime2[0]) + add;
String joint = addedHour < 10?" 0": " ";
time = splitTime[0] + joint + addedHour + ":00";
}
return time;
}
/**
* @param startDateTime 开始时间(格式为字符串yyyy-MM-dd)
* @param endDateTime 结束时间(格式为字符串yyyy-MM-dd)
* @param calUnit 间隔单位,Calendar类里的常量,年月周日时分秒
* @param interval 间隔大小,每隔多久获取一次时间
* @return 返回时间集合(格式为字符串yyyy-MM-dd)
*/
static Set<String> getBetweenDateStrsEveryDay(String startDateTime, String endDateTime, int calUnit, int interval) {
Set<String> dateTimes = new TreeSet<>(Comparator.naturalOrder());
String startMinuteTime = startDateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
String endMinuteTime = endDateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
Set<String> minuteTimes = getBetweenMinuteDateTimeStrsEveryHour(startMinuteTime, endMinuteTime, calUnit, interval);
for (String minuteTime : minuteTimes) {
String[] split = minuteTime.split(" ");
dateTimes.add(split[0]);
}
return dateTimes;
}
/**
* @param startTime 开始时间(格式为字符串yyyy-MM-dd HH:mm)
* @param endTime 结束时间(格式为字符串yyyy-MM-dd HH:mm)
* @param calUnit 间隔单位,Calendar类里的常量,年月周日时分秒
* @param interval 间隔大小,每隔多久获取一次时间
* @return 返回时间集合(格式为字符串yyyy-MM-dd HH:mm)
*/
static Set<String> getBetweenMinuteDateTimeStrsEveryHour(String startTime, String endTime, int calUnit, int interval) {
SimpleDateFormat sdfMinute = new SimpleDateFormat(DateConstants.YEAR_MONTH_DAY_HOUR_MINUTE);
Set<String> times = new TreeSet<>(Comparator.naturalOrder());
try {
Date startDate = sdfMinute.parse(startTime);
Date endDate = sdfMinute.parse(endTime);
Calendar startCal = Calendar.getInstance();
startCal.setTime(startDate);
times.add(startTime);
while (startCal.getTime().before(endDate)) {
startCal.add(calUnit, interval);
times.add(sdfMinute.format(startCal.getTime()));
}
} catch (ParseException e) {
logger.error(e.getMessage());
}
return times;
}
/**
* @param dateTime yyyy-MM-dd
* @return 返回当前日期的下一天 yyyy-MM-dd
*/
static String getNextDayDateStr(String dateTime) {
String minuteTime = dateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
String[] split = getOtherTimeByInterval(minuteTime, Calendar.DATE, 1).split(" ");
return split[0];
}
/**
* @param dateTime yyyy-MM-dd
* @return 返回当前日期的前一天 yyyy-MM-dd
*/
static String getLastDayDateStr(String dateTime) {
String minuteTime = dateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
String[] split = getOtherTimeByInterval(minuteTime, Calendar.DATE, -1).split(" ");
return split[0];
}
/**
* @return 获取当前时间对应的上一个小时的整点时间,
* 如传参 "2019-03-26 19:58",返回"2019-03-26 18:00"
* 传参 "2019-03-26 19:00",返回"2019-03-26 18:00"
*/
static String getLastZeroMinuteDateTimeStr(String time) {
String lastTime = getOtherTimeByInterval(time, Calendar.HOUR_OF_DAY, -1);
return getZeroMinuteDateTimeStr(lastTime);
}
/**
* @return 获取当前时间对应的下一个小时的整点时间,
*/
static String getNextZeroMinuteDateTimeStr(String time) {
String nextTime = getOtherTimeByInterval(time, Calendar.HOUR_OF_DAY, 1);
return getZeroMinuteDateTimeStr(nextTime);
}
/**
* @param time 传入时间
* @param calUnit 间隔单位,如:Calendar.MINUTE
* @return 获取与传入时间相隔N个时间单位的另一个时间
*/
private static String getOtherTimeByInterval(String time, int calUnit, int interval) {
SimpleDateFormat sdfMinute = new SimpleDateFormat(DateConstants.YEAR_MONTH_DAY_HOUR_MINUTE);
String otherTime = "";
Calendar calendar = Calendar.getInstance();
try {
Date date = sdfMinute.parse(time);
calendar.setTime(date);
calendar.add(calUnit, interval);
Date lastDate = calendar.getTime();
otherTime = sdfMinute.format(lastDate);
} catch (ParseException e) {
logger.error(e.getMessage());
}
return otherTime;
}
/**
* @param startDateTime 开始时间(格式为字符串yyyy-MM-dd)
* @param endDateTime 结束时间(格式为字符串yyyy-MM-dd)
* @return 获取间隔天数
*/
static long countDaysBetweenDateStrs(String startDateTime, String endDateTime) {
String startMinuteTime = startDateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
String endMinuteTime = endDateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
return countDaysBetweenMinuteDateTimeStrs(startMinuteTime, endMinuteTime);
}
/**
* @param startTime 开始时间(格式为字符串yyyy-MM-dd HH:mm)
* @param endTime 结束时间(格式为字符串yyyy-MM-dd HH:mm)
* @return 获取间隔天数
*/
static long countDaysBetweenMinuteDateTimeStrs(String startTime, String endTime) {
SimpleDateFormat sdfMinute = new SimpleDateFormat(DateConstants.YEAR_MONTH_DAY_HOUR_MINUTE);
long days = -1024;
if (StringUtils.compare(startTime, endTime) < 0) {
try {
Date startDate = sdfMinute.parse(startTime);
Date endDate = sdfMinute.parse(endTime);
days = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24);
} catch (ParseException e) {
logger.error(e.getMessage());
}
}
return days;
}
/**
* @param dateTime yyyy-MM-dd
* @param days 负数代表传入日期几天前的日期,正数代表传入日期几天后的日期。
* @return 得到与当前时间相距几天的时间 yyyy-MM-dd
*/
static String getDateStrDaysApart(String dateTime, int days) {
String minuteTime = dateTime + TimeConstants.HOUR_MINUTE_SUFFIX;
String addedMinuteTime = getMinuteDateTimeStrDaysApart(minuteTime, days);
String[] splitMinuteTime = StringUtils.split(addedMinuteTime, " ");
return splitMinuteTime[0];
}
/**
* @param time 格式为字符串yyyy-MM-dd HH:mm
* @param days 负数代表传入日期几天前的日期,正数代表传入日期几天后的日期。
* @return 得到与当前时间相距几天的时间 yyyy-MM-dd HH:mm
*/
static String getMinuteDateTimeStrDaysApart(String time, int days) {
SimpleDateFormat sdfMinute = new SimpleDateFormat(DateConstants.YEAR_MONTH_DAY_HOUR_MINUTE);
Date addedDate = null;
try {
Calendar calendar = Calendar.getInstance();
Date date = sdfMinute.parse(time);
calendar.setTime(date);
calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + days);
addedDate = calendar.getTime();
} catch (ParseException e) {
logger.error(e.getMessage());
}
return sdfMinute.format(addedDate);
}
}
JDK8 DateUtils
此工具类中的方法实现了与旧版DateUtils同名方法完全相同的功能,即两个类的同名方法入参相同的情况下,返回值也相同。
package com.jake.manager.util;
import org.apache.commons.lang3.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class JDK8DateUtils {
private JDK8DateUtils() {
}
private static final String YEAR_MONTH_DAY_HOUR_MINUTE = "yyyy-MM-dd HH:mm";
private static final String YEAR_MONTH_DAY_HOUR_ZERO = "yyyy-MM-dd HH:00";
private static DateTimeFormatter dtfMinute = DateTimeFormatter.ofPattern(YEAR_MONTH_DAY_HOUR_MINUTE);
private static DateTimeFormatter dtfZeroMinute = DateTimeFormatter.ofPattern(YEAR_MONTH_DAY_HOUR_ZERO);
/**
* @return 获取当前日期yyyy-MM-dd
*/
public static String getCurrentDateStr() {
return LocalDate.now().toString();
}
/**
* @return 获取当前时间的整点时间字符串yyyy-MM-dd HH:00
*/
public static String getCurrentZeroMinuteDateTimeStr() {
return dtfZeroMinute.format(LocalDateTime.now());
}
/**
* @param minuteDateTimeStr 传入时间参数yyyy-MM-dd HH:mm
* @return 返回传入时间的整点时间
*/
public static String getZeroMinuteDateTimeStr(String minuteDateTimeStr) {
return dtfZeroMinute.format(LocalDateTime.parse(minuteDateTimeStr, dtfMinute));
}
/**
* @param minuteDateTimeStr 传入时间为查询起始时间,参数yyyy-MM-dd HH:mm
* @return yyyy-MM-dd HH:00 返回查询起始时间对应的合理的整点时间:若起始时间为整点,则返回该整点时间;
* 若不为整点,则返回下一个整点时间。
*/
public static String getStartZeroMinuteDateTimeStr(String minuteDateTimeStr) {
if (!StringUtils.endsWith(minuteDateTimeStr, ":00")) {
LocalDateTime currentDateTime = LocalDateTime.parse(minuteDateTimeStr, dtfMinute);
LocalDateTime nextDateTime = currentDateTime.plusHours(1);
return dtfZeroMinute.format(nextDateTime);
}
return minuteDateTimeStr;
}
/**
* @param startDateStr 开始时间(格式为字符串yyyy-MM-dd)
* @param endDateStr 结束时间(格式为字符串yyyy-MM-dd)
* @return 返回时间集合(格式为字符串yyyy-MM-dd)
*/
public static Set<String> getBetweenDateStrsEveryDay(String startDateStr, String endDateStr) {
Set<String> dateStrs = new TreeSet<>(Comparator.naturalOrder());
LocalDate startDate = LocalDate.parse(startDateStr);
LocalDate endDate = LocalDate.parse(endDateStr);
dateStrs.add(startDateStr);
while (startDate.isBefore(endDate)) {
startDate = startDate.plusDays(1);
dateStrs.add(startDate.toString());
}
return dateStrs;
}
/**
* @param startMinDateTimeStr 开始时间(格式为字符串yyyy-MM-dd HH:mm)
* @param endMinDateTimeStr 结束时间(格式为字符串yyyy-MM-dd HH:mm)
* @return 返回时间集合(格式为字符串yyyy-MM-dd HH:mm)
*/
public static Set<String> getBetweenMinuteDateTimeStrsEveryHour(String startMinDateTimeStr, String endMinDateTimeStr) {
Set<String> timeStrs = new TreeSet<>(Comparator.naturalOrder());
LocalDateTime startDateTime = LocalDateTime.parse(startMinDateTimeStr, dtfMinute);
LocalDateTime endDateTime = LocalDateTime.parse(endMinDateTimeStr, dtfMinute);
timeStrs.add(startMinDateTimeStr);
while (startDateTime.isBefore(endDateTime)) {
startDateTime = startDateTime.plusHours(1);
timeStrs.add(StringUtils.replace(startDateTime.toString(), "T", " "));
}
return timeStrs;
}
/**
* @param dateStr yyyy-MM-dd
* @return 返回当前日期的下一天 yyyy-MM-dd
*/
public static String getNextDayDateStr(String dateStr) {
return LocalDate.parse(dateStr).plusDays(1).toString();
}
/**
* @param dateStr yyyy-MM-dd
* @return 返回当前日期的前一天 yyyy-MM-dd
*/
public static String getLastDayDateStr(String dateStr) {
return LocalDate.parse(dateStr).minusDays(1).toString();
}
/**
* @param minuteDateTimeStr yyyy-MM-dd HH:mm
* @return 获取当前时间对应的上一个小时的整点时间,
* 如传参 "2019-03-26 19:58",返回"2019-03-26 18:00"
* 传参 "2019-03-26 19:00",返回"2019-03-26 18:00"
*/
public static String getLastZeroMinuteDateTimeStr(String minuteDateTimeStr) {
LocalDateTime lastMinuteDateTime = LocalDateTime.parse(minuteDateTimeStr, dtfMinute).minusHours(1);
return dtfZeroMinute.format(lastMinuteDateTime);
}
/**
* @param minuteDateTimeStr yyyy-MM-dd HH:mm
* @return 获取当前时间对应的下一个小时的整点时间,
*/
public static String getNextZeroMinuteDateTimeStr(String minuteDateTimeStr) {
LocalDateTime nextMinuteDateTime = LocalDateTime.parse(minuteDateTimeStr, dtfMinute).plusHours(1);
return dtfZeroMinute.format(nextMinuteDateTime);
}
/**
* @param startDateStr 开始时间(格式为字符串yyyy-MM-dd)
* @param endDateStr 结束时间(格式为字符串yyyy-MM-dd)
* @return 获取间隔天数
*/
public static long countDaysBetweenDateStrs(String startDateStr, String endDateStr) {
return ChronoUnit.DAYS.between(LocalDate.parse(startDateStr), LocalDate.parse(endDateStr));
}
/**
* @param startMinDateTimeStr 开始时间(格式为字符串yyyy-MM-dd HH:mm)
* @param endMinDateTimeStr 结束时间(格式为字符串yyyy-MM-dd HH:mm)
* @return 获取间隔天数
*/
public static long countDaysBetweenMinuteDateTimeStrs(String startMinDateTimeStr, String endMinDateTimeStr) {
return ChronoUnit.DAYS.between(LocalDateTime.parse(startMinDateTimeStr, dtfMinute),
LocalDateTime.parse(endMinDateTimeStr, dtfMinute));
}
/**
* @param dateStr yyyy-MM-dd
* @param days 负数代表传入日期几天前的日期,正数代表传入日期几天后的日期。
* @return 得到与当前时间相距几天的时间 yyyy-MM-dd
*/
public static String getDateStrDaysApart(String dateStr, int days) {
return LocalDate.parse(dateStr).plusDays(days).toString();
}
/**
* @param minuteDateTimeStr 格式为字符串yyyy-MM-dd HH:mm
* @param days 负数代表传入日期几天前的日期,正数代表传入日期几天后的日期。
* @return 得到与当前时间相距几天的时间 yyyy-MM-dd HH:mm
*/
public static String getMinuteDateTimeStrDaysApart(String minuteDateTimeStr, int days) {
return StringUtils.replace(LocalDateTime.parse(minuteDateTimeStr, dtfMinute).plusDays(days).toString(), "T", " ");
}
}
JUnit Assert for Code Upgrade
使用JUnit Assert进行JDK8DateUtils和DateUtils同名方法的返回值校验。注意仅调用静态方法的单元测试类不需要加入以下注解:
@RunWith(SpringRunner.class)
@SpringBootTest
JDK8DateUtilsTests
package com.jake.manager.util;
import org.junit.Test;
import java.util.Calendar;
import static org.junit.Assert.*;
public class JDK8DateUtilsTests {
@Test
public void getCurrentDateStr() {
assertEquals(DateUtils.getCurrentDateStr(), JDK8DateUtils.getCurrentDateStr());
}
@Test
public void getCurrentZeroMinuteDateTimeStr() {
assertEquals(DateUtils.getCurrentZeroMinuteDateTimeStr(), JDK8DateUtils.getCurrentZeroMinuteDateTimeStr());
}
@Test
public void getZeroMinuteDateTimeStr() {
String time = "2019-06-25 15:28";
assertEquals(DateUtils.getZeroMinuteDateTimeStr(time), JDK8DateUtils.getZeroMinuteDateTimeStr(time));
}
@Test
public void getStartZeroMinuteDateTimeStr() {
String time = "2019-06-25 16:32";
assertEquals(DateUtils.getStartZeroMinuteDateTimeStr(time), JDK8DateUtils.getStartZeroMinuteDateTimeStr(time));
}
@Test
public void getBetweenDateStrsEveryDay() {
String startDate = "2019-01-01";
String endDate = "2019-03-05";
String[] jdk7DateArray = DateUtils.getBetweenDateStrsEveryDay(startDate, endDate,
Calendar.DATE, 1).toArray(new String[0]);
String[] jdk8DateArray = JDK8DateUtils.getBetweenDateStrsEveryDay(startDate,
endDate).toArray(new String[0]);
assertArrayEquals(jdk7DateArray, jdk8DateArray);
}
@Test
public void getBetweenMinuteDateTimeStrsEveryHour() {
String startTime = "2019-01-01 12:00";
String endTime = "2019-02-01 16:00";
String[] jdk7TimeArray = DateUtils.getBetweenMinuteDateTimeStrsEveryHour(startTime, endTime,
Calendar.HOUR_OF_DAY, 1).toArray(new String[0]);
String[] jdk8TimeArray = JDK8DateUtils.getBetweenMinuteDateTimeStrsEveryHour(startTime,
endTime).toArray(new String[0]);
assertArrayEquals(jdk7TimeArray, jdk8TimeArray);
}
@Test
public void getNextDayDateStr() {
String date = "2019-06-26";
assertEquals(DateUtils.getNextDayDateStr(date), JDK8DateUtils.getNextDayDateStr(date));
}
@Test
public void getLastDayDateStr() {
String date = "2019-06-26";
assertEquals(DateUtils.getLastDayDateStr(date), JDK8DateUtils.getLastDayDateStr(date));
}
@Test
public void getLastZeroMinuteDateTimeStr() {
String minuteTime = "2019-06-26 10:49";
String zeroMinuteTime = "2019-06-26 10:00";
assertEquals(DateUtils.getLastZeroMinuteDateTimeStr(minuteTime),
JDK8DateUtils.getLastZeroMinuteDateTimeStr(minuteTime));
assertEquals(DateUtils.getLastZeroMinuteDateTimeStr(zeroMinuteTime),
JDK8DateUtils.getLastZeroMinuteDateTimeStr(zeroMinuteTime));
}
@Test
public void getNextZeroMinuteDateTimeStr() {
String minuteTime = "2019-06-26 10:49";
String zeroMinuteTime = "2019-06-26 10:00";
assertEquals(DateUtils.getNextZeroMinuteDateTimeStr(minuteTime),
JDK8DateUtils.getNextZeroMinuteDateTimeStr(minuteTime));
assertEquals(DateUtils.getNextZeroMinuteDateTimeStr(zeroMinuteTime),
JDK8DateUtils.getNextZeroMinuteDateTimeStr(zeroMinuteTime));
}
@Test
public void countDaysBetweenDateStrs() {
String startDate = "2019-02-01";
String endDate = "2019-06-27";
assertEquals(DateUtils.countDaysBetweenDateStrs(startDate, endDate),
JDK8DateUtils.countDaysBetweenDateStrs(startDate, endDate));
}
@Test
public void countDaysBetweenMinuteDateTimeStrs() {
String startTime = "2019-02-01 12:30";
String endTimeBefore = "2019-06-27 09:20";
String endTimeEqual = "2019-06-27 12:30";
String endTimeAfter = "2019-06-27 15:40";
assertEquals(DateUtils.countDaysBetweenMinuteDateTimeStrs(startTime, endTimeBefore),
JDK8DateUtils.countDaysBetweenMinuteDateTimeStrs(startTime, endTimeBefore));
assertEquals(DateUtils.countDaysBetweenMinuteDateTimeStrs(startTime, endTimeEqual),
JDK8DateUtils.countDaysBetweenMinuteDateTimeStrs(startTime, endTimeEqual));
assertEquals(DateUtils.countDaysBetweenMinuteDateTimeStrs(startTime, endTimeAfter),
JDK8DateUtils.countDaysBetweenMinuteDateTimeStrs(startTime, endTimeAfter));
}
@Test
public void getDateStrDaysApart() {
String date = "2019-06-27";
assertEquals(DateUtils.getDateStrDaysApart(date, 10),
JDK8DateUtils.getDateStrDaysApart(date, 10));
assertEquals(DateUtils.getDateStrDaysApart(date, -10),
JDK8DateUtils.getDateStrDaysApart(date, -10));
}
@Test
public void getMinuteDateTimeStrDaysApart() {
String minutetime = "2019-06-27 09:25";
assertEquals(DateUtils.getMinuteDateTimeStrDaysApart(minutetime, 10),
JDK8DateUtils.getMinuteDateTimeStrDaysApart(minutetime, 10));
assertEquals(DateUtils.getMinuteDateTimeStrDaysApart(minutetime, -10),
JDK8DateUtils.getMinuteDateTimeStrDaysApart(minutetime, -10));
}
}
单元测试全部通过后,在IntelliJ IDEA中使用全局替换快捷键Ctrl + Shift + R做DateUtils替换为JDK8DateUtils的类名替换,注意仅替换main source中的工具类方法调用处,然后修正一下Build报错处。由此,便完成了代码升级,项目中所有的日期相关逻辑都变为使用JDK8处理。