java plusday_Android(Java)日期和时间处理完全解析——使用Gson和Joda-Time优雅地处理日常开发中关于时间处理的......

本文介绍了在Java旧版本中,日期和时间处理的不足,如java.util.Date和java.util.Calendar的缺陷。推荐使用Joda-Time库作为替代,并详细讲解了Joda-Time的核心类和常用API。同时,展示了如何结合Gson优雅地处理时间格式化,以适应Android日常开发中的时间展示需求。文章最后讨论了Java 8的时间API和Android Studio的兼容性问题。
摘要由CSDN通过智能技术生成

qye2ua2.jpg!web

原创声明: 该文章为原创文章,未经博主同意严禁转载。

简介: 对于 Android 和 Java 开发者来说,时间的处理是我们必须掌握的知识。如果你尝试过造时间处理方面的轮子的话,你就会知道,关于时间的处理是一个非常复杂的问题。我们在处理时间时需要把时间转化成能让计算机理解的形式,而Java 8之前的库对日期和时间的支持是非常不理想的。Java 8种提供了全新的时间API供我们使用,这些API在java.time包下。Android开发者需要注意的是,虽然Android Studio 2.4已经开始支持Java 8了,但是却无法导入java.time包下的类文件,这个问题应该是Android Studio的BUG。因为这个原因,所以笔者在这里介绍的是Java 8之前如何处理好时间和日期相关的问题。

Java旧版本时间API的简介

在Java 1.0中,对日期和时间的处理只能够以来java.util.Date类。正如类名所表达的,这个类无法表示日期,只能以毫秒的精度表示时间。更糟糕的是它的易用性,由于某些设计决策,这个类的易用性被深深地损害了,比如:年份的起始选择是1900年,月份的起始选择是0。这意味着,如果你要表示2017年4月30日的话,需要创建下面这样的Date实例:

Date date = new Date(117,3,30);

它的打印效果为:

Sun Apr 30 00:00:00 CST 2017

看起来不是十分直观。此外,Date类的toString方法返回的字符串也很容易误导人。以我们的例子而言,它的返回值中甚至还包含了时区CST,即中国时间。但这并不表示Date类在任何方面支持时区。

随着Java 1.0退出历史的舞台,Date类的种种问题和限制几乎一扫而光,但是很明显,这些问题的解决是伴随着兼容性的牺牲的。所以在Java 1.1中,Date类的很多方法都被废弃了。取而代之的是java.util.Calendar类。很不幸,Calendar类也有类似的问题和设计缺陷。导致使用这些方法写出的代码非常容易出错。比如,月份依旧是从0开始计算的。而更糟糕的是,同时存在Date和Calendar这两个类也增加了 程序员 的困惑。此外,有的特性只在某一个类有提供,比如用于以语言无关方式格式化和解析日期或时间的DateFormat方法就只在Date里面有。

DateFormat方法也有它自己的问题。比如,它不是线程安全的。

最后,Date和Calendar类都是可变的,想下将2017年4月30日改变为2017年5月1日的后果?这种设计会将你拖入维护的噩梦。

所以我们需要一个第三方的日期和时间库。在这里我们介绍的是Joda-Time。Java 8中java.time包中整合了很多Joda-Time的特性。

Joda-Time的简单介绍

引入MAVEN依赖

compile 'net.danlew:android.joda:2.9.9'

核心类介绍

Instant: 不可变的类,用来表示时间轴上一个瞬时的点

DateTime: 不可变的类,用来替换JDK的Calendar类

LocalDate: 不可变的类,表示一个本地的日期,而不包含时间部分(没有时区信息)

LocalTime: 不可变的类,表示一个本地的时间,而不包含日期部分(没有时区信息)

LocalDateTime: 不可变的类,表示一个本地的日期-时间(没有时区信息)

DateTime简介

DateTime是我们用得比较多的一个类,在这里笔者简单介绍下它的使用方法。首先我们来介绍下它的构造方法。

DateTime():这个无参的构造方法会创建一个在当前系统所在时区的当前时间,精确到毫秒。

DateTime(long instant):接受一个一个long类型的时间戳(它表示这个时间戳距1970-01-01T00:00:00Z的毫秒数)。创建时间实例,使用默认的时区。

