java API -- Date && SimpleDateFormat && Calendar

java中有一种特殊的对象,我们平时都是通过字符串的形式使用它,但在底层却时不时涉及到一些数字运算;除此之外,我们对它的字符串输出形式,往往还是百般挑剔,猜猜它是谁?

:-)没错,它就是java中的Date对象

java API下为我们封装了有关时间和日期操作了相关函数,总结如下:

Date类 – 日期获取的好帮手

首先,学习一个类,大致分为以下几个步骤:

  1. 看类所在package,如果在java.lang包下,则使用时可以省去导包操作。
  2. 接着看类的说明,了解类的大致用途即可。
  3. 查看类的构造函数
  4. 查看类的常用成员方法

好,我们按照上述步骤,来学习一下Date

所在package

package java.util;可知,Date包位于java.util包下,所有使用时需要导包。

类的介绍

查看源码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* The class <code>Date</code> represents a specific instant
* in time, with millisecond precision.
* <p>
* Prior to JDK&nbsp;1.1, the class <code>Date</code> had two additional
* functions. It allowed the interpretation of dates as year, month, day, hour,
* minute, and second values. It also allowed the formatting and parsing
* of date strings. Unfortunately, the API for these functions was not
* amenable to internationalization. As of JDK&nbsp;1.1, the
* <code>Calendar</code> class should be used to convert between dates and time
* fields and the <code>DateFormat</code> class should be used to format and
* parse date strings.
* The corresponding methods in <code>Date</code> are deprecated.
* <p>
*/

首先,它告诉我们:Date是用来表示一个以毫秒为单位的确定时刻的类。Date可以表征“年”、“月”、“日”、“时”、“分”和“秒”。

构造方法

由于一些方法已经过时,这里我们只提及两个常用的构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Allocates a <code>Date</code> object and initializes it so that
* it represents the time at which it was allocated, measured to the
* nearest millisecond.
*
* @see java.lang.System#currentTimeMillis()
*/
public Date() {
this(System.currentTimeMillis());
}

/**
* Allocates a <code>Date</code> object and initializes it to
* represent the specified number of milliseconds since the
* standard base time known as "the epoch", namely January 1,
* 1970, 00:00:00 GMT.
*
* @param date the milliseconds since January 1, 1970, 00:00:00 GMT.
* @see java.lang.System#currentTimeMillis()
*/
public Date(long date) {
fastTime = date; // fastTime 是成员变量
}

空参构造方法Date()用来初始化一个Date对象,其时间基准线为jvm运行环境的当前时间,也就是说这个Date对象的时间值与当前时间一致。

有参构造方法Date(long date)用来初始化一个相对时间基准线,时间值为long dateDate对象。其中,时间基准线为1970-1-1 00:00:00,俗称“计算机元年”。举个例子,Date(1000)代表的是1970-1-1 00:00:01这个时刻。

常用成员方法

这里我们介绍两个常用成员函数setTime()getTime()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Sets this <code>Date</code> object to represent a point in time that is
* <code>time</code> milliseconds after January 1, 1970 00:00:00 GMT.
*
* @param time the number of milliseconds.
*/
public void setTime(long time) {
fastTime = time;
cdate = null;
}

/**
* Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
* represented by this <tt>Date</tt> object.
*
* @return the number of milliseconds since January 1, 1970, 00:00:00 GMT
* represented by this date.
*/
public long getTime() {
return getTimeImpl();
}

private final long getTimeImpl() {
if (cdate != null && !cdate.isNormalized()) {
normalize();
}
return fastTime;
}

其中setTime(long time)用来设置时间,参数time为相对于“1970-1-1 00:00:00”的时间值,单位为毫秒。

getTime()代表的是当前时间的毫秒值,同样是相对于“1970-1-1 00:00:00”。

SimpleDateFormat类 – 日期格式化&解析神器

所在包

SimpleDateFormat位于包java.text下,使用前需要导包。

类概述

