持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情
4 处理日期的类
4.1 Date类
Java提供了Date类来处理日期、时间(此处的Date是指java.util包下的Date类,而不是java.sql包下的Date类),Date对象既包含日期,也包含时间。Date类从JDK 1.0起就开始存在了。但正因为它历史悠久,所以它的大部分构造器、方法都已经过时,不再推荐使用了。
剩下的两个构造器如下所示。
Date():生成一个代表当前日期时间的Date对象。该构造器在底层调用System.currentTimeMillis()获得long整数作为日期参数。Date(long date):根据指定的long型整数来生成一个Date对象。该构造器的参数表示创建的Date对象和GMT 1970年1月1日00:00:00之间的时间差,以毫秒作为计时单位。
与Date构造器相同的是,Date对象的大部分方法也Deprecated了,剩下为数不多的几个方法。
- boolean
after(Date when):测试该日期是否在指定日期when之后。 - boolean
before(Date when):测试该日期是否在指定日期when之前。 - int
compareTo(Date anotherDate):比较两个日期的大小,后面的时间大于前面的时间时返回-1,否则返回1。 - boolean
equals(Object obj):当两个时间表示同一时刻时返回true。 - long
getTime():返回该时间对应的long型整数,即从GMT 1970-01-0100:00:00 到该Date对象之间的时间差,以毫秒作为计时单位。 - void
setTime(long time):设置该Date对象的时间。
因为Date类的很多方法已经不推荐使用了,所以Date类的功能已经被大大削弱了。例如,对时间进行加减运算,获取指定Date对象里年、月、日的所有方法都已被Deprecated,如果需要对日期进行这些运算,则应该使用Calendar工具类。
4.2 Calendar类
Calendar类本身是一个抽象类,它是所有日历类的模板,并提供了一些所有日历通用的方法;但它本身不能直接实例化,程序只能创建Calendar子类的实例,Java本身提供了一个GregorianCalendar类,一个代表GregorianCalendar的子类,它代表了我们通常所说的公历。
Calendar类是一个抽象类,所以不能使用构造器来创建Calendar对象。但它提供了几个静态getInstance()方法来获取Calendar对象,这些方法根据TimeZone,Locale类来获取特定的Calendar,如果不指定TimeZone、Locale,则使用默认的TimeZone、Locale来创建Calendar。
Calendar与Date都是表示日期的工具类,它们直接可以自由转换,如下代码所示。
//创建一个默认的Calendar对象
Calendar calendar=Calendar.getInstance();
//从Calendar 对象中取出Date 对象
Date date=calendar.getTime();
//通过Date对象获得对应的Calendar对象
//因为Calendar/GregorianCalendar没有构造函数可以接收Date对象
//所以必须先获得一个Calendar实例,然后调用其setTime()方法
Calendar calendar2=Calendar.getInstance();
calendar2.setTime(date);
Calendar类提供了大量访问、修改日期时间的方法,常用方法如下。
- void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
- int get(int field):返回指定日历字段的值。
- int getActualMaximum(int field):返回指定日历字段可能拥有的最大值。例如月,最大值为11。
- int getActualMinimum(int field):返回指定日历字段可能拥有的最小值。例如月,最小值为0。
- void roll(int field, int amount):与add()方法类似,区别在于加上amount后超过了该字段所能表示的最大范围时,也不会向上一个字段进位。
- void set(int field, int value):将给定的日历字段设置为给定值。
- void set(int year, int month, int date):设置Calendar对象的年、月、日3个字段的值。
上面的很多方法都需要一个int类型的field参数,field是Calendar类的静态Field,如Calendar.YEAR、Calendar.MONTH等分别代表了年、月、日、小时、分钟、秒等时间字段。需要指出的是,Calendar.MONTH字段代表月份,月份的起始值不是1,而是0,所以要设置8月时,用7而不是8。如下程序示范了Calendar类的常规用法
public class CalendarTest
{
public static void main(String[] args)
{
Calendar c=Calendar.getInstance();
// 取出年
System.out.println(c.get(YEAR));
// 取出月份
System.out.println(c.get(MONTH));
// 取出日
System.out.println(c.get(DATE));
// 分别设置年、月、日、小时、分钟、秒
c.set(2003 , 10 , 23 , 12, 32, 23); //2003-11-23 12:32:23
System.out.println(c.getTime());
// 将Calendar的年前推1年
c.add(YEAR , -1); //2002-11-23 12:32:23
System.out.println(c.getTime());
// 将Calendar的月前推8个月
c.roll(MONTH , -8); //2002-03-23 12:32:23
System.out.println(c.getTime());
}
}
Calendar类还有如下几个注意点。 1.add与roll的区别 add(int field, int amount)的功能非常强大,add主要用于改变Calendar的特定字段的值。如果需要增加某字段的值,则让amount为正数;如果需要减少某字段的值,则让amount为负数即可。 2.设置Calendar的容错性
4.3 TimeZone类
在地理上,地球被划分成24个时区,中国北京时间属于东八区,而程序中对时间的默认实现是以格林威治时间为标准的,这样就产生了8小时的时间差。为了让程序更加通用,可以使用TimeZone设置程序中时间所属的时区,其中TimeZone就代表了时区。
TimeZone是一个抽象类,不能调用其构造器来创建实例,但可以调用它的静态方法:getDefault()或getTimeZone()得到TimeZone实例。
public class TimeZoneTest
{
public static void main(String[] args)
{
//取得Java所支持的所有时区ID
String[] ids=TimeZone.getAvailableIDs();
System.out.println(Arrays.toString(ids));
TimeZone my=TimeZone.getDefault();
//获取系统默认时区的ID:Asia/Shanghai
System.out.println(my.getID());
//获取系统默认时区的名称:中国标准时间
System.out.println(my.getDisplayName());
//获取指定ID的时区名称:纽芬兰标准时间
System.out.println(TimeZone
.getTimeZone("CNT").getDisplayName());
}
}
5 正则表达式
正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作。 String类里也提供了如下几个特殊的方法。
- boolean matches(String regex):判断该字符串是否匹配指定的正则表达式。
- String replaceAll(String regex, String replacement):将该字符串中所有匹配regex的子串替换成replacement。
- String replaceFirst(String regex, String replacement):将该字符串中第一个匹配regex的子串替换成replacement。
- String[] split(String regex):以regex作为分隔符,把该字符串分割成多个子串。
上面这些特殊的方法都依赖于Java提供的正则表达式支持,除此之外,Java还提供了Pattern和Matcher两个类专门用于提供正则表达式支持。
5.1 创建正则表达式
前面已经介绍了,正则表达式就是一个用于匹配字符串的模板,可以匹配一批字符串,所以创建正则表达式就是创建一个特殊的字符串。
正则表达式所支持的合法字符
除此之外,正则表达式中有一些特殊字符,这些特殊字符在正则表达式中有其特殊的用途,比如前面介绍的反斜线(\)。如果需要匹配这些特殊字符,就必须首先将这些字符转义,也就是在前面添加一个反斜线(\)。正则表达式中的特殊字符如下表所示。
正则表达式中的特殊字符
将上面多个字符拼起来,就可以创建一个正则表达式。例如:
"\\u0041\\" //匹配a\
"\\0101\t" //匹配a<制表符>
"\?\[" //匹配?[
上面的正则表达式依然只能匹配单个字符,这是因为还未在正则表达式中使用“通配符”,“通配符”是可以匹配多个字符的特殊字符。正则表达式中的“通配符”远远超出了普通通配符的功能,它被称为预定义字符,正则表达式支持如表所示的预定义字符。
上面的7个预定义字符其实很容易记忆——d是digit的意思,代表数字;s是space的意思,代表空白;w是word的意思,代表单词。d、s、w的大写形式恰好匹配与之相反的字符。
c\wt //可以匹配cat、cbt、cct、c0t、c9t等一批字符串
\d\d\d-\d\d\d-\d\d\d\d //匹配如000-000-0000形式的电话号码
在一些特殊情况下,例如,若只想匹配a~f的字母,或者匹配除了ab之外的所有小写字母,或者匹配中文字符,上面这些预定义字符就无能为力了,此时就需要使用方括号表达式,方括号表达式有如表7.4所示的几种形式。
正则表示还支持圆括号表达式,用于将多个表达式组成一个子表达式,圆括号中可以使用或运算符(|)。例如,正则表达式"(public|protected|private)"用于匹配Java的三个访问控制符其中之一。
5.2 使用正则表达式
一旦在程序中定义了正则表达式,就可以使用Pattern和Matcher来使用正则表达式。
Pattern对象是正则表达式编译后在内存中的表示形式,因此,正则表达式字符串必须先被编译为Pattern对象,然后再利用该Pattern对象创建对应的Matcher对象。执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可共享同一个Pattern对象。
//将一个字符串编译成Pattern对象
Pattern p=Pattern.compile("a*b");
//使用Pattern对象创建Matcher对象
Matcher m=p.matcher("aaaaab");
boolean b=m.matches(); //返回true
上面定义的Pattern对象可以多次重复使用。如果某个正则表达式仅需一次使用,则可直接使用Pattern类的静态matches方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配,如下所示。
boolean b=Pattern.matches("a*b", "aaaaab"); //返回true
上面语句等效于前面的三条语句。但采用这种语句每次都需要重新编译新的Pattern对象,不能重复利用已编译的Pattern对象,所以效率不高。
Pattern是不可变类,可供多个并发线程安全使用。
Matcher类提供了如下几个常用方法。
- find():返回目标字符串中是否包含与Pattern匹配的子串。
- group():返回上一次与Pattern匹配的子串。
- start():返回上一次与Pattern匹配的子串在目标字符串中的开始位置。
- end():返回上一次与Pattern匹配的子串在目标字符串中的结束位置加1。
- lookingAt():返回目标字符串前面部分与Pattern是否匹配。
- matches():返回整个目标字符串与Pattern是否匹配。
- reset(),将现有的Matcher对象应用于一个新的字符序列。
注意:
在Pattern、Matcher类的介绍中经常会看到一个CharSequence接口,该接口代表一个字符序列,其中CharBuffer、String、StringBuffer、StringBuilder都是它的实现类。简单地说,CharSequence代表一个各种表示形式的字符串。
6.7 使用DateFormat格式化日期、时间
与NumberFormat相似的是,DateFormat也是一个抽象类,它也提供了几个工厂方法用于获取DateFormat对象。
getDateInstance():返回一个日期格式器,它格式化后的字符串只有日期,没有时间。该方法可以传入多个参数,用于指定日期样式和Locale等参数;如果不指定这些参数,则使用默认参数。getTimeInstance():返回一个时间格式器,它格式化后的字符串只有时间,没有日期。该方法可以传入多个参数,用于指定时间样式和Locale等参数;如果不指定这些参数,则使用默认参数。getDateTimeInstance():返回一个日期、时间格式器,它格式化后的字符串既有日期,也有时间。该方法可以传入多个参数,用于指定日期样式、时间样式和Locale等参数;如果不指定这些参数,则使用默认参数。
上面3个方法可以指定日期样式、时间样式参数,它们是DateFormat的4个静态常量:FULL、LONG、MEDIUM和SHORT,通过这4个样式参数可以控制生成的格式化字符串。看如下例子程序。
public class DateFormatTest
{
public static void main(String[] args)
{
//需要被格式化的时间
Date dt=new Date();
//创建两个Locale,分别代表中国、美国
Locale[] locales={Locale.CHINA, Locale.US};
DateFormat[] df=new DateFormat[16];
//为上面两个Locale创建16个DateFormat对象
for (int i=0 ; i < locales.length ; i++)
{
df[i * 8]=DateFormat.getDateInstance(SHORT, locales[i]);
df[i * 8 + 1]=DateFormat.getDateInstance(MEDIUM, locales[i]);
df[i * 8 + 2]=DateFormat.getDateInstance(LONG, locales[i]);
df[i * 8 + 3]=DateFormat.getDateInstance(FULL, locales[i]);
df[i * 8 + 4]=DateFormat.getTimeInstance(SHORT, locales[i]);
df[i * 8 + 5]=DateFormat.getTimeInstance(MEDIUM , locales[i]);
df[i * 8 + 6]=DateFormat.getTimeInstance(LONG , locales[i]);
df[i * 8 + 7]=DateFormat.getTimeInstance(FULL , locales[i]);
}
for (int i=0 ; i < locales.length ; i++)
{
switch (i)
{
case 0:
System.out.println("-------中国日期格式--------");
break;
case 1:
System.out.println("-------美国日期格式--------");
break;
}
System.out.println("SHORT格式的日期格式:"
+ df[i * 8].format(dt));
System.out.println("MEDIUM格式的日期格式:"
+ df[i * 8 + 1].format(dt));
System.out.println("LONG格式的日期格式:"
+ df[i * 8 + 2].format(dt));
System.out.println("FULL格式的日期格式:"
+ df[i * 8 + 3].format(dt));
System.out.println("SHORT格式的时间格式:"
+ df[i * 8 + 4].format(dt));
System.out.println("MEDIUM格式的时间格式:"
+ df[i * 8 + 5].format(dt));
System.out.println("LONG格式的时间格式:"
+ df[i * 8 + 6].format(dt));
System.out.println("FULL格式的时间格式:"
+ df[i * 8 + 7].format(dt));
}
}
}
提示: 获得了DateFormat之后,还可以调用它的setLenient(boolean lenient)方法来设置该格式器是否采用严格语法。举例来说,如果采用不严格的日期语法(该方法的参数为true),对于字符串"2004-2-31"将会转换成2004年3月2日;如果采用严格的日期语法,解析该字符串时将抛出异常。
DateFormat的parse()方法可以把一个字符串解析成Date对象,但它要求被解析的字符串必须符合日期字符串的要求,否则可能抛出ParseException异常。例如,如下代码片段:
String str1="2007-12-12";
String str2="2007年12月10日";
//下面输出 Wed Dec 12 00:00:00 CST 2007
System.out.println(DateFormat.getDateInstance().parse(str1));
//下面输出 Mon Dec 10 00:00:00 CST 2007
System.out.println(DateFormat.getDateInstance(LONG).parse(str2));
//下面抛出 ParseException异常
System.out.println(DateFormat.getDateInstance().parse(str2));
6.8 使用SimpleDateFormat格式化日期
前面介绍的DateFormat的parse()方法可以把字符串解析成Date对象,但实际上DataFormat的format()方法不够灵活——它要求被解析的字符串必须满足特定的格式!为了更好地格式化日期、解析日期字符串,Java提供了SimpleDateFormat类。
SimpleDateFormat可以非常灵活地格式化Date,也可以用于解析各种格式的日期字符串。创建SimpleDateFormat对象时需要传入一个pattern字符串,这个pattern不是正则表达式,而是一个日期模板字符串。
public class SimpleDateFormatTest
{
public static void main(String[] args)
throws ParseException
{
Date d=new Date();
//创建一个SimpleDateFormat对象
SimpleDateFormat sdf1=new SimpleDateFormat("Gyyyy年中第D天");
//将d格式化成日期,输出:公元2007年中第354天
String dateStr=sdf1.format(d);
System.out.println(dateStr);
//一个非常特殊的日期字符串
String str="07###三月##21";
SimpleDateFormat sdf2=new SimpleDateFormat("y###MMM##d");
//将日期字符串解析成日期,输出:Wed Mar 21 00:00:00 CST 2007
System.out.println(sdf2.parse(str));
}
}
提示: 如果想知道SimpleDateFormat支持哪些日期、时间占位符,可以查阅API文档中Simple DateFormat类的说明,此处不再赘述。