Java8 日期 API 業務使用

最近在作帳單結算業務,須要根據客戶選擇的結算方式來推算下一次結算日期以及該次結算日期段。java

推算日期這樣的業務直男君之前就寫過,只不過使用的是熟悉的 java.util.date 和 java.util.Calendar。如今公司使用的 JDK8,因此本次就決定新的日期 API 啦,順便結合業務實現對比回顧下。sql

 
直男君水平有限,沒法從原理上洋灑,只能從業務開發角度分爲這麼幾塊講:
  • Java8 前喜聞樂見的日期操做
  • 爲何推薦用新的日期 API
  • 新 API 的典型使用
  • 兩種業務場景實現

但願能幫到各位道友,囉嗦結束,開始!安全

之前喜聞樂見的日期操做

熟悉的日期操做三基友:java.util 包下的 Date 和 Calendar 加上 java.text.SimpleDateFormat。架構

1)獲得當前日期對象app

//獲取日期對象(包含時間)
Date date = new Date();
Date date1 = new Date(System.currentTimeMillis());

2)日曆操做ui

 //獲取日曆對象
Calendar cald = Calendar.getInstance();
cald.setTime(date); //目標日期對象對應的日曆
cald.add(Calendar.MONTH, 1); //日曆選取(下個月本號)
Date date2 = cald.getTime(); //目標日曆對應的日期對象

3)日期格式化線程

//日期格式化
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowDateStr = df.format(now); //日期 >> 格式串
System.out.println(nowDateStr); //2019-05-28 20:11:11
String dateStr = "2019-05-28 20:33:33";
Date aimDate = df.parse(dateStr); //格式串 >> 日期
System.out.println(aimDate);
System.out.println(aimDate.after(now)); //日期比較
System.out.println(aimDate.compareTo(now));

爲何推薦 Java8 日期 API (爲啥用的爽)

1)更清晰合理的語義和架構設計

再也不使用 Date(util 和 sql 包都有) 表示日期、時間、日期時間,而是 LocalDate、LocalTime、LocalDateTime。orm

之前日期格式化的類是在 java.text 包下,如今所有在 java.time 下,且語義分工明確。對象

  • java.time 最經常使用的基礎類 LocalDate、LocalTime、LocalDateTime、Instant、Period、Duration 等。
  • java.time.format 日期格式化的類在這裏,固然基礎類已經提供了相關方法。
  • java.time.zone 時區支持
  • java.time.xxx 等

2)線程安全的設計

SimpleDataFormat 類一直爲人詬病的線程安全問題:

//SimpleDataFormat 源碼部分↓
private StringBuffer format(Date date, StringBuffer toAppendTo,
 FieldDelegate delegate) {
 // Convert input date to time field list
 calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
......

PS:直男君倒以爲沒什麼,避免單例或者線程共享的場景就好了。

新的 API 類都是不可變類,避免了線程安全隱患。

public final class LocalDateTime
public final class Instant
......

PS:什麼叫不可變類?

String 類就是不可變類,因此有這樣的說法:每次使用 + 鏈接字符串都會生成新的 String 對象,對於重複拼接的場景應該使用 StringBuilder/StringBuffer。

參考 String 類的設計,可總結不可變類的關鍵特色:類名 final,保證類不會被拓展;全部成員變量 final 且不提供任何能夠修改實例狀態的方法。

3)更方便的日期操做

基礎類好比 LocalDateTime 就已經合理的提供了足夠多的場景方法,日期調整、格式化、比較等等。

4)時區、日曆系統支持(體會還不深)

5)其餘

新 API 典型使用

1)獲取日期對象

提供了各類靜態方法構造日期時間對象,以 LocalDate 爲例:

		LocalDate date = LocalDate.now();
		System.out.println(date);
		date = LocalDate.of(2019, 5, 30);//LocalDate.of(2019, Month.MAY, 30)
		System.out.println(date);
		date = LocalDate.ofYearDay(2019, 300);
		System.out.println(date);
 //其餘

2)日期時間比較

		LocalDateTime now = LocalDateTime.now();
		logger.info("now: {}", now);
		LocalDate date = LocalDate.of(2017, 7, 27);
		LocalTime time = LocalTime.of(17, 11);
		LocalDateTime aim = LocalDateTime.of(date, time);
		logger.info("aim: {}", aim);
		System.out.println(now.compareTo(aim));
		System.out.println(now.isBefore(aim));
		System.out.println(now.isEqual(aim));
		System.out.println(now.isAfter(aim));

3)格式化

		//DateTimeFormatter 提供了不少內置格式,但好像都不是咱們想要的
		DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
		LocalDateTime now = LocalDateTime.now();
		String str = now.format(formatter);
		System.out.println(str);
		//咱們想要的格式
		formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
		System.out.println(now.format(formatter));
		//日期字符串轉日期對象
		String dateStr = "2019-05-30";
		LocalDate aim = LocalDate.parse(dateStr);
		System.out.println(aim);
		//日期時間串一塊兒轉對象
		String dateTimeStr = "2019-05-30 10:30:00";
		LocalDateTime aim2 = LocalDateTime.parse(dateTimeStr, formatter);
		System.out.println(aim2);

4)日曆操做(日期調整)

這個是業務實現上最有用的。

