本文爲原創分享,轉載請註明出處。html
即時通信IM應用中的聊天消息時間顯示是個再常見不過的需求,如今都講究用戶體驗,因此時間顯示不再能像傳統軟件同樣簡單粗地暴顯示成「年/月/日 時:分:秒」這樣。因此,市面上幾乎全部的IM都會對聊天消息的時間顯示格化作人性化處理,從而提高用戶體驗(使用感覺會明顯友好)。java
這兩天正在繼續開發RainbowChat-Web產品,因此正須要這樣的代碼。但通過在即時通信網的論壇和技術交流羣裏詢問,以及網上的所謂仿微信例子,都不符合要求。這些例子要麼簡陋粗暴(有邏輯bug硬傷)、要麼並不完整(可能只是隨手寫的練手代碼,並不適合放到產品中),因此本着作技術精益求精的態度,沒有現成的輪子可用,那就只能造輪子了。node
那麼,按怎樣的顯示邏輯來實現呢?做爲移動端IM的王者,微信無疑到處是標杆,因此本次的消息時間顯示格式,直接參照微信的實現邏輯準沒錯(隨大流雖然沒個性,但不至於非主流)。ios
* 提示:本文中的代碼實現,是從 RainbowChat 和 RainbowChat-Web 兩個IM產品中扒出來簡化後的結果,是基於徹底相同的算法邏輯分別用OC、Java和JavaScript實現的。如您以爲有用,能夠改改直接用於您的產品,如您有更好的建議請直接回復和評論。代碼僅供參考,不足以外,還請見諒!算法
學習交流:數據庫
- 即時通信/推送技術開發交流4羣:101279154 [推薦]編程
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》服務器
(本文同步發佈於:http://www.52im.net/thread-2371-1-1.html)微信
《用於IM中圖片壓縮的Android工具類源碼,效果可媲美微信 [附件下載]》網絡
《高仿Android版手機QQ可拖拽未讀數小氣泡源碼 [附件下載]》
《Android聊天界面源碼:實現了聊天氣泡、表情圖標(可翻頁) [附件下載]》
《高仿Android版手機QQ首頁側滑菜單源碼 [附件下載]》
《高仿手機QQ的Android版鎖屏聊天消息提醒功能 [附件下載]》
《高仿iOS版手機QQ錄音及振幅動畫完整實現 [源碼下載]》
先來看看微信中聊天消息的時間顯示成什麼樣:
來自微信官方對聊天消息時間顯示的規則說明:
▲ 該規則的定義,主要是二、3條(本圖引用自微信官方FAQ文檔)
參見第3節中的截圖和微信官方的說明,咱們能夠總結出微信對於聊天消息時間顯示的規則。
① 微信對於聊天消息時間顯示的規則總結以下(首頁「消息」界面):
1)當聊天消息時間爲一週以內時:當天的消息顯示爲「小時:分鐘」形式,而後是「昨天」、「前天」,而後就是「星期幾」這個樣子;
2)當聊天消息的時間大於一週時:直接顯示「年/月/日」的時間格式。
② 微信對於聊天消息時間顯示的規則總結以下(聊天內容界面):
1)當聊天消息時間爲一週以內時:當天的消息顯示爲「小時:分鐘」形式,而後是「昨天 時:分」、「前天 時:分」,而後就是「星期幾 時:分」這個樣子;
2)當聊天消息的時間大於一週時:直接顯示「年/月/日 時:分」的完整時間格式。
注意:聊天內容界面裏的時間格式,其實是首頁「消息」界面裏的時間格式加上「時:分」後的結果,因此代碼實現上這兩套代碼是能夠重用的,無需兩份代碼。
好了,規則已經摸清,下面將直接上代碼。
/**
* 返回指定pattern樣的日期時間字符串。
*
* @param dt
* @param pattern
* @return 若是時間轉換成功則返回結果,不然返回空字符串""
* @author 即時通信網([url=http://www.52im.net]http://www.52im.net[/url])
*/
publicstaticString getTimeString(Date dt, String pattern)
{
try
{
SimpleDateFormat sdf = newSimpleDateFormat(pattern);//"yyyy-MM-dd HH:mm:ss"
sdf.setTimeZone(TimeZone.getDefault());
returnsdf.format(dt);
}
catch(Exception e)
{
return"";
}
}
/**
* 仿照微信中的消息時間顯示邏輯,將時間戳(單位:毫秒)轉換爲友好的顯示格式.
* <p>
* 1)7天以內的日期顯示邏輯是:今天、昨天(-1d)、前天(-2d)、星期?(只顯示總計7天以內的星期數,即<=-4d);<br>
* 2)7天以外(即>7天)的邏輯:直接顯示完整日期時間。
*
* @param srcDate 要處理的源日期時間對象
* @param mustIncludeTime true表示輸出的格式裏必定會包含「時間:分鐘」,不然不包含(參考微信,不包含時分的狀況,用於首頁「消息」中顯示時)
* @return 輸出格式形如:「10:30」、「昨天 12:04」、「前天 20:51」、「星期二」、「2019/2/21 12:09」等形式
* @author 即時通信網([url=http://www.52im.net]http://www.52im.net[/url])
* @since 4.5
*/
publicstaticString getTimeStringAutoShort2(Date srcDate, booleanmustIncludeTime)
{
String ret = "";
try
{
GregorianCalendar gcCurrent = newGregorianCalendar();
gcCurrent.setTime(newDate());
intcurrentYear = gcCurrent.get(GregorianCalendar.YEAR);
intcurrentMonth = gcCurrent.get(GregorianCalendar.MONTH)+1;
intcurrentDay = gcCurrent.get(GregorianCalendar.DAY_OF_MONTH);
GregorianCalendar gcSrc = newGregorianCalendar();
gcSrc.setTime(srcDate);
intsrcYear = gcSrc.get(GregorianCalendar.YEAR);
intsrcMonth = gcSrc.get(GregorianCalendar.MONTH)+1;
intsrcDay = gcSrc.get(GregorianCalendar.DAY_OF_MONTH);
// 要額外顯示的時間分鐘
String timeExtraStr = (mustIncludeTime?" "+getTimeString(srcDate, "HH:mm"):"");
// 當年
if(currentYear == srcYear)
{
longcurrentTimestamp = gcCurrent.getTimeInMillis();
longsrcTimestamp = gcSrc.getTimeInMillis();
// 相差時間(單位:毫秒)
longdelta = (currentTimestamp - srcTimestamp);
// 當天(月份和日期一致纔是)
if(currentMonth == srcMonth && currentDay == srcDay)
{
// 時間相差60秒之內
if(delta < 60* 1000)
ret = "剛剛";
// 不然當天其它時間段的,直接顯示「時:分」的形式
else
ret = getTimeString(srcDate, "HH:mm");
}
// 當年 && 當天以外的時間(即昨天及之前的時間)
else
{
// 昨天(以「如今」的時候爲基準-1天)
GregorianCalendar yesterdayDate = newGregorianCalendar();
yesterdayDate.add(GregorianCalendar.DAY_OF_MONTH, -1);
// 前天(以「如今」的時候爲基準-2天)
GregorianCalendar beforeYesterdayDate = newGregorianCalendar();
beforeYesterdayDate.add(GregorianCalendar.DAY_OF_MONTH, -2);
// 用目標日期的「月」和「天」跟上方計算出來的「昨天」進行比較,是最爲準確的(若是用時間戳差值
// 的形式,是不許確的,好比:如今時刻是2019年02月22日1:00、而srcDate是2019年02月21日23:00,
// 這二者間只相差2小時,直接用「delta/(3600 * 1000)」 > 24小時來判斷是否昨天,就徹底是扯蛋的邏輯了)
if(srcMonth == (yesterdayDate.get(GregorianCalendar.MONTH)+1)
&& srcDay == yesterdayDate.get(GregorianCalendar.DAY_OF_MONTH))
{
ret = "昨天"+timeExtraStr;// -1d
}
// 「前天」判斷邏輯同上
elseif(srcMonth == (beforeYesterdayDate.get(GregorianCalendar.MONTH)+1)
&& srcDay == beforeYesterdayDate.get(GregorianCalendar.DAY_OF_MONTH))
{
ret = "前天"+timeExtraStr;// -2d
}
else
{
// 跟當前時間相差的小時數
longdeltaHour = (delta/(3600* 1000));
// 若是小於 7*24小時就顯示星期幾
if(deltaHour < 7*24)
{
String[] weekday = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
// 取出當前是星期幾
String weedayDesc = weekday[gcSrc.get(GregorianCalendar.DAY_OF_WEEK)-1];
ret = weedayDesc+timeExtraStr;
}
// 不然直接顯示完整日期時間
else
ret = getTimeString(srcDate, "yyyy/M/d")+timeExtraStr;
}
}
}
else
ret = getTimeString(srcDate, "yyyy/M/d")+timeExtraStr;
}
catch(Exception e)
{
System.err.println("【DEBUG-getTimeStringAutoShort】計算出錯:"+e.getMessage()+" 【NO】");
}
returnret;
}
// 用於首頁「消息」界面時
getTimeStringAutoShort2(newDate(), false);
// 用於聊天內容界面時
getTimeStringAutoShort2(newDate(), true);
源文件TimeTool.h:
#import <Foundation/Foundation.h>
@interfaceTimeTool : NSObject
/**
* 仿照微信中的消息時間顯示邏輯,將時間戳(單位:毫秒)轉換爲友好的顯示格式.
* 1)7天以內的日期顯示邏輯是:今天、昨天(-1d)、前天(-2d)、星期?(只顯示總計7天以內的星期數,即<=-4d);
* 2)7天以外(即>7天)的邏輯:直接顯示完整日期時間。
* @param dt 日期時間對象(本次被判斷對象)
* @param includeTime YES表示輸出的格式裏必定會包含「時間:分鐘」,不然不包含(參考微信,不包含時分的狀況,用於首頁「消息」中顯示時)
* @return 輸出格式形如:「剛剛」、「10:30」、「昨天 12:04」、「前天 20:51」、「星期二」、「2019/2/21 12:09」等形式
* @since 1.3
*/
+ (NSString*)getTimeStringAutoShort2:(NSDate*)dt mustIncludeTime:(BOOL)includeTime;
+ (NSString*)getTimeString:(NSDate*)dt format:(NSString*)fmt;
/**
* 得到指定NSDate對象iOS時間戳(格式聽從ios的習慣,以秒爲單位)。
*/
+ (NSTimeInterval) getIOSTimeStamp:(NSDate*)dat;
/**
* 得到指定NSDate對象iOS時間戳的long形式(格式聽從ios的習慣,以秒爲單位,形如:1485159493)。
*/
+ (long) getIOSTimeStamp_l:(NSDate*)dat;
/**
* 得到iOS當前系統時間的NSDate對象。
*/
+ (NSDate*)getIOSDefaultDate;@end
源文件TimeTool.m:
#import "TimeTool.h"
@implementationTimeTool
// 仿照微信的邏輯,顯示一我的性化的時間字串
+ (NSString*)getTimeStringAutoShort2:(NSDate*)dt mustIncludeTime:(BOOL)includeTime{
NSString*ret = nil;
NSCalendar*calendar = [NSCalendarcurrentCalendar];
// 當前時間
NSDate*currentDate = [NSDatedate];
NSDateComponents*curComponents = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitWeekdayfromDate:currentDate];
NSIntegercurrentYear=[curComponents year];
NSIntegercurrentMonth=[curComponents month];
NSIntegercurrentDay=[curComponents day];
// 目標判斷時間
NSDateComponents*srcComponents = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitWeekdayfromDate:dt];
NSIntegersrcYear=[srcComponents year];
NSIntegersrcMonth=[srcComponents month];
NSIntegersrcDay=[srcComponents day];
// 要額外顯示的時間分鐘
NSString*timeExtraStr = (includeTime?[TimeTool getTimeString:dt format:@" HH:mm"]:@"");
// 當年
if(currentYear == srcYear) {
longcurrentTimestamp = [TimeTool getIOSTimeStamp_l:currentDate];
longsrcTimestamp = [TimeTool getIOSTimeStamp_l:dt];
// 相差時間(單位:秒)
longdelta = currentTimestamp - srcTimestamp;
// 當天(月份和日期一致纔是)
if(currentMonth == srcMonth && currentDay == srcDay) {
// 時間相差60秒之內
if(delta < 60)
ret = @"剛剛";
// 不然當天其它時間段的,直接顯示「時:分」的形式
else
ret = [TimeTool getTimeString:dt format:@"HH:mm"];
}
// 當年 && 當天以外的時間(即昨天及之前的時間)
else{
// 昨天(以「如今」的時候爲基準-1天)
NSDate*yesterdayDate = [NSDatedate];
yesterdayDate = [NSDatedateWithTimeInterval:-24*60*60 sinceDate:yesterdayDate];
NSDateComponents*yesterdayComponents = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDayfromDate:yesterdayDate];
NSIntegeryesterdayMonth=[yesterdayComponents month];
NSIntegeryesterdayDay=[yesterdayComponents day];
// 前天(以「如今」的時候爲基準-2天)
NSDate*beforeYesterdayDate = [NSDatedate];
beforeYesterdayDate = [NSDatedateWithTimeInterval:-48*60*60 sinceDate:beforeYesterdayDate];
NSDateComponents*beforeYesterdayComponents = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDayfromDate:beforeYesterdayDate];
NSIntegerbeforeYesterdayMonth=[beforeYesterdayComponents month];
NSIntegerbeforeYesterdayDay=[beforeYesterdayComponents day];
// 用目標日期的「月」和「天」跟上方計算出來的「昨天」進行比較,是最爲準確的(若是用時間戳差值
// 的形式,是不許確的,好比:如今時刻是2019年02月22日1:00、而srcDate是2019年02月21日23:00,
// 這二者間只相差2小時,直接用「delta/3600」 > 24小時來判斷是否昨天,就徹底是扯蛋的邏輯了)
if(srcMonth == yesterdayMonth && srcDay == yesterdayDay)
ret = [NSStringstringWithFormat:@"昨天%@", timeExtraStr];// -1d
// 「前天」判斷邏輯同上
elseif(srcMonth == beforeYesterdayMonth && srcDay == beforeYesterdayDay)
ret = [NSStringstringWithFormat:@"前天%@", timeExtraStr];// -2d
else{
// 跟當前時間相差的小時數
longdeltaHour = (delta/3600);
// 若是小於或等 7*24小時就顯示星期幾
if(deltaHour <= 7*24){
NSArray<NSString*> *weekdayAry = [NSArrayarrayWithObjects:@"星期日", @"星期一", @"星期二", @"星期三", @"星期四", @"星期五", @"星期六", nil];
// 取出的星期數:1表示星期天,2表示星期一,3表示星期二。。。。 6表示星期五,7表示星期六
NSIntegersrcWeekday=[srcComponents weekday];
// 取出當前是星期幾
NSString*weedayDesc = [weekdayAry objectAtIndex:(srcWeekday-1)];
ret = [NSStringstringWithFormat:@"%@%@", weedayDesc, timeExtraStr];
}
// 不然直接顯示完整日期時間
else
ret = [NSStringstringWithFormat:@"%@%@", [TimeTool getTimeString:dt format:@"yyyy/M/d"], timeExtraStr];
}
}
}
// 往年
else{
ret = [NSStringstringWithFormat:@"%@%@", [TimeTool getTimeString:dt format:@"yyyy/M/d"], timeExtraStr];
}
returnret;
}
+ (NSString*)getTimeString:(NSDate*)dt format:(NSString*)fmt{
NSDateFormatter* format = [[NSDateFormatteralloc] init];
[format setDateFormat:fmt];
return[format stringFromDate:(dt==nil?[TimeTool getIOSDefaultDate]:dt)];
}
+ (NSTimeInterval) getIOSTimeStamp:(NSDate*)dat{
NSTimeIntervala = [dat timeIntervalSince1970];
returna;
}
+ (long) getIOSTimeStamp_l:(NSDate*)dat{
return[[NSNumbernumberWithDouble:[TimeTool getIOSTimeStamp:dat]] longValue];
}
+ (NSDate*)getIOSDefaultDate
{
return [NSDate date];
}@end
// 用於首頁「消息」界面時
[TimeTool getTimeStringAutoShort2:[NSDatedate] mustIncludeTime:NO];
抱歉:因文章字數限制,JavaScript版源碼無非法貼上來,請從連接:http://www.52im.net/thread-2371-1-1.html,查看JavaScript版完整源碼!
// 用於首頁「消息」界面時
_getTimeStringAutoShort2(1550789954260, false);
// 用於聊天內容界面時
_getTimeStringAutoShort2(1550789954260, true);
[1] 精品源碼下載:
《Java NIO基礎視頻教程、MINA視頻教程、Netty快速入門視頻 [有源碼]》
《輕量級即時通信框架MobileIMSDK的iOS源碼(開源版)[附件下載]》
《開源IM工程「蘑菇街TeamTalk」2015年5月前未刪減版完整代碼 [附件下載]》
《微信本地數據庫破解版(含iOS、Android),僅供學習研究 [附件下載]》
《NIO框架入門(四):Android與MINA二、Netty4的跨平臺UDP雙向通訊實戰 [附件下載]》
《NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰 [附件下載]》
《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示 [附件下載]》
《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示 [附件下載]》
《用於IM中圖片壓縮的Android工具類源碼,效果可媲美微信 [附件下載]》
《高仿Android版手機QQ可拖拽未讀數小氣泡源碼 [附件下載]》
《一個WebSocket實時聊天室Demo:基於node.js+socket.io [附件下載]》
《Android聊天界面源碼:實現了聊天氣泡、表情圖標(可翻頁) [附件下載]》
《高仿Android版手機QQ首頁側滑菜單源碼 [附件下載]》
《開源libco庫:單機千萬鏈接、支撐微信8億用戶的後臺框架基石 [源碼下載]》
《微信團隊原創Android資源混淆工具:AndResGuard [有源碼]》
《一個基於MQTT通訊協議的完整Android推送Demo [附件下載]》
《高仿手機QQ的Android版鎖屏聊天消息提醒功能 [附件下載]》
《高仿iOS版手機QQ錄音及振幅動畫完整實現 [源碼下載]》
《Android端社交應用中的評論和回覆功能實戰分享[圖文+源碼]》
《Android端IM應用中的@人功能實現:仿微博、QQ、微信,零入侵、高可擴展[圖文+源碼]》
《仿微信的IM聊天時間顯示格式(含iOS/Android/Web實現源碼)[圖文+源碼]》
[2] 精品文檔和工具下載:
《重磅發佈:《阿里巴巴Android開發手冊(規約)》[附件下載]》
《阿里技術結晶:《阿里巴巴Java開發手冊(規約)-終極版》[附件下載]》
《基於RTMP協議的流媒體技術的原理與應用(技術論文)[附件下載]》
《獨家發佈《TCP/IP詳解 卷1:協議》CHM版 [附件下載]》
《良心分享:WebRTC 零基礎開發者教程(中文)[附件下載]》
《經典書籍《UNIX網絡編程》最全下載(卷1+卷二、中文版+英文版)[附件下載]》
《音視頻開發理論入門書籍之《視頻技術手冊(第5版)》[附件下載]》
《國際電聯H.264視頻編碼標準官方技術手冊(中文版)[附件下載]》
《Apache MINA2.0 開發指南(中文版)[附件下載]》
《網絡通信數據抓包和分析工具 Wireshark 使用教程(中文) [附件下載]》
《最新收集NAT穿越(p2p打洞)免費STUN服務器列表 [附件下載]》
《高性能網絡編程經典:《The C10K problem(英文)》[附件下載]》
《華爲內部3G網絡資料: WCDMA系統原理培訓手冊[附件下載]》
《網絡測試:Android版多路ping命令工具EnterprisePing[附件下載]》
《Android反編譯利器APKDB:沒有美工的日子裏繼續堅強的擼》
《兩款加強型Ping工具:持續統計、圖形化展式網絡情況 [附件下載]》
[3] 精選視頻、演講PPT下載:
《美圖海量用戶的IM架構零基礎演進之路(PPT)[附件下載]》
《開源實時音視頻工程WebRTC的架構詳解與實踐總結(PPT+視頻)[附件下載]》
《QQ空間百億級流量的社交廣告系統架構實踐(視頻+PPT)[附件下載]》
《海量實時消息的視頻直播系統架構演進之路(視頻+PPT)[附件下載]》
《YY直播在移動弱網環境下的深度優化實踐分享(視頻+PPT)[附件下載]》
《QQ空間移動端10億級視頻播放技術優化揭祕(視頻+PPT)[附件下載]》
《RTC實時互聯網2017年度大會精選演講PPT [附件下載]》
《微信分享開源IM網絡層組件庫Mars的技術實現(視頻+PPT)[附件下載]》
《微服務理念在微信海量用戶後臺架構中的實踐(視頻+PPT)[附件下載]》
《移動端IM開發和構建中的技術難點實踐分享(視頻+PPT)[附件下載]》
《網易雲信的高品質即時通信技術實踐之路(視頻+PPT)[附件下載]》
《騰訊音視頻實驗室:直面音視頻質量評估之痛(視頻+PPT)[附件下載]》
《騰訊QQ1.4億在線用戶的技術挑戰和架構演進之路PPT[附件下載]》
《手機淘寶消息推送系統的架構與實踐(音頻+PPT)[附件下載]》
《如何進行實時音視頻的質量評估與監控(視頻+PPT)[附件下載]》
《Go語言構建高併發消息推送系統實踐PPT(來自360公司)[附件下載]》
《網易IM雲千萬級併發消息處理能力的架構設計與實踐PPT [附件下載]》
《手機QQ的海量用戶移動化實踐分享(視頻+PPT)[附件下載]》
《釘釘——基於IM技術的新一代企業OA平臺的技術挑戰(視頻+PPT)[附件下載]》
《微信技術總監談架構:微信之道——大道至簡(PPT講稿)[附件下載]》
《Netty的架構剖析及應用案例介紹(視頻+PPT)[附件下載]》
《微信海量用戶背後的後臺系統存儲架構(視頻+PPT)[附件下載]》
《在線音視頻直播室服務端架構最佳實踐(視頻+PPT)[附件下載]》
《從0到1:萬人在線的實時音視頻直播技術實踐分享(視頻+PPT)[附件下載]》
(本文同步發佈於:http://www.52im.net/thread-2371-1-1.html)