DateTime(Object instant):这个构造方法可以通过一个Object对象构造一个实例。这个Object对象可以是这些类型:ReadableInstant, String, Calendar和Date。其中String的格式需要是ISO8601格式,详见: ISODateTimeFormat.dateTimeParser() 。

DateTime(int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minuteOfHour, int secondOfMinute):这个构造方法可以根据具体的时间构造一个实例。

DateTime常用API

下面我们来介绍一下,DateTime类中常用的API。

get方法集合(如getYear):

get系列方法主要用于获取DateTime的一些具体信息,我们可以通过方法名来推断具体的作用。如getDayForYear,这个方法的作用是获取该DateTime实例属于该年的第几天。我们可以看看2017年4月30日这个例子。

DateTime dateTime = new DateTime(2017,04,30,0,0);

System.out.println("这天是2017年的第" + dateTime.getDayOfYear() + "天");

我们可以看看输出的打印的结果是:

这天是2017年的第120天

Joda-Time会自动帮我们处理闰年与月份的问题,好了我们现在可以打开日历软件看看这天是不是2017年的第30天了。

get方法集还有很多十分有用的API,读者可以自己体验下。

with方法集合(如withYear):

with方法集合主要是用来设置DateTime实例的一些属性的,如我们可以把2017年4月30日设置为2017年3月30日。上文提到过,DateTime是不可变类,所以with系列方法并没有改变原对象的属性,而是返回了一个新的对象。下面我们可以看看我们将2017年4月30日设置为2017年3月30日的代码。

DateTime dateTime = new DateTime(2017,04,30,0,0);

DateTime withDateTime = dateTime.withMonthOfYear(3);

System.out.println(withDateTime);

打印的结果是:2017-03-30T00:00:00.000Z

我们可以看到,月份已经变为3月了。

plus/minus方法集合(如:plusDay)

plus方法集合的功能是返回DateTime实例的某个属性增加/减少一定的时间后的实力。这里我们需要注意的一点是,我们可以把plus/minus方法集合想象成翻日历牌一样,所有的计算都是合法的,并不会出现输入一场的情况。下面我们可以来看看把2017年4月30增加3天的例子。

DateTime dateTime = new DateTime(2017,04,30,0,0);

DateTime plusDays = dateTime.plusDays(3);

System.out.println(plusDays);

打印的结果是:2017-05-03T00:00:00.000Z

这系列运算并不会抛出异常或返回2017年4月33日这样的错误结果的。

由于这篇文章的重点不是介绍Joda-Time这个库,所以关于Joda-Time的介绍到这里就结束了,有兴趣的读者可以阅读官方API文档或者去看一些优秀的Blog加深理解也是可以的。下面我们来介绍本文的重点了,我们如何在Android日常开发中优雅地处理时间相关的问题。

通过Gson优雅地处理时间

首先给大家看一则最近的热门微博话题下的一则微博。

vAFbAfY.png!web

我们主要关注笔者标记的两个时间,可以看到微博是把时间转化为更容易让我们理解的形式来表示的。我们来分析下各个时间段微博的现实形式,以2017年5月1日 22:00:00是现在为例。

2017年5月1日 22:00:40 -> 40秒前

2017年5月1日 22:40:05 -> 40分钟前

2017年5月1日 10:30:20 -> 今天10:30

2017年4月30日 10:30:32 -> 昨天10:30

2017年3月20日 20:30:30 -> 3月20日 20:30

2014年3月20日 17:20:00 -> 2014年3月20日 17:20

我们可以看到微博会按照一定的规律对时间进行格式化,格式化后的效果笔者认为更适合阅读微博时的时间显示。一般使用Date或Calendar进行实现类似的功能会有两个问题:

代码不够优雅

实现该功能十分繁琐

那么我们如何优雅的处理这个问题呢?答案就是通过Gson和Joda-Time。服务器回传过来的时间数据一般是一串类似格式的字符串(微博采用的就是该格式):

"Tue Apr 25 23:33:03 +0800 2017"

使用Gson把时间字符串转换成Date类型

