这几天在研究字符串与指定类型的转换,阴差阳错地研究起 java 的日期应用了,记录下来,希望你有帮助。
- 根据指定格式的字符串,转换为 Date(可研究根据指定格式的字符串,转化为其他指定的类型,如 json 转换为 javaBean)
需要使用到的特殊类:import java.text.ParsePosition;
/**
* <p>Parses a string representing a date by trying a variety of different parsers.
*
* <p>The parse will try each parse pattern in turn.
* A parse is only deemed successful if it parses the whole of the input string.
* If no parse patterns match, a ParseException is thrown.
*
* @param str the date to parse, not null
* @param parsePatterns the date format patterns to use, see SimpleDateFormat, not null
* @param lenient Specify whether or not date/time parsing is to be lenient.
* @return the parsed date
* @throws IllegalArgumentException if the date string or pattern array is null
* @throws ParseException if none of the date patterns were suitable
* @see java.util.Calender#isLenient()
*/
private
static
Date parseDateWithLeniency(
String str, String[] parsePatterns,
boolean
lenient)
throws
ParseException {
if
(str ==
null
|| parsePatterns ==
null
) {
throw
new
IllegalArgumentException(
"Date and Patterns must not be null"
);
}
SimpleDateFormat parser =
new
SimpleDateFormat();
parser.setLenient(lenient);
ParsePosition pos =
new
ParsePosition(
0
);
for
(String parsePattern : parsePatterns) {
String pattern = parsePattern;
// LANG-530 - need to make sure 'ZZ' output doesn't get passed to SimpleDateFormat
if
(parsePattern.endsWith(
"ZZ"
)) {
pattern = pattern.substring(
0
, pattern.length() -
1
);
}
parser.applyPattern(pattern);
pos.setIndex(
0
);
String str2 = str;
// LANG-530 - need to make sure 'ZZ' output doesn't hit SimpleDateFormat as it will ParseException
if
(parsePattern.endsWith(
"ZZ"
)) {
str2 = str.replaceAll(
"([-+][0-9][0-9]):([0-9][0-9])$"
,
"$1$2"
);
}
Date date = parser.parse(str2, pos);
if
(date !=
null
&& pos.getIndex() == str2.length()) {
return
date;
}
}
throw
new
ParseException(
"Unable to parse the date: "
+ str, -
1
);
}
在 java 日期格式里,"ZZ" 代表的是 时区。在java 里,格式化需要借助类 Formate,同时,parse 的时候一般需要借助类 parsePosition,
ParsePosition
是
Format
及其子类所使用的简单类,用来在解析过程中跟踪当前位置。各种
Format
类中的
parseObject
方法要求将
ParsePosition
对象作为一个变量。
日期解释的宽松性:
Calendar
有两种解释日历字段的模式,即 lenient 和 non-lenient。当 Calendar
处于 lenient 模式时,它可接受比它所生成的日历字段范围更大范围内的值。当 Calendar
重新计算日历字段值,以便由 get()
返回这些值时,所有日历字段都被标准化。例如,lenient 模式下的GregorianCalendar
将 MONTH == JANUARY
、DAY_OF_MONTH == 32
解释为 February 1。
当 Calendar
处于 non-lenient 模式时,如果其日历字段中存在任何不一致性,它都会抛出一个异常。例如,GregorianCalendar
总是在 1 与月份的长度之间生成 DAY_OF_MONTH
值。如果已经设置了任何超出范围的字段值,那么在计算时间或日历字段值时,处于 non-lenient 模式下的GregorianCalendar
会抛出一个异常。(在 non-lenient状态下,可以用来检测用户输入的日期是否合法)
- 日期加减(Calendar 的 add && set && roll)
set(int field,int amount) 方法,并不会立刻就计算出新的日期的值,而是在调用 getXxx() , add(),roll() 方法才会计算出新的日期的值,即多次调用set 方法,也不会产生多余的计算。
add(int filed, int amount) 方法,调用 add 方法,会立刻计算出新的日期的值。
roll(int fielde,int amount) 方法,调用 roll 方法,也会立刻计算出新的日期的值,与 add 不同的是,在进行计算完之后,不更改更高字段的值。
举例如下:假设一个日期(Calendar类型)是date 20140831
1)调用date.set(Calendar.Month,Calendar.September),在调用 set(Calendar.Data,1) ,c.getTime() 返回的时间是 20140901
2)调用date.add(Calendar.Month,1), c.getTime() 返回的日期是,20141001。(如果是设置了 c.setLeniency(true),返回的日期是 20141001,如果是设置了 c.setLeniency(false),返回的是 20140930)
3)调用date.roll(Calendar.Month,13),c.setLeniency(flase), c.getTime() 返回的日期是 20140930,请注意这里返回的不是 20141231
对于设置日期的值,可以这样编写代码:
private
static
Date set(Date date,
int
calendarField,
int
amount) {
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"The date must not be null"
);
}
// getInstance() returns a new object, so this method is thread safe.
Calendar c = Calendar.getInstance();
c.setLenient(
false
);// 这里使用严格模式,要求设置日期的值必须正确
c.setTime(date);
c.set(calendarField, amount);
return
c.getTime();
}
|
private
static
Date add(Date date,
int
calendarField,
int
amount) {
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"The date must not be null"
);
}
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(calendarField, amount);
return
c.getTime();
}
|
roll 日期,可以这样编写代码:
private
static
Date roll(Date date,
int
calendarField,
int
amount) {
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"The date must not be null"
);
}
Calendar c = Calendar.getInstance();
c.setTime(date);
c.roll(calendarField, amount);
return
c.getTime();
}
对应的设置年、月、日、时、分、秒,可以这样编写代码:(其他的可以仿写)
public
static
Date setMinutes(Date date,
int
amount) {
return
set(date, Calendar.MINUTE, amount);
}
|
- 借助Calendar 提供的 api ,我们可以获取一些特殊的日期值。如下面这些日期值:
构造一个 20140902 的 Calendar,通过 getFirstDayOfWeek() ,获取本周第一天的值(返回1),通过 get(Calendar.day_of_week) 获取当前日期是本周的第几天,进而获取天数差,再将当前日期减去天数差,即可获取本周第一天的日期值。
函数如下:
public
static
Date getFirstDateOfWeek(Date date){
Calendar c = Calendar.getInstance();
c.setTime(date);
// 获取本周第一天的值
int
firstDayOfWeek = c.getFirstDayOfWeek();
// 当前日期是本周的第几天
int
dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
int
day_diff =
1
;
//天数差的绝对值,20140901~20140903 天数差为3
if
(firstDayOfWeek < dayOfWeek) {
day_diff = Math.abs(dayOfWeek-firstDayOfWeek) +
1
;
}
c.add(Calendar.DATE,
1
-day_diff);
return
c.getTime();
}
|
为了本地化,我们可以用 GregorianCalendar 构造 Calendar,
GregorianCalendar calendar = new GregorianCalendar(Local.getDefault());
类似地,我们可以得到本周最后一天的日期:
public
static
Date getLastDateOfWeek(Date date){
Calendar c = Calendar.getInstance();
c.setTime(date);
// 获取本周第一天的值
int
firstDayOfWeek = c.getFirstDayOfWeek();
// 当前日期是本周的第几天
int
dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
int
day_diff =
1
;
//天数差的绝对值
if
(firstDayOfWeek < dayOfWeek) {
day_diff =Math.abs(dayOfWeek - firstDayOfWeek) +
1
;
}
c.add(Calendar.DATE,
7
-day_diff);
return
c.getTime();
}
再借助
getActualMaximum(int field)
给定此
Calendar
的时间值,返回指定日历字段可能拥有的最大值。
getActualMinimum(int field)
给定此
Calendar
的时间值,返回指定日历字段可能拥有的最小值。借助这两个函数,我们可以获取一天所在月的第一天/最后一天的日期。方法如下:
public
static
Date getFirstDateOfMonth(Date date){
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"the date must not be null"
);
}
Calendar c = Calendar.getInstance();
c.setTime(date);
// get the min date in this month
int
min_day = c.getActualMinimum(Calendar.DATE);
c.set(Calendar.DATE, min_day);
return
c.getTime();
}
public
static
Date getLastDateOfMonth(Date date){
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"the date must not be null"
);
}
Calendar c = Calendar.getInstance();
c.setTime(date);
//get the max date in the month
int
max_day = c.getActualMaximum(Calendar.DATE);
c.set(Calendar.DATE, max_day);
return
c.getTime();
}
聪明的我们,是否马上就会想到,能否通过 getActualMaximum 和 getActualMininum 来获取一周 day_of_week 的最大/小值呢??是的,通过这两个方法,我们可以减少一定的计算量,如下:
public
static
Date getFirstDateOfWeek(Date date){
Calendar c = Calendar.getInstance();
c.setTime(date);
// // 获取本周第一天的值
// int firstDayOfWeek = c.getFirstDayOfWeek();
// // 当前日期是本周的第几天
// int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
//
// int day_diff = 1;//天数差的绝对值,20140901~20140903 天数差为3
//
// if (firstDayOfWeek < dayOfWeek) {
// day_diff = Math.abs(dayOfWeek-firstDayOfWeek) + 1;
// }
//
// c.add(Calendar.DATE,1-day_diff);
int
min_day = c.getActualMinimum(Calendar.DAY_OF_WEEK);
c.set(Calendar.DAY_OF_WEEK,min_day);
return
c.getTime();
}
|
借助 getActualMaxinum 和 getActualMininum 可以获得很多最大/小值,如可以获取月、日、时、分、秒的最后/最初一刻,自然就可以获取一年的起始/结束日期,一天的起始/结束时刻等等,发挥想象了,灵活应用 api,就可以很多我们所需要的函数,组织起来就可以是一个 工具类了。
在这里再谈一下,如果获取季度的起始/结束日期,方法如下:
对于给定的日期,如果我们可以获取到其所在季度的起始月份和结束月份,自然就可以获取到季度的起始日期和结束日期。函数如下:
在这里我们初始化静态数组,用来存储每个月所在季度第一个和最后一个月份的值,注意在 java 里,月份的范围是[0,11]
private
static
final
int
[] seasonFirstMonth =
new
int
[] {
0
,
0
,
0
,
3
,
3
,
3
,
6
,
6
,
6
,
9
,
9
,
9
};
private
static
final
int
[] seasonLastMonth =
new
int
[] {
2
,
2
,
2
,
5
,
5
,
5
,
8
,
8
,
8
,
11
,
11
,
11
};
public
static
Date getFirstDateOfSeason(Date date){
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"the date must not be null"
);
}
Calendar c = Calendar.getInstance();
c.setTime(date);
c.set(Calendar.MONTH, seasonFirstMonth[c.get(Calendar.MONTH)]);
c.set(Calendar.DATE, c.getActualMinimum(Calendar.DATE));
return
c.getTime();
}
public
static
Date getLastDateOfSeason(Date date){
if
(date ==
null
) {
throw
new
IllegalArgumentException(
"the date must not be null"
);
}
Calendar c = Calendar.getInstance();
c.setTime(date);
c.set(Calendar.MONTH, seasonLastMonth[c.get(Calendar.MONTH)]);
c.set(Calendar.DATE, c.getActualMaximum(Calendar.DATE));
return
c.getTime();
}
|
最后谈一下,如何去计算两个日期之间的天数差(规定如下,20140901~20140903,天数差为3),函数如下:
public
static
long
getDaysBetweenTwoDate(Date date1,Date date2){
if
(date1 ==
null
|| date2 ==
null
) {
throw
new
IllegalArgumentException(
"date1 and date2 must not be null"
);
}
Calendar c1 = Calendar.getInstance();
c1.setTime(date1);
Calendar c2 = Calendar.getInstance();
c2.setTime(date2);
long
mills_one_day =
60
*
60
*
24
*
1000
;
return
Math.abs(c1.getTimeInMillis() - c2.getTimeInMillis())/mills_one_day +
1
;
}
附:通过阅读 Java API,借助一些基础的推算逻辑,我们可以合成很多很有用工具类,让我们彼此一起努力。在这一次学习当中,本人更感兴趣的,如何通过指定的字符串格式构造指定的对象(如通过字符串构造日期),又如何通过指定格式输出制定对象(如根据 yyyyMMdd 格式输出 日期对象)。
本文转自peiquan 51CTO博客,原文链接:http://blog.51cto.com/peiquan/1547482