1
2
3
4
5
6
/**
* <code>SimpleDateFormat</code> is a concrete class for formatting and
* parsing dates in a locale-sensitive manner. It allows for formatting
* (date &rarr; text), parsing (text &rarr; date), and normalization.
*
* <p>

大意是说:SimpleDateFormat是一个用来格式化Date(输入为Date,输出为String),以及解析Date字符串(输入为String,输出为Date)的具体类。SimpleDateFormat特色是支持用户自定义格式,格式化Date

构造方法

这里我们主要讲解两个常用构造方法,分别是:空参构造方法SimpleDateFormat(),以及有参构造方法SimpleDateFormat(String)

下面是它们的jdk源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Constructs a <code>SimpleDateFormat</code> using the default pattern and
* date format symbols for the default
* {@link java.util.Locale.Category#FORMAT FORMAT} locale.
* <b>Note:</b> This constructor may not support all locales.
* For full coverage, use the factory methods in the {@link DateFormat}
* class.
*/
public SimpleDateFormat() {
this("", Locale.getDefault(Locale.Category.FORMAT)); applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale).getDateTimePattern(SHORT, SHORT, calendar));
}

/**
* Constructs a <code>SimpleDateFormat</code> using the given pattern and
* the default date format symbols for the default
* {@link java.util.Locale.Category#FORMAT FORMAT} locale.
* <b>Note:</b> This constructor may not support all locales.
* For full coverage, use the factory methods in the {@link DateFormat}
* class.
* <p>This is equivalent to calling
* {@link #SimpleDateFormat(String, Locale)
* SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}.
*
* @see java.util.Locale#getDefault(java.util.Locale.Category)
* @see java.util.Locale.Category#FORMAT
* @param pattern the pattern describing the date and time format
* @exception NullPointerException if the given pattern is null
* @exception IllegalArgumentException if the given pattern is invalid
*/
public SimpleDateFormat(String pattern)
{
this(pattern, Locale.getDefault(Locale.Category.FORMAT));
}

其中,空参构造方法SimpleDateFormat()是以默认格式Sun Apr 28 20:45:54 CST 2019构造SimpleDateFormat对象,在之后的format过程中,会以这个格式来生成对应的字符串。

而有参构造方法SimpleDateFormat(String),则是用给定的格式(如:yyyy:MM:dd HH:mm:ss)来生成对应SimpleDateFormat对象,在之后的format过程中,会以这个格式来生成对应的字符串。

常用成员方法

这里要注意一下,我们一般不怎么使用SimpleDateFormat自己特有的成员方法;相反,我们比较爱用其继承于父类DateFormat的方法formate(Date)parse(String)

相关源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Formats a Date into a date/time string.
* @param date the time value to be formatted into a time string.
* @return the formatted time string.
*/
public final String format(Date date)
{
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}


/**
* Parses text from the beginning of the given string to produce a date.
* The method may not use the entire text of the given string.
* <p>
* See the {@link #parse(String, ParsePosition)} method for more information
* on date parsing.
*
* @param source A <code>String</code> whose beginning should be parsed.
* @return A <code>Date</code> parsed from the string.
* @exception ParseException if the beginning of the specified string
* cannot be parsed.
*/
public Date parse(String source) throws ParseException
{
ParsePosition pos = new ParsePosition(0);
Date result = parse(source, pos);
if (pos.index == 0)
throw new ParseException("Unparseable date: \"" + source + "\"" ,
pos.errorIndex);
return result;
}

从以上代码可知,方法format主要完成了Date对象到指定格式字符串的转换;而parse方法主要完成了String对象到Date对象的转化。

Calendar类 – 日期操作的手术刀

所在包

位于java.util包下,使用前需执行导包操作。

类简述

见看一段jdk英文注解:

1
2
3
4
5
6
7
8
9
/**
* The <code>Calendar</code> class is an abstract class that provides methods
* for converting between a specific instant in time and a set of {@link
* #fields calendar fields} such as <code>YEAR</code>, <code>MONTH</code>,
* <code>DAY_OF_MONTH</code>, <code>HOUR</code>, and so on, and for
* manipulating the calendar fields, such as getting the date of the next
* week. An instant in time can be represented by a millisecond value that is
* an offset from the <a name="Epoch"><em>Epoch</em></a>, January 1, 1970
* 00:00:00.000 GMT (Gregorian).

翻译过来,大致意思就是:Calendar是一个在“年”、“月”、“日”、“时”等日历属性,以及时间点(以毫秒为单位)之间进行转化的一个工具类。它可以对“年”、“月”、“日”、“时”等日历属性进行相关算术运算,最终反应在Date中的就是属性值fastTime的变化(即毫秒值的变化)。

构造方法

Calendar构造函数有两个,分别为Calendar()以及Calendar(TimeZone zone, Locale aLocale),但平时使用中我们基本不用。

实例化Calendar类,我们一般选择使用ta的静态成员方法getInstance(),实例化的是其子类对象。原因是:我们使用的某些成员方法如add(int, int),它的实现是在Calendar的子类中完成的,这里用到了多态的向上转型。相应源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Gets a calendar using the default time zone and locale. The
* <code>Calendar</code> returned is based on the current time
* in the default time zone with the default
* {@link Locale.Category#FORMAT FORMAT} locale.
*
* @return a Calendar.
*/
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}

需要注意的是:通过getInstance()方法获取的Calendar子类对象,其时间值是相对“计算机元年”而言的。

常用成员方法

这里,我们主要讲解“查询”、“设置”、“修改”calendar指定字段(filed)的成员方法。它们分别对应为:get(int)方法、set(int,int)方法、add(int,int)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
 /**
* Returns the value of the given calendar field. In lenient mode,
* all calendar fields are normalized. In non-lenient mode, all
* calendar fields are validated and this method throws an
* exception if any calendar fields have out-of-range values. The
* normalization and validation are handled by the
* {@link #complete()} method, which process is calendar
* system dependent.
*
* @param field the given calendar field.
* @return the value for the given calendar field.
* @throws ArrayIndexOutOfBoundsException if the specified field is out of range
* (<code>field &lt; 0 || field &gt;= FIELD_COUNT</code>).
* @see #set(int,int)
* @see #complete()
*/
public int get(int field)
{
complete();
return internalGet(field);
}


/**
* Sets the given calendar field to the given value. The value is not
* interpreted by this method regardless of the leniency mode.
*
* @param field the given calendar field.
* @param value the value to be set for the given calendar field.
* @throws ArrayIndexOutOfBoundsException if the specified field is out of range
* (<code>field &lt; 0 || field &gt;= FIELD_COUNT</code>).
* in non-lenient mode.
* @see #set(int,int,int)
* @see #set(int,int,int,int,int)
* @see #set(int,int,int,int,int,int)
* @see #get(int)
*/
public void set(int field, int value)
{
// If the fields are partially normalized, calculate all the
// fields before changing any fields.
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value);
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}

/**
* Adds or subtracts the specified amount of time to the given calendar field,
* based on the calendar's rules. For example, to subtract 5 days from
* the current time of the calendar, you can achieve it by calling:
* <p><code>add(Calendar.DAY_OF_MONTH, -5)</code>.
*
* @param field the calendar field.
* @param amount the amount of date or time to be added to the field.
* @see #roll(int,int)
* @see #set(int,int)
*/
abstract public void add(int field, int amount);

先说get(int)方法,这个方法主要通过指定int型字段来获取相应字段的整数型值。例如,get(Calendar.YEAR)返回的就是当前系统的年份值。

常用的字段总结如下:

字段名含义
YEAR年份
MONTH月份
DAY_OF_MONTH月份中的日子
HOUR_OF_DAY一天中的小时
MINUTE小时中的分钟
SECOND分钟中的秒

接着谈谈set(int field, int value)方法,这个方法是给指定字段设置给定值。例如,我们可以将月份设置为一月,即set(Calendar.MONTH, Calendar.JANUARY)或者set(Calendar.MONTH, 0)(因为这里采用的是格里高利纪年法,0表示一月)。

最后轮到add(int field, int amount)了,这个方法和set(int ,int )方法的区别有点类似“绝对路径”和“相对路径”的关系。add方法更加偏向于通过运算(相对与当前字段而言)来更改字段值。举个栗子:如果当前月份是二月,我们向将其改为三月,可以这样add(Calendar.MONTH, 1);如果我们想将其改为一月,可以这样add(Calendar.MONTH, -1)