java日期 - Calender

转载请注明出处~

上回说到,Java的Date类,处理时区问题,不那么给力,而且很多方法,都被deprecated掉了。那么用什么呢,用Calender类。稍微看了一下Calendar的API,大概可以完成Date类之前的所有功能,除了,格式化。格式化的职责,则交给了java.text.DateFormat类。
创建对象
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)+"分");
    }

输出:
Sun Mar 12 15:40:51 CST 2017
Asia/Shanghai:2017年 15时40分
UTC:2017年 7时40分
Japan:2017年 16时40分
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));

输出结果:
1986-05-04 01:00:00
1986-05-04 00:00:00
515520000000
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));

输出:
是否是夏令时true
时间偏移量:9
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()));
输出:
0:2017-03-15 00:00:00
1:2017-01-15 00:00:00
2:2017-03-05 00:00:00
3:2017-03-15 22:00:00
4:2017-03-15 00:05:00
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));
在我们用的日历中,主要就是一个月有多少天,是不固定的,输出:
2008-02-15 00:00:00
一个月的最多多少天:31
一个月的最少多少天:28
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)








  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值