我们知道Gson可以把 Json 数据转化成任何我们需要的类型,那么这串关于时间的字符串用Gson当然也能够轻易转化为Date类型啦。那我我们如何使用Gson来处理呢?我们先来看看使用Gson进行处理的代码:

首先我们要创建能解析上面格式时间的Gson实例:

Gson gson = newGsonBuilder()

//设置需要解析的时间格式

.setDateFormat("EEE MMM dd HH:mm:ss Z yyyy")

.create();

setDateFormat的参数内容我们暂时先放下,在下一节笔者我详细解释这串字符串的含义的。

我们可以模拟下解析微博内容来测试下,我们需要解析的数据是:

{“date”:”Tue May 02 10:02:03 +0800 2017”,”text”:”Hello word”}

微博实体类:

public class Weibo {

//微博创建时间

public Date date;

//正文

public String text;

public Weibo(String date ,String text){

this.date = new Date(date);

this.text = text;

}

@Override

public String toString() {

return "这条微博创建的时间是:" + date + "\n微博正文:" + text ;

}

}

使用Gson解析

Weibo weobo = gson.fromJson(json,Weibo.class);

System.out.println(weobo);

打印的结果:

这条微博创建的时间是:Tue May 02 10:02:03 CST 2017

微博正文:Hello word

这里我们需要注意一点是,使用上面创建的gson来直接解析时间会报错的。如:

Date date = gson.fromJson("Tue May 02 10:02:03 +0800 2017",Date.class);

上面这行代码会抛出一个JsonSyntaxException异常。

这个错误是和Gson有关,我们日常使用的情况下,基本不会遇到直接解析Date数据的需求的,所以这种情况我们可以不做处理。如果想解决这个问题的话,我们可以重写一个用于解析时间的TyepAdapter来处理,在这里就不细说下去了。

通过Joda-Time把时间转换成更容易理解的格式

根据上文的分析,我们需要把时间转换成类似微博这种表现形式的话,需要把获取到的时间和系统当前时间进行比较,然后再转换。我们先来看看代码:

public class DateFormatUtil {

private static final String ONE_SECOND_AGO = "秒前";

private static final String ONE_MINUTE_AGO = "分钟前";

@SuppressLint("SimpleDateFormat")

public static String format(Date date) {

//把时区转换为东8区

TimeZone timeZone = TimeZone.getTimeZone("GMT+8");

DateTimeZone.setDefault(DateTimeZone.forTimeZone(timeZone));

DateTime nowDateTime = DateTime.now();

DateTime dateTime = new DateTime(date);

return formatDate(dateTime,nowDateTime);

}

@SuppressLint("SimpleDateFormat")

private static String formatDate(DateTime dateTime,DateTime nowDateTime){

int seconds = Seconds.secondsBetween(dateTime,nowDateTime).getSeconds();

if (seconds < 60) {

return seconds + ONE_SECOND_AGO;

}

int minutes = Minutes.minutesBetween(dateTime,nowDateTime).getMinutes();

if (minutes < 60) {

return minutes + ONE_MINUTE_AGO;

}

int day = nowDateTime.getDayOfYear() - dateTime.getDayOfYear();

int year = nowDateTime.getYear() - dateTime.getYear();

if (year < 1 && day < 1) {

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("今天 HH:mm");

return simpleDateFormat.format(dateTime.toDate());

}

if (year < 1 && day < 2) {

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("昨天 HH:mm");

return simpleDateFormat.format(dateTime.toDate());

}

if (year < 1) {

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM月dd日 HH:mm");

return simpleDateFormat.format(dateTime.toDate());

}

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm");

return simpleDateFormat.format(dateTime.toDate());

}

}

我们可以看到,代码十分简单,只是把对微博时间处理的分析结果简单地转化为代码而已。只是简单地把上面分析的结果转化成代码而已。

现在我们来测试下DateFormatUtil这个类吧,假设现在的时间是2017年5月2日14点43分,我们的测试代码是:

Date date0 = new Date("Tue May 02 14:43:03 +0800 2017");

Date date1 = new Date("Tue May 02 14:08:03 +0800 2017");

Date date2 = new Date("Tue May 02 02:00:03 +0800 2017");

Date date3 = new Date("Mon May 1 09:32:13 +0800 2017");

