LocalDate
分類分工
java.time.LocalDate ->只對年月日做出處理
java.time.LocalTime ->只對時分秒納秒做出處理
java.time.LocalDateTime ->同時可以處理年月日和時分秒
優點
除了使用起來更加簡單和靈活,主要是傳統的時期處理類Date、Calendar不是多線程安全的,而LocalDate 線程安全的,所以不用擔心併發問題。
實際使用
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Lists;
/**
*
*
* Java 8 的時間工具類
*/
public class DateUtils {
/**
* 默認使用系統當前時區
*/
private static final ZoneId ZONE = ZoneId.systemDefault();
private static final String DATE_FORMAT = "yyyy-MM-dd";
private static final String DATE_FORMAT_DS = "yyyyMMdd";
private static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";
private static final String TIME_FORMAT = "yyyyMMddHHmmss";
private static final String REGEX = "\\:|\\-|\\s";
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 獲取當前時間
*
* @param format
* @return
*/
public static String getCurrentTime(String format) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDateTime now = LocalDateTime.now();
return now.format(dateTimeFormatter);
}
/**
* 獲取昨日時間
*
* @param format
* @return
*/
public static String getYesterday(String format) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate nowDate = LocalDate.now();
LocalDate yesterday = nowDate.minusDays(1);
return yesterday.format(dateTimeFormatter);
}
/**
* 獲取上週的時間
*
* @param format
* @return
*/
public static String getLastWeek(String format) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate nowDate = LocalDate.now();
LocalDate lastWeek = nowDate.minusWeeks(1);
return lastWeek.format(dateTimeFormatter);
}
/**
* 獲取上個月的時間
*
* @param format
* @return
*/
public static String getLastMonth(String format) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate nowDate = LocalDate.now();
LocalDate lastMonth = nowDate.minusMonths(1);
return lastMonth.format(dateTimeFormatter);
}
/**
* 獲取去年的時間
*
* @param format
* @return
*/
public static String getLastYear(String format) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate nowDate = LocalDate.now();
LocalDate lastYear = nowDate.minusYears(1);
return lastYear.format(dateTimeFormatter);
}
/**
* 獲取前多少天的日期
*
* @param format
* @param num
* @return
*/
public static String getBeforeSomeDay(String format, int num) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate nowDate = LocalDate.now();
LocalDate beforeDay = nowDate.minusDays(num);
return beforeDay.format(dateTimeFormatter);
}
/**
* 獲取指定時間的前多少天
*
* @param format
* @param date
* @param num
* @return
*/
public static String getBeforeDayOfDate(String format, String date, int num) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
LocalDate beforeDay = localDate.minusDays(num);
return beforeDay.format(dateTimeFormatter);
}
/**
* 獲取當天的開始時間 yyyy-MM-dd 00:00:00
*
* @param format
* @return
*/
public static String getDayStartTime(String format, String date) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
LocalDateTime toDayStart = LocalDateTime.of(localDate, LocalTime.MIN);
return toDayStart.format(FORMATTER);
}
/**
* 獲取當天的結束時間 yyyy-MM-dd 23:59:59
*
* @param format
* @return
*/
public static String getDayEndTime(String format, String date) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
LocalDateTime toDayStart = LocalDateTime.of(localDate, LocalTime.MAX);
return toDayStart.format(FORMATTER);
}
/**
* 獲取兩個時間之間的間隔天數
*
* @param startDate yyyyMMdd
* @param endDate yyyyMMdd
* @return
*/
public static long getRangeCountOfDate(String startDate, String endDate) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT_DS);
LocalDate startLocalDate = LocalDate.parse(startDate, dateTimeFormatter);
LocalDate endLocalDate = LocalDate.parse(endDate, dateTimeFormatter);
long count = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);
return count;
}
/**
* 後期兩個時間之間的所有日期 【包含開始時間和結束時間】
*
* @param startDate yyyyMMdd
* @param endDate yyyyMMdd
* @return
*/
public static List<String> getRangeOfDate(String startDate, String endDate) {
List<String> range = Lists.newArrayList();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT_DS);
LocalDate startLocalDate = LocalDate.parse(startDate, dateTimeFormatter);
LocalDate endLocalDate = LocalDate.parse(endDate, dateTimeFormatter);
long count = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);
if (count < 0) {
return range;
}
range = Stream.iterate(startLocalDate, d -> d.plusDays(1)).limit(count + 1).map(
s -> s.format(dateTimeFormatter)).collect(Collectors.toList());
return range;
}
public static void main(String[] args) {
System.out.println(getRangeOfDate("20191010", "20191020"));
}
}
參考:https://blog.csdn.net/wusd1256/article/details/102934354
Date線程不安全的原因
1- SimpleDateFormat線程安全問題
使用ExecutorService提交多個任務的方式,模擬併發環境將字符串轉換爲日期即測試parse方法,代碼如下:
@Test
public void testParse() {
ExecutorService executorService = Executors.newCachedThreadPool();
List<String> dateStrList = Lists.newArrayList(
"2018-04-01 10:00:01",
"2018-04-02 11:00:02",
"2018-04-03 12:00:03",
"2018-04-04 13:00:04",
"2018-04-05 14:00:05"
);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for (String str : dateStrList) {
executorService.execute(() -> {
try {
simpleDateFormat.parse(str);
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
運行後,報錯如下:
可見併發環境下使用SimpleDateFormat的parse方法有線程安全問題!
線程安全問題的原因:
在SimpleDateFormat轉換日期是通過Calendar對象來操作的,SimpleDateFormat繼承DateFormat類,DateFormat類中維護一個Calendar對象,代碼如下:
通過DateFormat類中的註釋可知:此處Calendar實例被用來進行日期-時間計算,既被用於format方法也被用於parse方法!
在parse方法的最後,會調用CalendarBuilder的establish方法,入參就是SimpleDateFormat維護的Calendar實例,在establish方法中會調用calendar的clear方法,如下:
可知SimpleDateFormat維護的用於format和parse方法計算日期-時間的calendar被清空了,如果此時線程A將calendar清空且沒有設置新值,線程B也進入parse方法用到了SimpleDateFormat對象中的calendar對象,此時就會產生線程安全問題!
2- 解決方案
**每一個使用SimpleDateFormat對象進行日期-時間進行format和parse方法的時候就創建一個新的SimpleDateFormat對象,用完就銷燬即可!**代碼如下:
/**
* 模擬併發環境下使用SimpleDateFormat的parse方法將字符串轉換成Date對象
*/
@Test
public void testParseThreadSafe() {
ExecutorService executorService = Executors.newCachedThreadPool();
List<String> dateStrList = Lists.newArrayList(
"2018-04-01 10:00:01",
"2018-04-02 11:00:02",
"2018-04-03 12:00:03",
"2018-04-04 13:00:04",
"2018-04-05 14:00:05"
);
for (String str : dateStrList) {
executorService.execute(() -> {
try {
//創建新的SimpleDateFormat對象用於日期-時間的計算
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
simpleDateFormat.parse(str);
TimeUnit.SECONDS.sleep(1);
simpleDateFormat = null; //銷燬對象
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
在運行可以發現不會報出之前的錯誤了!
綜上所述,使用SimpleDateFormat對象進行日期-時間計算時,如果SimpleDateFormat是多個線程共享的就會有線程安全問題!應該讓每一個線程都有一個獨立的SimpleDateFormat對象用於日期-時間的計算!此時就可以使用ThreadLocal將SimpleDateFormat綁定到線程上,是的該線程上的日期-時間計算順序的使用SimpleDateFormat對象,這樣也可以避免線程安全問題!
另外就是使用LocalDateTime
LocalDateTime now = LocalDateTime.of(LocalDate.now(), LocalTime.now());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String dateStr = now.format(fmt);