本文为原创分享,转载请注明出处。
1、引言
即时通讯IM应用中的聊天消息时间显示是个再常见不过的需求,现在都讲究用户体验,所以时间显示再也不能像传统软件一样简单粗地暴显示成“年/月/日 时:分:秒”这样。所以,市面上几乎所有的IM都会对聊天消息的时间显示格化做人性化处理,从而提升用户体验(使用感受会明显友好)。
这两天正在继续开发RainbowChat-Web产品,所以正需要这样的代码。但经过在即时通讯网的论坛和技术交流群里询问,以及网上的所谓仿微信例子,都不符合要求。这些例子要么简陋粗暴(有逻辑bug硬伤)、要么并不完整(可能只是随手写的练手代码,并不适合放到产品中),所以本着做技术精益求精的态度,没有现成的轮子可用,那就只能造轮子了。
那么,按怎样的显示逻辑来实现呢?作为移动端IM的王者,微信无疑处处是标杆,所以本次的消息时间显示格式,直接参照微信的实现逻辑准没错(随大流虽然没个性,但不至于非主流)。
* 提示:本文中的代码实现,是从 RainbowChat 和 RainbowChat-Web 两个IM产品中扒出来简化后的结果,是基于完全相同的算法逻辑分别用OC、Java和JavaScript实现的。如您觉得有用,可以改改直接用于您的产品,如您有更好的建议请直接回复和评论。代码仅供参考,不足之外,还请见谅!
学习交流:- 即时通讯/推送技术开发交流4群:101279154 [推荐]
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
2、相关文章
3、看看微信中聊天消息的时间显示规则
先来看看微信中聊天消息的时间显示成什么样:微信主页“消息”界面
聊天界面(注意聊天界面中默认带了“时:分”的显示)
来自微信官方对聊天消息时间显示的规则说明:
▲ 该规则的定义,主要是2、3条(本图引用自微信官方FAQ文档)
4、总结一下微信中聊天消息的时间显示逻辑
参见第3节中的截图和微信官方的说明,我们可以总结出微信对于聊天消息时间显示的规则。
① 微信对于聊天消息时间显示的规则总结如下(首页“消息”界面):1)当聊天消息时间为一周之内时:当天的消息显示为“小时:分钟”形式,然后是“昨天”、“前天”,然后就是“星期几”这个样子;
2)当聊天消息的时间大于一周时:直接显示“年/月/日”的时间格式。
② 微信对于聊天消息时间显示的规则总结如下(聊天内容界面):1)当聊天消息时间为一周之内时:当天的消息显示为“小时:分钟”形式,然后是“昨天 时:分”、“前天 时:分”,然后就是“星期几 时:分”这个样子;
2)当聊天消息的时间大于一周时:直接显示“年/月/日 时:分”的完整时间格式。
注意:聊天内容界面里的时间格式,实际上是首页“消息”界面里的时间格式加上“时:分”后的结果,所以代码实现上这两套代码是可以重用的,无需两份代码。
好了,规则已经摸清,下面将直接上代码。
5、Android平台上的代码实现(标准Java)
5.1 完整源码
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"";
}
}
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;
}
5.2 调用示例// 用于首页“消息”界面时
getTimeStringAutoShort2(newDate(), false);
// 用于聊天内容界面时
getTimeStringAutoShort2(newDate(), true);
5.3 运行效果
▲ 上述代码在RainbowChat Android版上的运行效果(首页)
▲ 上述代码在RainbowChat Android版上的运行效果(聊天界面)
6、iOS平台上的代码实现(Objective-C)
6.1 完整源码
源文件TimeTool.h:#import
@interfaceTimeTool : NSObject
+ (NSString*)getTimeStringAutoShort2:(NSDate*)dt mustIncludeTime:(BOOL)includeTime;
+ (NSString*)getTimeString:(NSDate*)dt format:(NSString*)fmt;
+ (NSTimeInterval) getIOSTimeStamp:(NSDate*)dat;
+ (long) getIOSTimeStamp_l:(NSDate*)dat;
@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 *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];
}
@end
6.2 调用示例// 用于首页“消息”界面时
[TimeTool getTimeStringAutoShort2:[NSDatedate] mustIncludeTime:NO];
6.3 运行效果
▲ 上述代码在RainbowChat iOS版上的运行效果(首页)
7、Web网页端的代码实现(JavaScript)
7.1 完整源码抱歉:因文章字数限制,JavaScript版源码无非法贴上来,请从链接:http://www.52im.net/thread-2371-1-1.html,查看JavaScript版完整源码!
7.2 调用示例// 用于首页“消息”界面时
_getTimeStringAutoShort2(1550789954260, false);
// 用于聊天内容界面时
_getTimeStringAutoShort2(1550789954260, true);
7.3 运行效果
▲ 上述代码在RainbowChat-Web产品上的运行效果
附录:更多精品资源下载