java日期 - Calender
2017-03-13 19:21
134 查看
转载请注明出处~
上回说到,Java的Date类,处理时区问题,不那么给力,而且很多方法,都被deprecated掉了。那么用什么呢,用Calender类。稍微看了一下Calendar的API,大概可以完成Date类之前的所有功能,除了,格式化。格式化的职责,则交给了java.text.DateFormat类。
参考文档:http://www.apihome.cn/api/java/Calendar.html
创建对象
Calendar 并不能使用new来直接创建,而是使用getInstance方法。如果你看源码的话,会发现,这个类是抽象的。也就是说,我们使用的Calender对象,实际上是Calender的子类的对象。
那么为什么要这样?
API的解释:和其他对环境敏感的类一样,Calendar提供了一个getInstance方法,以获得通用的对象。也就是说,你调用了getInstance方法会根据你的环境给你创建一个你需要的对象。这个对象以当前的时间初始化~
那具体根据什么环境?
没错,时区,额,还有文化...
如果调用不带参数的getInstance方法,那会使用默认的时区和Locale(表示了特定的地理、政治和文化地区)。
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
显然,我们也可以获取指定时区和Locale的Calendar对象。Calendar提供了单独指定时区,单独指定Locale,以及同时指定两者的getInstance方法。
TimeZone
指定时区,就需要使用TimeZone的对象,这个就是个时区类,可以表示时区的偏移量,也可以解决夏令时问题。尽量使用没有歧义的ID,反例CST,居然可以代表4个时区(可视为美国,澳大利亚,古巴或中国的标准时间),可以通过TimeZone.getAvailableIDs()方法来查看支持的时区。
System.out.println(TimeZone.getDefault().getID());
Asia/Shanghai//这就是天朝的ID
置于Locale类,好想还挺复杂的~
时区和日本历
TimeZone shanghaiZone = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone UTCZone = TimeZone.getTimeZone("UTC");
TimeZone japanZone = TimeZone.getTimeZone("Japan");
Date d = new Date();
System.out.println(d);
Calendar shanghaiTime = Calendar.getInstance(shanghaiZone);
Calendar UTCTime = Calendar.getInstance(UTCZone);
Calendar japanTime = Calendar.getInstance(japanZone,Locale.JAPAN);
Calendar japanSpecTime = Calendar.getInstance(japanZone,new Locale("ja", "JP", "JP"));
Calendar[] arr = {shanghaiTime,UTCTime,japanTime,japanSpecTime};
for(Calendar cal:arr){
cal.setTime(d);
System.out.println(cal.getTimeZone().getID()+":"
+cal.get(Calendar.YEAR)+"年 "
+cal.get(Calendar.HOUR_OF_DAY)+"时"
+cal.get(Calendar.MINUTE)+"分");
}
输出:
[align=left]Sun Mar 12 15:40:51 CST 2017[/align]
[align=left]Asia/Shanghai:2017年 15时40分[/align]
[align=left]UTC:2017年 7时40分[/align]
[align=left]Japan:2017年 16时40分[/align]
Japan:29年 16时40分
从输出可以看出来,相同的时间,不同的时区,显示出来的是不同的,最后一个japanSpecTime给定了特殊的Locale对象,这有创建了一个日本历,日本历有点类似以前每个王朝,使用年号做纪元~
夏令时
什么是夏令时?
摘自百度百科:
夏时令(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。
就是说,实行了这个制度,某一天可能少一小时,某一天可能多一个小时。
更清楚的了解这个问题,请移步:http://www.cnblogs.com/snake-hand/archive/2013/06/10/3131157.html
而在java中,给我们解决了这个问题,通过 TimeZone 的子类 sun.util.calendar.ZoneInfo 实现的。ZoneInfo 中的数据存放于 %JRE_HOME%/lib/zi 目录中,就像刚才那个文章那样,java记录了以前的夏令时。那如果变了呢,目前有两种方法,一是更新JRE版本,二是安装TZUpdater工具来同步JRE的最新时区信息。-->http://www.oracle.com/technetwork/java/javase/dst-faq-138158.html
如果我想忽略夏令时这个问题呢?!
用GMT时间喽~,GMT呢,跟UTC差不多,没什么特别要求,可以认为是一致的,有兴趣可以自行搜索。总之呢,无论是GMT还是UTC,都是没有夏令时的。
SimpleDateFormat fmtCST = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone shanghaiZone = TimeZone.getTimeZone("Asia/Shanghai");
fmtCST.setTimeZone(shanghaiZone);
Date d1 = fmtCST.parse("1986-05-04 00:00:00");
System.out.println(fmtCST.format(d1));
SimpleDateFormat fmtGMT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone GMT8 = TimeZone.getTimeZone("GMT+8");
fmtGMT.setTimeZone(GMT8);
Date d2 = fmtGMT.parse("1986-05-04 00:00:00");
System.out.println(fmtGMT.format(d2));
输出结果:
[align=left]1986-05-04 01:00:00[/align]
[align=left]1986-05-04 00:00:00[/align]
[align=left]515520000000[/align]
515520000000
1986年5月4日,由于有了夏令时这个问题,没有了0时,所以输出的结果就是1点。第二个中,我们使用了GMT时间,这个是标准时间,我们使用的是东八区(GMT+8),而不是天朝的Asia/Shanghai这个ID,自然一切就正常了,使用GMT时间,就相当于忽略了夏令时。所以喽,还是长整型数字大法好,真是一点歧义都没有。
这里强调一点,输出的时候不要直接输出Date对象,之前已经说过,Date对象使用的是默认时区,你要用它的toString()方法,那么,都是使用本地时区给你格式化的。
强调一点,不要以为夏令时是个bug,这是Java给我们解决了好伐!!!
那我就想处理夏令时,如何判断这一天是不是夏令时呢?
前文已经说过,这个夏令时的功能是TimeZone实现的,所以要到这个类中寻找方法。
SimpleDateFormat fmtCST = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone shanghaiZone = TimeZone.getTimeZone("Asia/Shanghai");
fmtCST.setTimeZone(shanghaiZone);
Date d1 = fmtCST.parse("1986-05-04 00:00:00");
System.out.println("是否是夏令时"+shanghaiZone.inDaylightTime(d1));
System.out.println("时间偏移量:"+ shanghaiZone.getOffset(d1.getTime())/(1000*60*60));
System.out.println(fmtCST.format(d1));
输出:
[align=left]是否是夏令时true[/align]
[align=left]时间偏移量:9[/align]
1986-05-04 01:00:00
inDaylightTime这个方法,就可以判断是否在夏令时。再者,也可以获取这个时区的时区偏移量,常规情况下,我们的偏移量是8,如果偏移量是9,那么这天在夏令时范围内,如果这天的前一天还是8,那么,这天就少一个小时喽。反之,也可以判断是不是多一个小时~~
牛B的set方法
Calendar创建对象,以当前时间做为默认值。要指定日期时间,就需要自己设置,如下:
public void set(int field, int value);
public final void set(int year, int month, int date);
public final void set(int year, int month, int date, int hourOfDay, int minute);
public final void set(int year, int month, int date, int hourOfDay, int minute, int second);
设置日期
除了set(int,int) ,其余的都很简单啦,就是字面意思 ,简单说一下set(int,int) ,这个用起来有那么一丢丢蛋疼。你设置什么,需要先指定field。这个在Calendar类中,都有定义。什么YEAR,MONTH,DATE等等等等...总之,很强大。就是,有点太强大,容易糊涂。搞定它,就由衷的佩服,真牛B,搞不定,就会糊涂,设计的什么玩应~
API中管这个叫“日历字段定义的革命(Calendar Fields Resolution)“。
你可以这样定义一个日期:
YEAR + MONTH + DAY_OF_MONTH
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
YEAR + DAY_OF_YEAR
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
你可以这样定义一天中的时间:
HOUR_OF_DAY
AM_PM + HOUR
如果各种各样的定义方式,你混合使用,冲突了,那么,越近设置的优先级越高。
分别举栗子吧:
YEAR + MONTH + DAY_OF_MONTH
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.setMinimalDaysInFirstWeek(1);
cld.setFirstDayOfWeek(Calendar.MONDAY);
cld.clear();
//YEAR + MONTH + DAY_OF_MONTH
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.MONDAY, Calendar.JANUARY);//注意月份实际上从0开始的
cld.set(Calendar.DAY_OF_MONTH, 15);
System.out.println("YEAR + MONTH + DAY_OF_MONTH");
System.out.println(fmt.format(cld.getTime()));
就是年月日,没啥好说的,注意一月从0开始就可以了:
YEAR + MONTH + DAY_OF_MONTH
2017-01-15 00:00:00
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
//YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.MONDAY, Calendar.JANUARY);//注意月份实际上从0开始的
cld.set(Calendar.WEEK_OF_MONTH, 2);
cld.set(Calendar.DAY_OF_WEEK,Calendar.MONDAY);
System.out.println();
System.out.println("YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK");
System.out.println("2017 年 1 月 第二周 星期一");
System.out.println(fmt.format(cld.getTime()));
这个栗子中,光说2017年1月第二周,是有歧义的,一个是你第一周至少有几天才算是一周(是不是不够一周就算到上个月!?),另一个是,一周的第一天是星期几,星期一还是星期日?!你需要把这两个问题明确定义下来,才好回答2017年1月第二周是哪一周。看到第一个栗子中的红色加粗部分了吧,用setMinimalDaysInFirstWeek 来设置第一周至少几天,用setFirstDayOfWeek来设置一周的第一天是星期几。有了这个初始化后,输出一下:
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
2017 年 1 月 第二周 星期一
2017-10-02 00:00:00
以下的栗子,也默认有这些初始化。
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
//YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.MONDAY, Calendar.JANUARY);//注意月份实际上从0开始的
cld.set(Calendar.DAY_OF_WEEK_IN_MONTH, 2);
cld.set(Calendar.DAY_OF_WEEK,Calendar.MONDAY);
System.out.println();
System.out.println("YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK");
System.out.println("2017 年 1 月 第二个七天 星期一(就是1月的第二个星期一)");
System.out.println(fmt.format(cld.getTime()));
DAY_OF_WEEK_IN_MONTH是没有歧义的,这个就是代表七天的意思,一周的第一个七天,就是第一周,第二个七天,就是第二周,所以以上程序就是一月的第二个星期一(想一想为啥):
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
2017 年 1 月 第二个七天 星期一(就是1月的第二个星期一)
2017-01-09 00:00:00
YEAR + DAY_OF_YEAR
//YEAR + DAY_OF_YEAR
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.DAY_OF_YEAR, 100);
System.out.println();
System.out.println("YEAR + DAY_OF_YEAR");
System.out.println("2017 年 第100天");
System.out.println(fmt.format(cld.getTime()));
没什么好说的:
YEAR + DAY_OF_YEAR
2017 年 第100天
2017-04-10 00:00:00
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
//YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.WEEK_OF_YEAR, 10);
cld.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
弄清楚了第一周有几天,第一周第一天是星期几,就没啥歧义了:
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
2017 年 第10周 星期一
2017-02-27 00:00:00
HOUR_OF_DAY和AM_PM + HOUR
//HOUR_OF_DAY
cld.set(Calendar.HOUR_OF_DAY, 18);
System.out.println();
System.out.println("HOUR_OF_DAY:18时");
System.out.println(fmt.format(cld.getTime()));
//AM_PM + HOUR
cld.set(Calendar.AM_PM,Calendar.PM);
cld.set(Calendar.HOUR, 6);
System.out.println();
System.out.println("AM_PM + HOUR:6PM");
System.out.println(fmt.format(cld.getTime()));
没错,输出来的是一样一样的:
HOUR_OF_DAY:18时
2017-02-27 18:00:00
AM_PM + HOUR:6PM
2017-02-27 18:00:00
弄清楚后是不是感觉:
大写的牛逼!!!!
不管你有没有,反正我是有这种感觉。
等等
上面红色代码中clear是什么意思?!
清空,就是把时间设定为1970-01-01 00:00:00,这个方法也可以把指定字段清空,把你要清空的字段传进去就是了。
cld.clear(Calendar.MILLISECOND);//清空毫秒
add和roll
下面看Calendar的两个可以改变值的另外两个方法,add和roll。
public void add(int field, int amount);
public void roll(int field, int amount);
add和roll的作用,是在给定的字段上,加减一个值。区别在于,溢出的情况下,处理的方式不同。add会去改变更大的字段,来让结果符合预期。roll则不会,roll就是在一定的范围内“滚动”:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cldAdd = Calendar.getInstance();
cldAdd.set(2017, Calendar.JANUARY, 15, 0, 0 ,0);
cldAdd.setFirstDayOfWeek(Calendar.MONDAY);
Calendar cldRoll = (Calendar) cldAdd.clone();
cldAdd.add(Calendar.DATE, -30);
cldRoll.roll(Calendar.DATE, -30);
System.out.println(fmt.format(cldAdd.getTime()));
System.out.println(fmt.format(cldRoll.getTime()));
结果:
2016-12-16 00:00:00
2017-01-16 00:00:00
roll不会影响他的上一级,但是这个层级关系是什么样的呢?
试试吧
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2017, Calendar.MARCH, 15, 0, 0 ,0);
cld.setFirstDayOfWeek(Calendar.MONDAY);
Calendar cld1 = (Calendar) cld.clone();
Calendar cld2 = (Calendar) cld.clone();
Calendar cld3 = (Calendar) cld.clone();
Calendar cld4 = (Calendar) cld.clone();
Calendar cld5 = (Calendar) cld.clone();
cld1.roll(Calendar.MONTH, 10);
cld2.roll(Calendar.DATE, 300);
cld3.roll(Calendar.HOUR_OF_DAY, 46);
cld4.roll(Calendar.MINUTE, 65);
cld5.roll(Calendar.SECOND, 65);
System.out.println("0:" + fmt.format(cld.getTime()));
System.out.println("1:" + fmt.format(cld1.getTime()));
System.out.println("2:" + fmt.format(cld2.getTime()));
System.out.println("3:" + fmt.format(cld3.getTime()));
System.out.println("4:" + fmt.format(cld4.getTime()));
System.out.println("5:" + fmt.format(cld5.getTime()));
输出:
[align=left]0:2017-03-15 00:00:00[/align]
[align=left]1:2017-01-15 00:00:00[/align]
[align=left]2:2017-03-05 00:00:00[/align]
[align=left]3:2017-03-15 22:00:00[/align]
[align=left]4:2017-03-15 00:05:00[/align]
5:2017-03-15 00:00:05
结论:年--> 月--> 日--> 时--> 分--> 秒 这个方法的roll这个单词用的很传神啊!
get方法
有set,也就有get,用法类似
public int get(int field);
重点来了:
public int getMinimum(int field)
public int getActualMinimum(int field)
public int getGreatestMinimum(int field)
public int getMaximum(int field)
public int getLeastMaximum(int field)
public int getActualMaximum(int field)
getMaximum和getLeastMaximum跟日历有关,跟具体时间无关。获取这种日历的某字段的最大和最小最大值(好绕口)~~。而getActualMinimum则跟具体日期有关!!!
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2008, Calendar.FEBRUARY, 15, 0, 0 ,0);
System.out.println(fmt.format(cld.getTime()));
System.out.println("一个月的最多多少天:"+cld.getMaximum(Calendar.DAY_OF_MONTH));
System.out.println("一个月的最少多少天:"+cld.getLeastMaximum(Calendar.DAY_OF_MONTH));
System.out.println("2008年2月有多少天:"+cld.getActualMaximum(Calendar.DAY_OF_MONTH));
在我们用的日历中,主要就是一个月有多少天,是不固定的,输出:
[align=left]2008-02-15 00:00:00[/align]
[align=left]一个月的最多多少天:31[/align]
[align=left]一个月的最少多少天:28[/align]
2008年2月有多少天:29
至于getMininum这几个函数,都差不多,因为我们平常用的日历,这个东西没什么不同~~我看源码,好像对1582年儒略历和高里历有过处理,蛋是,我循环了一下,这一年也没发现什么不同。谁发现了可以给我留言。
如何获取一个月的最后一天~
熟悉了API后,这个问题非常好解决:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2008, Calendar.FEBRUARY, 15, 0, 0 ,0);
cld.set(Calendar.DATE, cld.getActualMaximum(Calendar.DATE));
System.out.println(fmt.format(cld.getTime()));
输出;
2008-02-29 00:00:00
weekYear
weekYear这个东西,Calendar是1.7之后才开始支持的,看源码都是since1.7。
那什么是weekYear呢?
在weekYear中,每一年的第一周的第一天,算是这一年的开始。比如,2017年(星期一算第一天),2016-12-26算作是2017年的第一天。
weekYear相关的方法:
public final boolean isWeekDateSupported();//我们用的高里历是支持的
public int getWeekYear();//获取在weekYear中代表的年
public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek);//使用weekYear的方式设置值
public int getWeeksInWeekYear();//获取weekYear有多少个星期
猜得到,一周的第一天对这个weekYear必然是有影响的!!
上代码:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2016, Calendar.DECEMBER, 31, 0, 0 ,0);
cld.setFirstDayOfWeek(Calendar.MONDAY);
System.out.println("星期一是一周的第一天");
System.out.println(fmt.format(cld.getTime()));
System.out.println(cld.getWeekYear() + " WeekYear有"+cld.getWeeksInWeekYear()+"周");
cld.setWeekDate(2017, 1, Calendar.MONDAY);
System.out.println("2017 WeekYear的第一天是:"+ fmt.format(cld.getTime()));
System.out.println();
cld.set(2016, Calendar.DECEMBER, 31, 0, 0 ,0);
cld.setFirstDayOfWeek(Calendar.SUNDAY);
System.out.println("星期日是一周的第一天");
System.out.println(fmt.format(cld.getTime()));
System.out.println(cld.getWeekYear()+" WeekYear有"+cld.getWeeksInWeekYear()+"周");
cld.setWeekDate(2017, 1, Calendar.SUNDAY);
System.out.println("2017 WeekYear的第一天是:"+ fmt.format(cld.getTime()));
输出:
星期一是一周的第一天
2016-12-31 00:00:00
2017 WeekYear有53周
2017 WeekYear的第一天是:2016-12-26 00:00:00
星期日是一周的第一天
2016-12-31 00:00:00
2016 WeekYear有53周
2017 WeekYear的第一天是:2017-01-01 00:00:00
在星期一是第一天的情况下,从2016年12月26日就算作是2017了,星期日是第一天的情况下,则是2017年1月1日啦(恰好是周日)。
日期比较
Calendar对象的比较,和Date比类似,有before和after两个方法。也实现了equal方法和Comparable接口。
lenient
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 会抛出一个异常。
与Date转换
即使有了新的类,旧的也不能不用啊,要兼容啊。那么Calendar必然有可以和Date互相转换的方法,也该有获取和设置长整型的数字的方法:
public final Date getTime()
public final void setTime(Date date)
public long getTimeInMillis()
public void setTimeInMillis(long millis)
上回说到,Java的Date类,处理时区问题,不那么给力,而且很多方法,都被deprecated掉了。那么用什么呢,用Calender类。稍微看了一下Calendar的API,大概可以完成Date类之前的所有功能,除了,格式化。格式化的职责,则交给了java.text.DateFormat类。
参考文档:http://www.apihome.cn/api/java/Calendar.html
创建对象
Calendar 并不能使用new来直接创建,而是使用getInstance方法。如果你看源码的话,会发现,这个类是抽象的。也就是说,我们使用的Calender对象,实际上是Calender的子类的对象。
那么为什么要这样?
API的解释:和其他对环境敏感的类一样,Calendar提供了一个getInstance方法,以获得通用的对象。也就是说,你调用了getInstance方法会根据你的环境给你创建一个你需要的对象。这个对象以当前的时间初始化~
那具体根据什么环境?
没错,时区,额,还有文化...
如果调用不带参数的getInstance方法,那会使用默认的时区和Locale(表示了特定的地理、政治和文化地区)。
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
显然,我们也可以获取指定时区和Locale的Calendar对象。Calendar提供了单独指定时区,单独指定Locale,以及同时指定两者的getInstance方法。
TimeZone
指定时区,就需要使用TimeZone的对象,这个就是个时区类,可以表示时区的偏移量,也可以解决夏令时问题。尽量使用没有歧义的ID,反例CST,居然可以代表4个时区(可视为美国,澳大利亚,古巴或中国的标准时间),可以通过TimeZone.getAvailableIDs()方法来查看支持的时区。
System.out.println(TimeZone.getDefault().getID());
Asia/Shanghai//这就是天朝的ID
置于Locale类,好想还挺复杂的~
时区和日本历
TimeZone shanghaiZone = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone UTCZone = TimeZone.getTimeZone("UTC");
TimeZone japanZone = TimeZone.getTimeZone("Japan");
Date d = new Date();
System.out.println(d);
Calendar shanghaiTime = Calendar.getInstance(shanghaiZone);
Calendar UTCTime = Calendar.getInstance(UTCZone);
Calendar japanTime = Calendar.getInstance(japanZone,Locale.JAPAN);
Calendar japanSpecTime = Calendar.getInstance(japanZone,new Locale("ja", "JP", "JP"));
Calendar[] arr = {shanghaiTime,UTCTime,japanTime,japanSpecTime};
for(Calendar cal:arr){
cal.setTime(d);
System.out.println(cal.getTimeZone().getID()+":"
+cal.get(Calendar.YEAR)+"年 "
+cal.get(Calendar.HOUR_OF_DAY)+"时"
+cal.get(Calendar.MINUTE)+"分");
}
输出:
[align=left]Sun Mar 12 15:40:51 CST 2017[/align]
[align=left]Asia/Shanghai:2017年 15时40分[/align]
[align=left]UTC:2017年 7时40分[/align]
[align=left]Japan:2017年 16时40分[/align]
Japan:29年 16时40分
从输出可以看出来,相同的时间,不同的时区,显示出来的是不同的,最后一个japanSpecTime给定了特殊的Locale对象,这有创建了一个日本历,日本历有点类似以前每个王朝,使用年号做纪元~
夏令时
什么是夏令时?
摘自百度百科:
夏时令(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。
就是说,实行了这个制度,某一天可能少一小时,某一天可能多一个小时。
更清楚的了解这个问题,请移步:http://www.cnblogs.com/snake-hand/archive/2013/06/10/3131157.html
而在java中,给我们解决了这个问题,通过 TimeZone 的子类 sun.util.calendar.ZoneInfo 实现的。ZoneInfo 中的数据存放于 %JRE_HOME%/lib/zi 目录中,就像刚才那个文章那样,java记录了以前的夏令时。那如果变了呢,目前有两种方法,一是更新JRE版本,二是安装TZUpdater工具来同步JRE的最新时区信息。-->http://www.oracle.com/technetwork/java/javase/dst-faq-138158.html
如果我想忽略夏令时这个问题呢?!
用GMT时间喽~,GMT呢,跟UTC差不多,没什么特别要求,可以认为是一致的,有兴趣可以自行搜索。总之呢,无论是GMT还是UTC,都是没有夏令时的。
SimpleDateFormat fmtCST = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone shanghaiZone = TimeZone.getTimeZone("Asia/Shanghai");
fmtCST.setTimeZone(shanghaiZone);
Date d1 = fmtCST.parse("1986-05-04 00:00:00");
System.out.println(fmtCST.format(d1));
SimpleDateFormat fmtGMT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone GMT8 = TimeZone.getTimeZone("GMT+8");
fmtGMT.setTimeZone(GMT8);
Date d2 = fmtGMT.parse("1986-05-04 00:00:00");
System.out.println(fmtGMT.format(d2));
输出结果:
[align=left]1986-05-04 01:00:00[/align]
[align=left]1986-05-04 00:00:00[/align]
[align=left]515520000000[/align]
515520000000
1986年5月4日,由于有了夏令时这个问题,没有了0时,所以输出的结果就是1点。第二个中,我们使用了GMT时间,这个是标准时间,我们使用的是东八区(GMT+8),而不是天朝的Asia/Shanghai这个ID,自然一切就正常了,使用GMT时间,就相当于忽略了夏令时。所以喽,还是长整型数字大法好,真是一点歧义都没有。
这里强调一点,输出的时候不要直接输出Date对象,之前已经说过,Date对象使用的是默认时区,你要用它的toString()方法,那么,都是使用本地时区给你格式化的。
强调一点,不要以为夏令时是个bug,这是Java给我们解决了好伐!!!
那我就想处理夏令时,如何判断这一天是不是夏令时呢?
前文已经说过,这个夏令时的功能是TimeZone实现的,所以要到这个类中寻找方法。
SimpleDateFormat fmtCST = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone shanghaiZone = TimeZone.getTimeZone("Asia/Shanghai");
fmtCST.setTimeZone(shanghaiZone);
Date d1 = fmtCST.parse("1986-05-04 00:00:00");
System.out.println("是否是夏令时"+shanghaiZone.inDaylightTime(d1));
System.out.println("时间偏移量:"+ shanghaiZone.getOffset(d1.getTime())/(1000*60*60));
System.out.println(fmtCST.format(d1));
输出:
[align=left]是否是夏令时true[/align]
[align=left]时间偏移量:9[/align]
1986-05-04 01:00:00
inDaylightTime这个方法,就可以判断是否在夏令时。再者,也可以获取这个时区的时区偏移量,常规情况下,我们的偏移量是8,如果偏移量是9,那么这天在夏令时范围内,如果这天的前一天还是8,那么,这天就少一个小时喽。反之,也可以判断是不是多一个小时~~
牛B的set方法
Calendar创建对象,以当前时间做为默认值。要指定日期时间,就需要自己设置,如下:
public void set(int field, int value);
public final void set(int year, int month, int date);
public final void set(int year, int month, int date, int hourOfDay, int minute);
public final void set(int year, int month, int date, int hourOfDay, int minute, int second);
设置日期
除了set(int,int) ,其余的都很简单啦,就是字面意思 ,简单说一下set(int,int) ,这个用起来有那么一丢丢蛋疼。你设置什么,需要先指定field。这个在Calendar类中,都有定义。什么YEAR,MONTH,DATE等等等等...总之,很强大。就是,有点太强大,容易糊涂。搞定它,就由衷的佩服,真牛B,搞不定,就会糊涂,设计的什么玩应~
API中管这个叫“日历字段定义的革命(Calendar Fields Resolution)“。
你可以这样定义一个日期:
YEAR + MONTH + DAY_OF_MONTH
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
YEAR + DAY_OF_YEAR
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
你可以这样定义一天中的时间:
HOUR_OF_DAY
AM_PM + HOUR
如果各种各样的定义方式,你混合使用,冲突了,那么,越近设置的优先级越高。
分别举栗子吧:
YEAR + MONTH + DAY_OF_MONTH
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.setMinimalDaysInFirstWeek(1);
cld.setFirstDayOfWeek(Calendar.MONDAY);
cld.clear();
//YEAR + MONTH + DAY_OF_MONTH
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.MONDAY, Calendar.JANUARY);//注意月份实际上从0开始的
cld.set(Calendar.DAY_OF_MONTH, 15);
System.out.println("YEAR + MONTH + DAY_OF_MONTH");
System.out.println(fmt.format(cld.getTime()));
就是年月日,没啥好说的,注意一月从0开始就可以了:
YEAR + MONTH + DAY_OF_MONTH
2017-01-15 00:00:00
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
//YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.MONDAY, Calendar.JANUARY);//注意月份实际上从0开始的
cld.set(Calendar.WEEK_OF_MONTH, 2);
cld.set(Calendar.DAY_OF_WEEK,Calendar.MONDAY);
System.out.println();
System.out.println("YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK");
System.out.println("2017 年 1 月 第二周 星期一");
System.out.println(fmt.format(cld.getTime()));
这个栗子中,光说2017年1月第二周,是有歧义的,一个是你第一周至少有几天才算是一周(是不是不够一周就算到上个月!?),另一个是,一周的第一天是星期几,星期一还是星期日?!你需要把这两个问题明确定义下来,才好回答2017年1月第二周是哪一周。看到第一个栗子中的红色加粗部分了吧,用setMinimalDaysInFirstWeek 来设置第一周至少几天,用setFirstDayOfWeek来设置一周的第一天是星期几。有了这个初始化后,输出一下:
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
2017 年 1 月 第二周 星期一
2017-10-02 00:00:00
以下的栗子,也默认有这些初始化。
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
//YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.MONDAY, Calendar.JANUARY);//注意月份实际上从0开始的
cld.set(Calendar.DAY_OF_WEEK_IN_MONTH, 2);
cld.set(Calendar.DAY_OF_WEEK,Calendar.MONDAY);
System.out.println();
System.out.println("YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK");
System.out.println("2017 年 1 月 第二个七天 星期一(就是1月的第二个星期一)");
System.out.println(fmt.format(cld.getTime()));
DAY_OF_WEEK_IN_MONTH是没有歧义的,这个就是代表七天的意思,一周的第一个七天,就是第一周,第二个七天,就是第二周,所以以上程序就是一月的第二个星期一(想一想为啥):
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
2017 年 1 月 第二个七天 星期一(就是1月的第二个星期一)
2017-01-09 00:00:00
YEAR + DAY_OF_YEAR
//YEAR + DAY_OF_YEAR
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.DAY_OF_YEAR, 100);
System.out.println();
System.out.println("YEAR + DAY_OF_YEAR");
System.out.println("2017 年 第100天");
System.out.println(fmt.format(cld.getTime()));
没什么好说的:
YEAR + DAY_OF_YEAR
2017 年 第100天
2017-04-10 00:00:00
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
//YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
cld.set(Calendar.YEAR, 2017);
cld.set(Calendar.WEEK_OF_YEAR, 10);
cld.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
弄清楚了第一周有几天,第一周第一天是星期几,就没啥歧义了:
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
2017 年 第10周 星期一
2017-02-27 00:00:00
HOUR_OF_DAY和AM_PM + HOUR
//HOUR_OF_DAY
cld.set(Calendar.HOUR_OF_DAY, 18);
System.out.println();
System.out.println("HOUR_OF_DAY:18时");
System.out.println(fmt.format(cld.getTime()));
//AM_PM + HOUR
cld.set(Calendar.AM_PM,Calendar.PM);
cld.set(Calendar.HOUR, 6);
System.out.println();
System.out.println("AM_PM + HOUR:6PM");
System.out.println(fmt.format(cld.getTime()));
没错,输出来的是一样一样的:
HOUR_OF_DAY:18时
2017-02-27 18:00:00
AM_PM + HOUR:6PM
2017-02-27 18:00:00
弄清楚后是不是感觉:
大写的牛逼!!!!
不管你有没有,反正我是有这种感觉。
等等
上面红色代码中clear是什么意思?!
清空,就是把时间设定为1970-01-01 00:00:00,这个方法也可以把指定字段清空,把你要清空的字段传进去就是了。
cld.clear(Calendar.MILLISECOND);//清空毫秒
add和roll
下面看Calendar的两个可以改变值的另外两个方法,add和roll。
public void add(int field, int amount);
public void roll(int field, int amount);
add和roll的作用,是在给定的字段上,加减一个值。区别在于,溢出的情况下,处理的方式不同。add会去改变更大的字段,来让结果符合预期。roll则不会,roll就是在一定的范围内“滚动”:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cldAdd = Calendar.getInstance();
cldAdd.set(2017, Calendar.JANUARY, 15, 0, 0 ,0);
cldAdd.setFirstDayOfWeek(Calendar.MONDAY);
Calendar cldRoll = (Calendar) cldAdd.clone();
cldAdd.add(Calendar.DATE, -30);
cldRoll.roll(Calendar.DATE, -30);
System.out.println(fmt.format(cldAdd.getTime()));
System.out.println(fmt.format(cldRoll.getTime()));
结果:
2016-12-16 00:00:00
2017-01-16 00:00:00
roll不会影响他的上一级,但是这个层级关系是什么样的呢?
试试吧
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2017, Calendar.MARCH, 15, 0, 0 ,0);
cld.setFirstDayOfWeek(Calendar.MONDAY);
Calendar cld1 = (Calendar) cld.clone();
Calendar cld2 = (Calendar) cld.clone();
Calendar cld3 = (Calendar) cld.clone();
Calendar cld4 = (Calendar) cld.clone();
Calendar cld5 = (Calendar) cld.clone();
cld1.roll(Calendar.MONTH, 10);
cld2.roll(Calendar.DATE, 300);
cld3.roll(Calendar.HOUR_OF_DAY, 46);
cld4.roll(Calendar.MINUTE, 65);
cld5.roll(Calendar.SECOND, 65);
System.out.println("0:" + fmt.format(cld.getTime()));
System.out.println("1:" + fmt.format(cld1.getTime()));
System.out.println("2:" + fmt.format(cld2.getTime()));
System.out.println("3:" + fmt.format(cld3.getTime()));
System.out.println("4:" + fmt.format(cld4.getTime()));
System.out.println("5:" + fmt.format(cld5.getTime()));
输出:
[align=left]0:2017-03-15 00:00:00[/align]
[align=left]1:2017-01-15 00:00:00[/align]
[align=left]2:2017-03-05 00:00:00[/align]
[align=left]3:2017-03-15 22:00:00[/align]
[align=left]4:2017-03-15 00:05:00[/align]
5:2017-03-15 00:00:05
结论:年--> 月--> 日--> 时--> 分--> 秒 这个方法的roll这个单词用的很传神啊!
get方法
有set,也就有get,用法类似
public int get(int field);
重点来了:
public int getMinimum(int field)
public int getActualMinimum(int field)
public int getGreatestMinimum(int field)
public int getMaximum(int field)
public int getLeastMaximum(int field)
public int getActualMaximum(int field)
getMaximum和getLeastMaximum跟日历有关,跟具体时间无关。获取这种日历的某字段的最大和最小最大值(好绕口)~~。而getActualMinimum则跟具体日期有关!!!
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2008, Calendar.FEBRUARY, 15, 0, 0 ,0);
System.out.println(fmt.format(cld.getTime()));
System.out.println("一个月的最多多少天:"+cld.getMaximum(Calendar.DAY_OF_MONTH));
System.out.println("一个月的最少多少天:"+cld.getLeastMaximum(Calendar.DAY_OF_MONTH));
System.out.println("2008年2月有多少天:"+cld.getActualMaximum(Calendar.DAY_OF_MONTH));
在我们用的日历中,主要就是一个月有多少天,是不固定的,输出:
[align=left]2008-02-15 00:00:00[/align]
[align=left]一个月的最多多少天:31[/align]
[align=left]一个月的最少多少天:28[/align]
2008年2月有多少天:29
至于getMininum这几个函数,都差不多,因为我们平常用的日历,这个东西没什么不同~~我看源码,好像对1582年儒略历和高里历有过处理,蛋是,我循环了一下,这一年也没发现什么不同。谁发现了可以给我留言。
如何获取一个月的最后一天~
熟悉了API后,这个问题非常好解决:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2008, Calendar.FEBRUARY, 15, 0, 0 ,0);
cld.set(Calendar.DATE, cld.getActualMaximum(Calendar.DATE));
System.out.println(fmt.format(cld.getTime()));
输出;
2008-02-29 00:00:00
weekYear
weekYear这个东西,Calendar是1.7之后才开始支持的,看源码都是since1.7。
那什么是weekYear呢?
在weekYear中,每一年的第一周的第一天,算是这一年的开始。比如,2017年(星期一算第一天),2016-12-26算作是2017年的第一天。
weekYear相关的方法:
public final boolean isWeekDateSupported();//我们用的高里历是支持的
public int getWeekYear();//获取在weekYear中代表的年
public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek);//使用weekYear的方式设置值
public int getWeeksInWeekYear();//获取weekYear有多少个星期
猜得到,一周的第一天对这个weekYear必然是有影响的!!
上代码:
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cld = Calendar.getInstance();
cld.set(2016, Calendar.DECEMBER, 31, 0, 0 ,0);
cld.setFirstDayOfWeek(Calendar.MONDAY);
System.out.println("星期一是一周的第一天");
System.out.println(fmt.format(cld.getTime()));
System.out.println(cld.getWeekYear() + " WeekYear有"+cld.getWeeksInWeekYear()+"周");
cld.setWeekDate(2017, 1, Calendar.MONDAY);
System.out.println("2017 WeekYear的第一天是:"+ fmt.format(cld.getTime()));
System.out.println();
cld.set(2016, Calendar.DECEMBER, 31, 0, 0 ,0);
cld.setFirstDayOfWeek(Calendar.SUNDAY);
System.out.println("星期日是一周的第一天");
System.out.println(fmt.format(cld.getTime()));
System.out.println(cld.getWeekYear()+" WeekYear有"+cld.getWeeksInWeekYear()+"周");
cld.setWeekDate(2017, 1, Calendar.SUNDAY);
System.out.println("2017 WeekYear的第一天是:"+ fmt.format(cld.getTime()));
输出:
星期一是一周的第一天
2016-12-31 00:00:00
2017 WeekYear有53周
2017 WeekYear的第一天是:2016-12-26 00:00:00
星期日是一周的第一天
2016-12-31 00:00:00
2016 WeekYear有53周
2017 WeekYear的第一天是:2017-01-01 00:00:00
在星期一是第一天的情况下,从2016年12月26日就算作是2017了,星期日是第一天的情况下,则是2017年1月1日啦(恰好是周日)。
日期比较
Calendar对象的比较,和Date比类似,有before和after两个方法。也实现了equal方法和Comparable接口。
lenient
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 会抛出一个异常。
与Date转换
即使有了新的类,旧的也不能不用啊,要兼容啊。那么Calendar必然有可以和Date互相转换的方法,也该有获取和设置长整型的数字的方法:
public final Date getTime()
public final void setTime(Date date)
public long getTimeInMillis()
public void setTimeInMillis(long millis)
相关文章推荐
- Java计算两个日期相差几年几月几时几分几秒,Calender方式实现
- Java 日期类的使用:Date,Calender,SimpleDateFormat的简单使用
- 3.5 java基础总结日期相关类①Date②Calender
- java关于日期的运算等处理方法
- java的日期处理(转载)
- JAVA & 日期
- 计算Java日期
- 使用java.text包格式化数字和日期-ZT
- 计算Java日期
- java中有关日期的显示问题
- Java高级日期概念
- java中有关日期的显示问题
- 记事贴4:Java中的日期处理真是烂!
- java语言时间日期学习
- JAVA小函数-计算日期差
- 使用java.text包格式化数字和日期
- JAVA日期和时间类彻底解决(2)
- 计算Java日期--学习怎样创建和使用日期
- 计算Java日期
- 在JAVA里用函数得到日期,并转换成字符