在日常的程序中,我们经常会用到日期时间,常常会从字符串和日期进行转换,因此本文决定深入学习一下java的Date相关部分,我这里用的jdk是8.
在编程中,一般涉及到日期的操作有三种:
1日期的表示,2日期的转换,3日期的设定修改。
JAVA在jdk1.1 之前,Date类负责这三个功能。但是jdk1.1后,java把这三个功能分成了三个类,其中:
Date类负责时间的表示,在计算机中,时间的表示是一个较大的概念,现有的系统基本都是利用从1970.1.1 00:00:00 到当前时间的毫秒数进行计时,这个时间称为epoch。在后文中如果没有明确说明,毫秒数就是指从1970年到对应时间的毫秒数。在Java 的Date类内部其实也是一个毫秒数,对外表现为一个Date对象,后面我将对Date类进行深入探究。
Calendar是一个工具类,负责对Date类进行修改等操作,以及从Date类中提取年月日等时间的特定信息。
DateFormat 则负责日期的转换,比如读取特定格式的字符串,转换成date对象,或者将date对象按照指定的格式转成字符串。
由于Date,Calendar,DateFormat这几个类关联不大,因此我这里分别对各个类进行探究,就不画统一的类图了。
为了更好的理解Date,可以从jdk中看一下Date类的结构。
Date类的方法还是比较多的,但是大多数都是Deprecated,也就是我上面所说的从Jdk1.1开始,将其他的功能都分流到了Calendar和DateFormat类中。
正如我们之前讲过的,Date类内部其实是一个从1970到其代表的时间之间的毫秒数,那就必然有一个filed要表示这个毫秒数,这就是 fastTime这个域的作用了。
private transient long fastTime;
在我们日常的使用Date类时,必然都要初始化一个Date对象,这时看看Date的两个剩余的构造函数,一个是无参的,一个是接收了一个long类型的毫秒数,无参构造函数其实是调用了系统的当前毫秒数。最终都是将fastTime这个属性赋值一个对应时间的毫秒数。
public Date() {
this(System.currentTimeMillis());
}
public Date(long date) {
fastTime = date;
}
Date类还可以对时间进行比较,比如before,after,compareTo等方法,都是对时间的先后进行比较的。
Date类还是保留一些工具类的方法的,比如这个静态的getMillisOf()就是获取date对象的毫秒数。
static final long getMillisOf(Date date) {
if (date.cdate == null || date.cdate.isNormalized()) {
return date.fastTime;
}
BaseCalendar.Date d = (BaseCalendar.Date) date.cdate.clone();
return gcal.getTime(d);
}
三 Calendar类
1 Calendar类的作用
在程序内部一个时间可以表示为一个毫秒数,但是对于我们日常程序中,可能需要用到这个时间点的年份,月份,小时数等详细信息,这时Calendar类就走上了舞台。
Calendar类前面说是一个工具类,其实它比工具类还是更深一步的,它更像是一个加强版的Date类。一般的工具类会提供一堆static方法,工具类本身并不存储对象。但是Calendar类不是像一般的工具类只提供一堆static方法,而是在其内部本身就有一个毫秒数,所以它不是对外部Date对象操作,而是对内部的毫秒数进行转化,所以说Calendar本身就包含了日期时间信息,像一个装饰者模式。
在Calendar类中的这个毫秒数就是下面这个field。
@SuppressWarnings("ProtectedField")
protected long time;
2 Calendar的使用:
Calendar本身是一个抽象类,不能直接实例化,但是Calendar类提供一个工厂方法,即getInstance来创建一个Calendar实例,通过setTime()设定一个Calendar内部的毫秒数,之后就可以对这个毫秒数进行分析,进而得到它的年月日信息。同时,我们也可以对Calendar直接设定年月日属性,从而获取对应的Date对象。
需要注意的是,Calendar的一月是从0开始的,即你设定月份为4时,其实是五月。
下面是一个Calendar的简单示例。
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.get(Calendar.YEAR);
System.out.println("the year is "+calendar.get(Calendar.YEAR));
System.out.println("the month is "+calendar.get(Calendar.MONTH));
calendar.set(Calendar.YEAR, 2014);
calendar.set(Calendar.MONTH, 2);
calendar.set(Calendar.DAY_OF_MONTH, 12);
System.out.println("calendar date is "+calendar.getTime());
输出:
the year is 2016
the month is 9
calendar date is Wed Mar 12 22:17:50 CST 2014
在讲到Calendar就不可避免的涉及到java的国际化问题和时区的问题。
(1) 国际化
Locale 表示地区,每一个Locale对象都代表了一个特定的地理、政治和文化地区。
你可以用如下代码看看你的系统默认地区。
Locale locale = Locale.getDefault();
locale.getCountry();
locale.getLanguage();
System.out.println("default locale is "+locale);
System.out.println("default country is "+locale.getCountry());
System.out.println("default language is "+locale.getLanguage());
比如我这里的输出就是:
default locale is zh_CN
default country is CN
default language is zh
说明了我所在的国家时CN,语言是zh。
(2) 时区
在java中用java.util.TimeZone类来表示一个时区。每一个时区都一个id,可以利用TimeZone.getAvailableIDs()这个方法获取所有的id。
你可以试试利用如下代码看看你的系统默认时区。
TimeZone timeZone = TimeZone.getDefault();
System.out.println("default time zone is "+ timeZone.getDisplayName());
本文的默认时区是
default time zone is 中国标准时间
3 Calendar的工厂构造方法
前面说到,Calendar是一个抽象类,提供了static的工厂构造方法来提供一个实例。
Calendar有多个的工厂构造方法:
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
public static Calendar getInstance(Locale aLocale)
{
return createCalendar(TimeZone.getDefault(), aLocale);
}
public static Calendar getInstance(TimeZone zone, Locale aLocale)
{
return createCalendar(zone, aLocale);
}
从上面的代码看出,Calendar的实例构造最终都是调用了这个方法
private static Calendar createCalendar(TimeZone zone, Locale aLocale)
这个方法接收两个参数,一个是时区,一个是地区。这个方法中通过对地区的判定返回不同的Calendar的子类(实现类),由于涉及到一些地区的问题,本文不做深究。
当我们有了Calendar的实例后,就可以对Date对象进行分析,提取出对应的年月日;也可以通过设定对应的年月日从而获取对应的Date对象。
1 DateFormat类的作用
DateFormat用来在Date对象和字符串之间进行转化。
比如,我有一个date,想转成2013年12月01日这种样式,或者想从2013-12-01这个字符串转成一个date对象,都可以利用DateFormat类。
2 DateFormat类的结构
DateFormat本身是一个抽象类,但是定义好了所有的方法,我们一般都是使用它的子类SimpleDateFormat,这也是一个标准的模板模式(template pattern)。
在DateFormat中定义了众多的方法,最重要的就是parse()和format(),这两个方法一个是将字符串转成Date对象,一个是将Date对象按照指定的模式进行转换字符串。
3 利用DateFormat进行转换
DateFormat虽然是个抽象类,我们在日常中可以使用它的子类SimpleDateFormat类,但DateFormat还是提供了一些static方法构建DateFormat实例,进行简单的转换日期功能。
DateFormat提供的转换格式分为两种,一种是只转换Date部分,一种是转换Date和Time两个部分。
先看只转换Date部分的,从代码中能看出,你可以调用getDateInstance()这个系列方法,最后其实调用的都是get方法。
这个系列主要需要注意的两个参数就是style参数和Locale参数。这个style参数,这个参数决定了你的日期格式的长度,默认的的default是2,从DateFormat类中定义了几个常数,这个2就是medium,也可以设置为LONG。而最后一个参数就是地区了,决定了你的日期输出语言,中文就是年月日,英文就是单词了,我下面用一个例子说明不同参数的区别。
DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, Locale.SIMPLIFIED_CHINESE);
DateFormat format2 = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE);
DateFormat format3 = DateFormat.getDateInstance(DateFormat.LONG, Locale.SIMPLIFIED_CHINESE);
DateFormat format4 = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ENGLISH);
DateFormat format5 = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.ENGLISH);
DateFormat format6 = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH);
try {
System.out.println("format : " +format.format(new Date()));
System.out.println("format2 : "+format2.format(new Date()));
System.out.println("format3 : "+format3.format(new Date()));
System.out.println("format4 : "+format4.format(new Date()));
System.out.println("format5 : "+format5.format(new Date()));
System.out.println("format6 : "+format6.format(new Date()));
} catch (Exception e) {
e.printStackTrace();
}
输出:
format : 16-11-2
format2 : 2016-11-2
format3 : 2016年11月2日
format4 : 11/2/16
format5 : Nov 2, 2016
format6 : November 2, 2016
上面的例子,对构造DateFormat的地区和style参数就进行了说明,可以看出当style参数设为SHORT时,中英文的日期表示也是不一样的。
再说说DateFormat的转换日期和时间格式,即转换的Date对象同时包含日期和时间数据。这也是一个系列的方法,与上面说的getDateInstance()系列类似。
与只转换日期格式的Date有个重要的地方就是这里有两个sytle,一个是日期style,一个是时间style,可以根据自己的需要自由组合日期和时间style。
我这里就不说了,有兴趣可以去看看源码。
public final static DateFormat getDateTimeInstance()
{
return get(DEFAULT, DEFAULT, 3, Locale.getDefault(Locale.Category.FORMAT));
}
public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle)
{
return get(timeStyle, dateStyle, 3, Locale.getDefault(Locale.Category.FORMAT));
}
public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
{
return get(timeStyle, dateStyle, 3, aLocale);
}
1 SimpleDateFormat使用
其实这个SimpleDateFormat类是我们日常转换字符串和Date对象最常用的类。
我们如果用DateFormat进行转换格式,只能用它规定的几种格式进行转换,非常的不直观,从字面我们看不出来从Date转出来的日期是怎样的格式。
但是如果用SimpleDateFormat就不一样了,在构造SimpleDateFormat我们可以直接传一个pattern,只要pattern符合要求且是合理的,就可以对Date和字符串进行转换了。
这个其实不用多说,只要来一个例子就都明白了,这里有一个坑,就是m和M是不一样的,m是分钟(minute),而M是月份,用的时候一定要注意。
String dateString = "2013-02-12T12:22:33";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
System.out.println("simpleDate format " + simpleDateFormat.format(new Date()));
try {
System.out.println("simpleDate parse "+ simpleDateFormat.parse(dateString));
} catch (ParseException e) {
e.printStackTrace();
}
输出:
simpleDate format 2016-11-02T09:19:58
simpleDate parse Tue Feb 12 00:22:33 CST 2013
还有就是当parse时,只有你的字符串和SimpleDateFormat的pattern完全匹配才能识别,否则报错。
当我们只需要一个日期时,或从系统取得,或从数据库查询,都可以放入一个Date对象。
当我们需要对Date进行详细分析,获取其中的年月日分秒各个部分的信息,用Calendar类。
当我们需要对一个字符串转成Date对象,或者想让一个Date对象按照我们预期的格式进行输出字符串时,需要用DateFormat类或它的子类SimpleDateFormat。