Date date4 = new Date("Tue Apr 25 23:33:03 +0800 2017");

Date date5 = new Date("Thu Aug 4 12:03:03 +0800 2016");

System.out.println(DateFormatUtil.format(date0));

System.out.println(DateFormatUtil.format(date1));

System.out.println(DateFormatUtil.format(date2));

System.out.println(DateFormatUtil.format(date3));

System.out.println(DateFormatUtil.format(date4));

System.out.println(DateFormatUtil.format(date5));

输出结果:

1分钟前

36分钟前

今天 02:00

昨天 09:32

04月25日 23:33

2016年08月04日 12:03

为了方便大家理解笔者删除了部分不重要的代码,只留下核心代码供大家学习,各位可以根据实际需求修改后再使用。

DateFormat使用介绍与字段解析

前文在介绍使用Gson解析Date数据的时候出现过一行这样的代码:

setDateFormat("EEE MMM dd HH:mm:ss Z yyyy")

很多朋友对”EEE MMM dd HH:mm:ss Z yyyy”这个字符串处于一知半解的状况,这个字符串是用来控制时间的格式的,我们首先简单了解下各个字母的作用与其含义。

字符

日期或时间元素

表示

例子

G

Era 标志符

Text

AD

y

Year

1971; 71

M

年中的月份

Month

July; Jul; 07

w

年中的周数

Number

13

W

月份中的周数

Number

3

D

年中的天数

Number

232

d

月份中的天数

Number

10

F

月份中的星期

Number

2

E

星期中的天数

Text

Tuesday; Tue

a

Am/pm 标记

Text

PM

H

一天中的小时数(0-23)

Number

12

k

一天中的小时数(1-24)

Number

24

K

am/pm 中的小时数(0-11)

Number

0

h

am/pm 中的小时数(1-12)

Number

12

m

小时中的分钟数

Number

30

s

分钟中的秒数

Number

55

S

毫秒数

Number

978

z

时区

General time zone

Pacific Standard Time; PST; GMT-08:00

Z

时区

RFC 822 time zone

-0800

需要特别注意的是:字符是区分大小写的,如HH:mm:ss中HH是代表小时采用24小时制,而hh则表示采用12小时制。

那么我们的字母的数量代表什么意思呢?还是使用上面的例子:

“EEE MMM dd HH:mm:ss Z yyyy”

其中我们星期中的天数E,年中的月份M的格式为EEE MMM。这样写的作用是最多显示3位的意思。那么HH就是代表小时采用24小时制并显示两位数字,yyyy则代表年份为4位。上面格式对应的一个时间例如如下:

“ Tue May 02 14:43:03 +0800 2017”

“ 17年07月12日” 我们可以采用下面这个DateFormat来解析:

“ yy年MM月dd日”

回到上面那段通过GsonBuilder创建Gson的代码中:

Gson gson = newGsonBuilder()

//设置需要解析的时间格式

.setDateFormat("EEE MMM dd HH:mm:ss Z yyyy")

.create();

如果我们需要解释的时间格式是”17年07月12日 12:35:11” 那么我们只需要把

.setDateFormat("EEE MMM dd HH:mm:ss Z yyyy")

替换成

.setDateFormat("yy年MM月dd日 HH:mm:ss")

即可。

小结

时间方面的文章介绍到这里就结束了,至于为什么会写一篇这样的基础文章呢?答案是因为笔者和其他人交流的时候发现,对于时间的处理,很多人都只是知其然而不知其所以然,所以笔者就把一些简单的小心得分享给大家。

关于优雅地时间处理的问题一直是Java中一个比较大的问题,而这个问题在Java 8之前一直都无法解决,只能通过Joda-Time之类的第三方库来减轻这些问题带来的影响。现在Java 8已经提供了一套全新的关于时间处理的方面的库,但不知道为什么到今天为止Android Studio 2.4暂时还不支持。笔者估计是和Android系统中时间处理方面的兼容性有关(如日期相关控件是通过java.util.Calendar实现的)。

如果Android Studio 2.4支持java.time包的话,那么我们可以用java.time包替换Joda-Time库。Joda-Time 库的作者参与了java.time包的API设计,所以java.time包API的使用方式和Joda-Time库是十分类似的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值