内容简介:Android(Java)日期和时间处理完全解析——使用Gson和Joda-Time优雅地处理日常开发中关于时间处理的...
原创声明: 该文章为原创文章,未经博主同意严禁转载。
简介: 对于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优雅地处理时间
首先给大家看一则最近的热门微博话题下的一则微博。
我们主要关注笔者标记的两个时间,可以看到微博是把时间转化为更容易让我们理解的形式来表示的。我们来分析下各个时间段微博的现实形式,以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库是十分类似的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Android(Java)日期和时间处理完全解析——使用Gson和Joda-Time优雅地处理日常开发中关于时间处理的...
- python时间日期函数与利用pandas进行时间序列处理详解
- Python入门 —— 05时间日期处理小结
- 了解Python语言中的时间处理
- django2中关于时间处理策略
- 30例 | 一文搞懂python日期时间处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Trading and Exchanges
Larry Harris / Oxford University Press, USA / 2002-10-24 / USD 95.00
This book is about trading, the people who trade securities and contracts, the marketplaces where they trade, and the rules that govern it. Readers will learn about investors, brokers, dealers, arbit......一起来看看 《Trading and Exchanges》 这本书的介绍吧!