再也不像之前使用 Calendar,而是基礎類搭配 TemporalAdjusters 很好用!

		//今天
		LocalDateTime now = LocalDateTime.now();
		System.out.println(now);
		//分別獲取今天日曆值 也可使用 ChronoField 的常量指定獲取
		logger.info("今天是,本月{}號, 周{}, 今年的第{}天, 今年的第{}月", 
				now.getDayOfMonth(), now.getDayOfWeek().getValue(), now.getDayOfYear(), now.getMonthValue());
		
		//明年今天
		LocalDateTime aim = now.plusYears(1);
		System.out.println(aim);
		//下月今天
		aim = now.plusMonths(1);
		System.out.println(aim);
		//下週今天
		aim = now.plusWeeks(1);
		System.out.println(aim);
		
		//本月一號
		aim = now.with(TemporalAdjusters.firstDayOfMonth());
		System.out.println(aim);
		//本月最後一天
		aim = now.with(TemporalAdjusters.lastDayOfMonth());
		System.out.println(aim);
		//...

5)時長對象使用

  • java.time.Period 日期時長
  • java.time.Duration 時間時長
		//如今
		LocalDateTime now = LocalDateTime.now();
		System.out.println(now);
		//3個月時長
		Period months3 = Period.ofMonths(3);
		//3個月後
		LocalDateTime aim = now.plus(months3);
		System.out.println(aim);
		//2個小時時長
		Duration hours2 = Duration.ofHours(2);
		//2個小時後
		aim = now.plus(hours2);
		System.out.println(aim);

6)兼容舊 API

當系統升級 JDK8 ,很容易將遺留的 Date 過渡爲新 API 類使用,使用 java.time.Instant。

		//Date >> LocalDate(Time)
		Date date = new Date();
		LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
		System.out.println(localDateTime);
		//Calendar >> LocalDate
		Calendar calen = Calendar.getInstance();
		localDateTime = LocalDateTime.ofInstant(calen.toInstant(), ZoneId.systemDefault());
		System.out.println(localDateTime);

兩種業務場景實現

1)定投業務

這個支付寶和各大銀行都有的業務,即獲得客戶容許,按照客戶選擇的方式(按月或按周)投入本金至餘額寶或其餘理財產品,通常投入動做工做日進行,節假日順延。

		/**
		 * 模擬客戶輸入
		 */
		//假設客戶選擇 按月,每個月5號定投
		Integer type = 1, day = 5; 
		//前置校驗 日期號檢查 略
		ActionType aimType = ActionType.ordinalContain(type);
		if(aimType==null) {
			throw new RuntimeException("定投類型輸入不合法!");
		}
		/**
		 * 業務處理
		 */
		LocalDate now = LocalDate.now(); //當天日期
		LocalDate aimDate = LocalDate.now(); //結果日期
		switch (aimType) {
		case T_WEEK: //按周,假設每週5
			aimDate = now.with(ChronoField.DAY_OF_WEEK, day); //本週5
			if(!now.isBefore(aimDate)) { //若是當天>=本週5,取下週5
				aimDate = aimDate.plusWeeks(1);
			}
			break;
		case T_MONTH: //按月,假設每個月5號
			aimDate = now.withDayOfMonth(day);//本月5號
			if(!now.isBefore(aimDate)) { //若是當天>=本月5號,取下月5號
				aimDate = aimDate.plusMonths(1);
			}
			break;
		default:
			break;
		}
		/**
		 * 輸出結果
		 */
		DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
		String aimDateStr = aimDate.format(formatter);
		//節假日順延須要有工做日曆藍本,此處略
		logger.info("下次投入日期:{}", aimDateStr);

2)帳單業務

這個也很常見,好比每個月幾號結算上個月/季度的帳單(分成,繳費啥的)。關鍵一點和定投場景不一樣的是,老是下個月開始動做,而定投會根據當前日期比對。

		/**
		 * 模擬客戶輸入
		 */
		//假設客戶選擇 按月,每個月5號結算上個月的帳單
		Integer type = 1, day = 5; 
		//前置校驗 日期號檢查 略
		ActionType aimType = ActionType.ordinalContain(type);
		if(aimType==null) {
			throw new RuntimeException("結算類型輸入不合法!");
		}
		/**
		 * 業務處理
		 */
		LocalDate now = LocalDate.now(); //當天日期 6.5
		//結算日期 下個月5號(7.5) or 三個月後的的5號(9.5)
		LocalDate aimDate = now.withDayOfMonth(day).plusMonths(aimType.monthPeriod);
		//帳單起始日 6.1
		//帳單結束日 6.30 or 8.31
		LocalDate startDate = now.with(TemporalAdjusters.firstDayOfMonth()); 
		LocalDate endDate = aimDate.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
		/**
		 * 輸出結果
		 */
		DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
		logger.info("下次結算日期:{},帳單週期段:[{}, {}]", aimDate.format(formatter), 
				startDate.format(formatter), endDate.format(formatter));

結語

最近忙於業務開發,陸陸續續總算總結完了,寫點題外話。

小夥伴們應該發現了,直男君的文章都是按需發的,一來記錄下本身碰到的有所真正實踐的點,但願職友碰到這些場景能夠有所共鳴;二來培養梳理表達能力並但願獲得溝通成長。

因此,文章都是淺顯直白的,沒有高深的東西(職場實踐向,直男君尚未大宗師那樣的火候...)。

打完手工,歡迎踩點吐槽,收藏轉發交流~

相關文章
相關標籤/搜索