曾經我以為計算兩個日期之差很簡單,在給我的團隊成員分配任務時,也覺得就是調用一個方法的問題,可是當我發現結果老是不對時,才發現原來JDK 提供的API中根本沒有這樣的方法,我也很惱火,也怪不得不少牛人在部落格裡怒斥Java標準庫中對日期的處理晦澀不堪的現狀,想這樣的功能提供也是理所應當的,也就說明Date,Calendar中提供的日期處理的功能不夠強大,因為已經有開源(Joda,某個知名的Java開源類庫,在時間日期的處理上相比Java標準庫更加強大且易用,IBM的日期類庫中提供了強大的功能),各大論壇中對這個問題爭論很多,可是很多都是考慮的比較簡單,每個月按30天計算,可能你會說,計算的這麼精確嗎?答案是在一定的場合下是非常有必要的,在銀行,圖書館到期圖書計費,網路流量計費等,都是按照自然月來計算的,需要考慮的因素很多,而不是簡單的30天,我現在需要的場合是Baby Care這款軟體中,要計算Baby的年齡,就是xx years,xx months,xx days,因為很多人需要用這個看看小孩是否滿月,是否周歲等,開始為了簡單,我們按30天每月,後來有人反饋,計算方法不對,只好讓使用者選擇,是每月按30天計算,還是按自然月計算。
按每月30天計算,論壇中常討論的方法,,並且似乎也是沒有問題的,但是往往計算的結果有時會相差一天:
public long diffValue(Date date1, Date date2) { //return date1.getTime() / (24*60*60*1000) - date2.getTime() / (24*60*60*1000); return (date2.getTime() - date1.getTime() )/ 86400000; //用立即數,減少乘法計算的開銷 }
下面是正確的解法:
問題的關鍵是過濾掉時分秒,保留日期部分。乾的活象低通濾波器,濾掉高頻雜波,保留低頻訊號,/86400000,就是把時分秒全忽略掉了。
解法1:
這篇部落格對這個問題分析的比較透徹:http://blog.csdn.net/solomonxu/archive/2007/04/27/1587237.aspx
public long differ(Date date1, Date date2) { //return date1.getTime() / (24*60*60*1000) - date2.getTime() / (24*60*60*1000); return date2.getTime() / 86400000 - date1.getTime() / 86400000; //用立即數,減少乘法計算的開銷 }
解法2:
http://blog.csdn.net/rmartin/archive/2006/12/22/1452867.aspx;http://butunclebob.com/ArticleS.UncleBob.JavaDates;
把date1,date2都設定為同樣的時間,我曾經設定date1為00:00:00,date2為23:59:59秒,在非常情況下,1S之差也會導致計算的天數少1.
private int daysBetween(Date now, Date returnDate) { Calendar cNow = Calendar.getInstance(); Calendar cReturnDate = Calendar.getInstance(); cNow.setTime(now); cReturnDate.setTime(returnDate); setTimeToMidnight(cNow); setTimeToMidnight(cReturnDate); long todayMs = cNow.getTimeInMillis(); long returnMs = cReturnDate.getTimeInMillis(); long intervalMs = todayMs - returnMs; return millisecondsToDays(intervalMs); } private int millisecondsToDays(long intervalMs) { return (int) (intervalMs / (1000 * 86400)); } private void setTimeToMidnight(Calendar calendar) { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); }
解法3:
SimpleDateFormat myFormatter = new SimpleDateFormat("yyyy-MM-dd"); java.util.Date date= myFormatter.parse("2003-05-1"); java.util.Date mydate= myFormatter.parse("1899-12-30"); long day=(date.getTime()-mydate.getTime())/(24*60*60*1000); out.println(day);
上面的解法思想都一樣,都是忽略的時分秒,相比之下,第一種方法最為簡單,可是這些都是每月按30天計算的,自然月計算的方法系統API中沒有,總不至於為了使用這個方法,去引用開源的庫吧,但是這個問題的處理邏輯也是很複雜的,要考慮的因素很多,往往測試的時候,發現某種特例計算的結果不正確,煞為惱火,Java真不給力~~
基本上花了一下午的時間,去分析,然後畫了流程圖,寫成代碼,可能水平有限,方法雖然笨拙,但是還是能用的,如有不正確的地方,歡迎大家指正,也期待有更加簡單巧妙的方法。
相應代碼:
public static int[] getNeturalAge(Calendar calendarBirth,Calendar calendarNow) { int diffYears = 0, diffMonths, diffDays; int dayOfBirth = calendarBirth.get(Calendar.DAY_OF_MONTH); int dayOfNow = calendarNow.get(Calendar.DAY_OF_MONTH); if (dayOfBirth <= dayOfNow) { diffMonths = getMonthsOfAge(calendarBirth, calendarNow); diffDays = dayOfNow - dayOfBirth; if (diffMonths == 0) diffDays++; } else { if (isEndOfMonth(calendarBirth)) { if (isEndOfMonth(calendarNow)) { diffMonths = getMonthsOfAge(calendarBirth, calendarNow); diffDays = 0; } else { calendarNow.add(Calendar.MONTH, -1); diffMonths = getMonthsOfAge(calendarBirth, calendarNow); diffDays = dayOfNow + 1; } } else { if (isEndOfMonth(calendarNow)) { diffMonths = getMonthsOfAge(calendarBirth, calendarNow); diffDays = 0; } else { calendarNow.add(Calendar.MONTH, -1);// 上個月 diffMonths = getMonthsOfAge(calendarBirth, calendarNow); // 擷取上個月最大的一天 int maxDayOfLastMonth = calendarNow .getActualMaximum(Calendar.DAY_OF_MONTH); if (maxDayOfLastMonth > dayOfBirth) { diffDays = maxDayOfLastMonth - dayOfBirth + dayOfNow; } else { diffDays = dayOfNow; } } } } // 計算月份時,沒有考慮年 diffYears = diffMonths / 12; diffMonths = diffMonths % 12; return new int[] { diffYears, diffMonths, diffDays }; }
/** * 擷取兩個日曆的月份之差 * * @param calendarBirth * @param calendarNow * @return */ public static int getMonthsOfAge(Calendar calendarBirth, Calendar calendarNow) { return (calendarNow.get(Calendar.YEAR) - calendarBirth .get(Calendar.YEAR))* 12+ calendarNow.get(Calendar.MONTH) - calendarBirth.get(Calendar.MONTH); }
/** * 判斷這一天是否是月底 * * @param calendar * @return */ public static boolean isEndOfMonth(Calendar calendar) { int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); if (dayOfMonth == calendar.getActualMaximum(Calendar.DAY_OF_MONTH)) return true; return false; }
如果你有更好的方法,歡迎探討:-)
本文出自 “超越夢想” 部落格,請務必保留此出處http://breezy.blog.51cto.com/2400264/451353