【Java基础】使用Calendar日历类处理日期时间

简介

Java三次引入处理时间的API,JDK1.0中包含了一个Date类,但大多数方法在java1.1引入Calendar类之后被弃用了。 它的实例都是可变的,而且它的API很难使用,比如月份是从0开始这种反人类的设置。java8引入的java.time API 已经纠正了之前的问题。它已经完全实现了JSR310规范。

  1. Date:java.util.Date包,包含日期,时间,毫秒数。

  2. Calendar/ˈkælɪndə(r)/java.util.Calendar包,abstract修饰 Date的很多方法已经过时,迁移到了Calendar类上

  3. LocalDate/LocalDateTime:java.time.LocalDate/java.time.LocalDateTime包,这个类是不可变的和线程安全的。使用equals方法比较 。

java.util.Date

Date的setXXX()和getXXX()获取Date,Day,Year,Hours,TimezoneOffSet等等之类的方法都已经被弃用了
在这里插入图片描述

java.util.Calendar

Calendar描述
public abstract class Calendar  extends Object implements Serializable, Cloneable, Comparable<Calendar>

该类被abstract所修饰,说明不能通过new的方式来获得实例,因此Calendar提供了一个静态方法getInstance,以获得此类型的一个通用的对象,getInstance方法返回一个Calendar对象(该对象为Calendar的子类对象),其日历字段已由当前日期和时间初始化:

		Calendar calendar = Calendar.getInstance();
		
        calendar.add(Calendar.DATE,-1);//减一天
        
        StringBuffer bf=new StringBuffer();
        bf.append(calendar.get(Calendar.YEAR)+"年");
        bf.append(calendar.get(Calendar.MONTH)+1+"月");
        bf.append(calendar.get(Calendar.DATE)+"日");
        bf.append(calendar.get(Calendar.HOUR_OF_DAY)+"时");//24小时制
        //bf.append(c.get(Calendar.HOUR)+"时");//12小时制
        bf.append(calendar.get(Calendar.MINUTE)+"分");
        bf.append(calendar.get(Calendar.SECOND)+"秒");
        bf.append("本年第"+calendar.get(Calendar.DAY_OF_YEAR)+"天;");
        bf.append("本月第"+calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH)+"周;");
        bf.append("本周第"+calendar.get(Calendar.DAY_OF_WEEK)+"天;");
        bf.append(calendar.get(Calendar.WEEK_OF_MONTH)+";今年第"+calendar.get(Calendar.WEEK_OF_YEAR)+"周");    

Calendar 可以看作是一个抽象类
它的实现,采用了设计模式中的工厂方法。表现在:当我们获取Calendar实例时,Calendar会根据传入的参数来返回相应的Calendar对象。获取Calendar实例,有以下两种方式:

  1. 当我们通过 Calendar.getInstance()获取日历时,默认的是返回的一个GregorianCalendar对象。GregorianCalendar是Calendar的一个实现类,它提供了世界上大多数国家/地区使用的标准日历系统

  2. 当我们通过 Calendar.getInstance(TimeZone timezone, Locale locale)Calendar.getInstance(TimeZone timezone)Calendar.getInstance(Locale locale)获取日历时,是返回“对应时区(zone) 或 地区(local)等所使用的日历”。
    Calendar的使用无非是对年月日时分秒等信息的操作,Calendar实际上是存了一个时间。

为什么说返回的是Calendar的子类对象呢?
因为每个国家地区都有自己的一套日历算法,比如西方国家的第一个星期大部分为星期日,而中国则为星期一,我们来看看getInstance方法获取实例的源码

/**
 * 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 locale.
 *
 * @return a Calendar.
 */
public static Calendar getInstance(){
    Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
    cal.sharedZone = true;
    return cal;
}

其中createCalendar方法就是根据不同国家地区返回对应的日期子类

private static Calendar createCalendar(TimeZone zone,Locale aLocale){
        Calendar cal = null;

        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype == null) {
            // Calendar type is not specified.
            // If the specified locale is a Thai locale,
            // returns a BuddhistCalendar instance.
            if ("th".equals(aLocale.getLanguage())
                    && ("TH".equals(aLocale.getCountry()))) {
                cal = new BuddhistCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        } else if (caltype.equals("japanese")) {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else if (caltype.equals("buddhist")) {
            cal = new BuddhistCalendar(zone, aLocale);
        } else {
            // Unsupported calendar type.
            // Use Gregorian calendar as a fallback.
            cal = new GregorianCalendar(zone, aLocale);
        }

        return cal;
}

为了更加便捷的对日期进行操作,Calendar类对YEARMONTHDAY_OF_MONTHHOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。

Calendar 各个字段的定义和初始化
(字段0) public final static int ERA = 0;
//说明:纪元。
//取值:只能为0 或 1。0表示BC("before Christ",即公元前),1表示AD(拉丁语"Anno Domini",即公元)。

(字段1) public final static int YEAR = 1;
//说明:年。

//--------------------------特殊字段 MONTH--------------------------
(字段2) public final static int MONTH = 2;
//说明:月
//取值:可以为,JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER, UNDECIMBER。
//其中第一个月是 JANUARY,它为 0。

/*也是因为西方文化的原因,一年的第一个月是从“0”开始算起的,一年中12个月份分别是:“0-11”,12指的是下一年的一月份*/
	calendar.set(2016,12,9);
	SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
	String date=format.format(calendar.getTime());//获得的结果是:2017-01-09
//所以为 calendar 设置“月份(MONTH)”时需要 “-1”,通过 calendar 获取“月份(month)”时需要 “+1”
//----------------------------------------------------


//--------------------------特殊字段 WEEK_OF_YEAR--------------------------
(字段3) public final static int WEEK_OF_YEAR = 3;// 一年中的第几周
//说明:当前日期在本年中对应第几个星期。一个年中第一个星期的值为 1。

/*由于西方的一周指的是:星期日-星期六,星期日是一周的第一天,星期六是一周的最后一天,
所以,使用 calendar.get(Calendar.WEEK_OF_YEAR) 时应该注意一周的开始应该是哪一天
如果一周的开始是星期一,那么可以进行如下操作:*/
	Calendar calendar=Calendar.getInstance();
	calendar.set(2016,9,9); //2016-10-09  这一天是星期日
	long week1=calendar.get(Calendar.WEEK_OF_YEAR);
	calendar.setFirstDayOfWeek(Calendar.MONDAY);//设置一周的第一天是星期几
	calendar.set(2016,9,9);  //一定要在calendar.setFirstDayOfWeek();方法后重新设置一遍日期,否则无效
	long week2=calendar.get(Calendar.WEEK_OF_YEAR);

/*注意:一年有52个周,calendar.get(Calendar.WEEK_OF_YEAR);的取值范围是:“1-52”,所以当一年中最后的几天超过52周,进入第53周时,将以下一年的第一周来计算
(跨年问题:跨年的那个星期获取 “WEEK_OF_YEAR” 得到的结果总是“1”,)
如:*/
	calendar.setFirstDayOfWeek(Calendar.MONDAY);
	calendar.set(2016,11,31);             //2016-12-31
	long week=calendar.get(Calendar.WEEK_OF_YEAR);       //week= 1
	//2016-12-31 通过计算得到:1
//----------------------------------------------------

(字段4) public final static int WEEK_OF_MONTH = 4;
//说明:当前日期在本月中对应第几个星期。一个月中第一个星期的值为 1。

(字段5) public final static int DATE = 5;
//说明:日。一个月中第一天的值为 1。

(字段5) public final static int DAY_OF_MONTH = 5;
//说明:同“DATE”,表示“日”。

(字段6) public final static int DAY_OF_YEAR = 6;
//说明:当前日期在本年中对应第几天。一年中第一天的值为 1。

//------------------------特殊字段 DAY_OF_WEEK ---------------------------
(字段7) public final static int DAY_OF_WEEK = 7;
//说明:星期几。
//取值:可以为,SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY 和 SATURDAY。
//     其中,SUNDAY为1,MONDAY为2,依次类推。

	String[] weeks = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
	int index=calendar.get(Calendar.DAY_OF_WEEK);
	String weekDay=weeks[index-1];  
/*返回的是周几,取值为“1-7”  指的是  “1=星期日-7=星期六”,所以获取星期几时需要 “-1”
(这个值跟一周的第一天是星期几无关)*/
//---------------------------------------------------

(字段8) public final static int DAY_OF_WEEK_IN_MONTH = 8;
//说明:当前月中的第几个星期。

(字段9) public final static int AM_PM = 9;
//说明:上午 还是 下午
//取值:可以是AM 或 PM。AM为0,表示上午;PM为1,表示下午。

(字段10) public final static int HOUR = 10;
//说明:指示一天中的第几小时。
//HOUR 用于 12 小时制时钟 (0 - 11)。中午和午夜用 0 表示,不用 12 表示。

(字段11) public final static int HOUR_OF_DAY = 11;
//说明:指示一天中的第几小时。
//HOUR_OF_DAY 用于 24 小时制时钟。例如,在 10:04:15.250 PM 这一时刻,HOUR_OF_DAY 为 22。

(字段12) public final static int MINUTE = 12;
//说明:一小时中的第几分钟。
//例如,在 10:04:15.250 PM这一时刻,MINUTE 为 4。

(字段13) public final static int SECOND = 13;
//说明:一分钟中的第几秒。
//例如,在 10:04:15.250 PM 这一时刻,SECOND 为 15。

(字段14) public final static int MILLISECOND = 14;
//说明:一秒中的第几毫秒。
//例如,在 10:04:15.250 PM 这一时刻,MILLISECOND 为 250。

(字段15) public final static int ZONE_OFFSET = 15;
//说明:毫秒为单位指示距 GMT 的大致偏移量。

(字段16) public final static int DST_OFFSET = 16;
//说明:毫秒为单位指示夏令时的偏移量。

public final static int FIELD_COUNT = 17;
//这17个字段是保存在int数组中。定义如下:

// 保存这17个字段的数组
protected int  fields[];

// 数组的定义函数
protected Calendar(TimeZone zone, Locale aLocale){
 // 初始化“fields数组”
 fields = new int[FIELD_COUNT];
 isSet = new boolean[FIELD_COUNT];
 stamp = new int[FIELD_COUNT];

 this.zone = zone;
 setWeekCountData(aLocale);
}

protected Calendar(TimeZone zone, Locale aLocale) 这是Calendar的构造函数。
它会被它的子类的构造函数调用到,从而新建“保存Calendar的17个字段数据”的数组。
Calendar常用方法
abstract void add(int field, int amount) 根据日历的规则,为给定的日历字段添加或减去指定的时间量。
    boolean after(Object when) 判断此 Calendar 表示的时间是否在指定 Object 表示的时间之后,返回判断结果。
    boolean before(Object when) 判断此 Calendar 表示的时间是否在指定 Object 表示的时间之前,返回判断结果。
    int compareTo(Calendar anotherCalendar) 比较两个 Calendar 对象表示的时间值(从历元至现在的毫秒偏移量)
    boolean equals(Object obj) 将此 Calendar 与指定 Object 比较。
    
    void clear()将此 Calendar 的所日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。
    void clear(int field) 将此 Calendar 的给定日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。
    
    Object clone()创建并返回此对象的一个副本。
    protected void complete()填充日历字段中所有未设置的字段。
    protected abstract void computeFields()将当前毫秒时间值 time 转换为 fields[] 中的日历字段值。
    protected abstract void computeTime()将 fields[] 中的当前日历字段值转换为毫秒时间值 time。

    int get(int field)返回给定日历字段的值。
    int getActualMaximum(int field)给定此 Calendar 的时间值,返回指定日历字段可能拥有的最大值。
    int getActualMinimum(int field)给定此 Calendar 的时间值,返回指定日历字段可能拥有的最小值。
    abstract int getMaximum(int field) 返回此 Calendar 实例给定日历字段的最大值。
    abstract int getMinimum(int field) 返回此 Calendar 实例给定日历字段的最小值。
    abstract int getLeastMaximum(int field) 返回此 Calendar 实例给定日历字段的最低的最大值。
    abstract int getGreatestMinimum(int field)返回此 Calendar 实例给定日历字段的最高的最小值。
    void setMinimalDaysInFirstWeek(int value) 设置一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则使用值 1 调用此方法。
    int getMinimalDaysInFirstWeek()获取一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则此方法将返回 1static Locale[] getAvailableLocales()返回所有语言环境的数组,此类的 getInstance 方法可以为其返回本地化的实例。
    String getDisplayName(int field, int style, Locale locale) 返回给定 style 和 locale 下的日历 field 值的字符串表示形式。
    Map<String,Integer> getDisplayNames(int field, int style, Locale locale) 返回给定 style 和 locale 下包含日历 field 所有名称的 Map 及其相应字段值。
    
    void setFirstDayOfWeek(int value) 设置一星期的第一天是哪一天;例如,在美国,这一天是 SUNDAY,而在法国,这一天是 MONDAY。
    int getFirstDayOfWeek()获取一星期的第一天;例如,在美国,这一天是 SUNDAY,而在法国,这一天是 MONDAY。

    
    static Calendar getInstance() 使用默认时区和语言环境获得一个日历。
    static Calendar getInstance(Locale aLocale) 使用默认时区和指定语言环境获得一个日历。
    static Calendar getInstance(TimeZone zone) 使用指定时区和默认语言环境获得一个日历。
    static Calendar getInstance(TimeZone zone, Locale aLocale) 使用指定时区和语言环境获得一个日历。
    
    Date getTime()返回一个表示此 Calendar 时间值(从历元至现在的毫秒偏移量)的 Date 对象。
    long getTimeInMillis()返回此 Calendar 的时间值,以毫秒为单位。
    TimeZone getTimeZone()获得时区。
    
    int hashCode()返回该此日历的哈希码。
    protected int internalGet(int field)返回给定日历字段的值。
    boolean isSet(int field) 确定给定日历字段是否已经设置了一个值,其中包括因为调用 get 方法触发内部字段计算而导致已经设置该值的情况。
   
    abstract void roll(int field, boolean up) 在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。
    void roll(int field, int amount) 向指定日历字段添加指定(有符号的)时间量,不更改更大的字段。
    
    void set(int field, int value) 将给定的日历字段设置为给定值。
    void set(int year, int month, int date) 设置日历字段 YEAR、MONTH 和 DAY_OF_MONTH 的值。
    void set(int year, int month, int date, int hourOfDay, int minute) 设置日历字段 YEAR、MONTH、DAY_OF_MONTH、HOUR_OF_DAY 和 MINUTE 的值。
    void set(int year, int month, int date, int hourOfDay, int minute, int second) 设置字段 YEAR、MONTH、DAY_OF_MONTH、HOUR、MINUTE 和 SECOND 的值。
    
    
    boolean isLenient()判断日期/时间的解释是否为宽松的。
    void setLenient(boolean lenient) 指定日期/时间解释是否是宽松的。
    
   
    void setTime(Date date) 使用给定的 Date 设置此 Calendar 的时间。
    void setTimeInMillis(long millis) 用给定的 long 值设置此 Calendar 的当前时间值。
    void setTimeZone(TimeZone value) 使用给定的时区值来设置时区。
    String toString() 返回此日历的字符串表示形式

常用方法实例
  1. getMaximum() 和 getActualMaximum(),getMinimum和getActualMinimum方法
   Calendar calendar = Calendar.getInstance();

        //!!!!!!!!若要获取其它字段的最大值,只需要将示例中的MONTH相应的替换成其它字段名即可。!!!!!
        //1. 获取指定字段的最大值
        int max = calendar.getMaximum(Calendar.DATE);
        System.out.println("getMaximum-----" + max);//31

        int actualMax = calendar.getActualMaximum(Calendar.DATE);
        System.out.println("getActualMaximum-----" + actualMax);//32

//        A、 getMaximum() 获取的“字段最大值”,是指在所有的日期,在所有这些日期中得出的“字段最大值”。
//        例如,getMaximum(Calendar.DATE)的目的是“获取‘日的最大值’”。综合所有的日期,得出一个月最多有31天。因此,getMaximum(Calendar.DATE)的返回值是“31”!
//        B、 getActualMaximum() 获取的“当前日期时,该字段的最大值”。
//        例如,当日期为2019-09-01时,getActualMaximum(Calendar.DATE)是获取“日的最大值”是“30”。当前日期是9月份,而9月只有30天。因此,getActualMaximum(Calendar.DATE)的返回值是“30”!

        // 2. 获取指定字段的最小值
        int min = calendar.getMinimum(Calendar.DATE);
        System.out.println("getMinimum-----" + min);//1

        int actualMin = calendar.getActualMinimum(Calendar.DATE);
        System.out.println("getActualMinimum-----" + actualMin);//1
//        注意:在Java默认的Calendar中,虽然 getMinimum() 和 getActualMinimum() 的含义不同;
//        但是,它们的返回值是一样的。因为Calendar的默认是返回GregorianCalendar对象,
//        而在GregorianCalendar.java中,getMinimum() 和 getActualMinimum() 返回值一样。

执行结果:
在这里插入图片描述

  1. get(int field)方法
  		Calendar calendar = Calendar.getInstance();
    	// get(int field)
        //作用:获取“字段的当前值”。获取field字段的当前值。
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println("get(int field)----" + year + "-" + month + "-" + day); //西方文化的原因,一年的第一个月是从“0”开始算起的,一年中12个月份分别是:“0-11”

执行结果:
在这里插入图片描述

  1. set(int field, int value)方法
		Calendar calendar = Calendar.getInstance();
        //set(int field, int value)
        //作用:设置“字段的当前值”。设置field字段的当前值为value 示例:设置当前年为2019年
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2018);
        int calYear = cal.get(Calendar.MONTH);
        System.out.println("set(int field, int value)---" + calYear);

执行结果:
在这里插入图片描述

  1. add(int field, int value)
        //add(int field, int value)
        //作用:给“字段的当前值”添加值。给field字段的当前值添加value。 示例:以“MONTH”字段来说。方法如下:
        // 获取Calendar实例,并设置日期为“2019-08-24”
        Calendar cal2 = Calendar.getInstance();
        cal2.set(Calendar.YEAR, 2019);
        cal2.set(Calendar.MONTH, 8);
        cal2.set(Calendar.DATE, 24);

        //当前月份加2个月
        cal2.add(Calendar.MONTH, 2);//2019-11-24
        System.out.println("add(int field, int value)+2----" + cal2.get(Calendar.YEAR) + "-" + (cal2.get(Calendar.MONTH) + 1) + "-" + cal2.get(Calendar.DAY_OF_MONTH));
        //当前月份减3个月
        cal2.add(Calendar.MONTH, -3);//2019-11-24
        System.out.println("add(int field, int value)-3----" + cal2.get(Calendar.YEAR) + "-" + (cal2.get(Calendar.MONTH) + 1) + "-" + cal2.get(Calendar.DAY_OF_MONTH));
        //说明
//        A、添加值可以为正数,也可以是负数。正数表示将日期增加,负数表示将日期减少。
//            假设:现在cal的值是“2013-09-01”,现在我们将MONTH字段值增加-10。得到的结果是:“2012-10-01”。
//            为什么会这样呢?“2013-09-01”增加-10,也就是将日期向前减少10个月;得到的结果就是“2012-10-01”。
//        B、 Calendar的17个字段中:除了回滚Calendar.ZONE_OFFSET时,会抛出IllegalArgumentException异常;其它的字段都支持该操作。

执行结果:
在这里插入图片描述

  1. roll(int field, int value)
  //6.roll(int field, int value)
        //作用:回滚“字段的当前值”
        Calendar cal3 = Calendar.getInstance();
        cal3.set(Calendar.YEAR, 2019);
        cal3.set(Calendar.MONTH, 05);
        cal3.set(Calendar.DATE, 24);
        // 将“cal日历”的当前MONTH值 “向前滚动2”
        cal3.roll(Calendar.MONTH, 10);
        System.out.println("roll(int field, int value)-10----" + cal3.get(Calendar.YEAR) + "-" + (cal3.get(Calendar.MONTH) + 1) + "-" + cal3.get(Calendar.DAY_OF_MONTH));

        //说明
//        A、当回滚值是负数时,表示将当前字段向前滚;当回滚值是正数时,表示将当前字段向后滚。
//        B、回滚Calendar中某一字段时,不更改更大的字段!这是roll()与add()的根据区别!add()可能会更改更大字段,比如“使用add()修改‘MONTH’字段,可能会引起‘YEAR’字段的改变”;
//          但是roll()不会更改更大的字段,例如“使用roll()修改‘MONTH’字段,不回引起‘YEAR’字段的改变。”
//        假设:现在cal的值是“2013-09-01”,现在我们将MONTH字段值增加-10。得到的结果是:“2013-10-01”。
//        为什么会这样呢?这就是因为“回滚”就是“在最小值和最大值之间来回滚动”。
//        本例中,MONTH是9月,前回滚10,得到的值是10月,但是roll()不会改变“比MONTH”更大的字段,所以YEAR字段不会改变。所以结果是“2013-10-01”。
//        B、 Calendar的17个字段中:除了回滚Calendar.ZONE_OFFSET时,会抛出IllegalArgumentException异常;其它的字段都支持该操作。
//        C、 若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。

执行结果:
在这里插入图片描述

  1. clear(int field)
	    //clear(int field)
        //作用:清空“字段的当前值”。所谓清空,实际上是将“field”的值设置为0;若field最小值为1,则设置为1。
        // 获取Calendar实例,并设置日期为“9月”
        Calendar cal4 = Calendar.getInstance();
        cal4.set(Calendar.MONTH, 9);
        // 清空MONTH
        cal4.clear(Calendar.MONTH);
        System.out.println("clear(Calendar.MONTH)----" + cal4.get(Calendar.MONTH));

执行结果:
在这里插入图片描述

  1. isSet(int field)
 		 //isSet(int field)
        //作用:判断“字段field”是否被设置。
        Calendar cal5 = Calendar.getInstance();// 获取Calendar实例
        cal5.set(Calendar.MONTH, 10);// 判断MONTH是否被设置
        boolean bset = cal5.isSet(Calendar.MONTH);
        System.out.println("isSet(Calendar.MONTH)----" + bset);

执行结果:
在这里插入图片描述

  1. 日期比较函数
// 比较“当前Calendar对象”和“calendar” 的日期、时区等内容是否相等。
        boolean equals (Object object)// 当前Calendar对象 是否 早于calendar
        boolean before (Object calendar)// 当前Calendar对象 是否 晚于calendar
        boolean after (Object calendar)// 比较“当前Calendar对象”和“calendar”。
        int compareTo (Calendar anotherCalendar)
        // 若 早于 “calendar” 则,返回-1
        // 若 相等, 则,返回0
        // 若 晚于 “calendar” 则,返回1
  1. 宽容函数
        void setLenient(boolean value) //设置“Calendar的宽容度”
        boolean isLenient() // 获取“Calendar的宽容度”
      
        //Calendar 有两种解释日历字段的模式,即 lenient 和 non-lenient。
        //A、 当 Calendar 处于 lenient 模式时,它可接受比它所生成的日历字段范围更大范围内的值。当 Calendar 重新计算日历字段值,以便由 get() 返回这些值时,所有日历字段都被标准化。
        //          例如,lenient 模式下的 GregorianCalendar 将 MONTH == JANUARY、DAY_OF_MONTH == 32 解释为 February 1。
        //B、 当 Calendar 处于 non-lenient 模式时,如果其日历字段中存在任何不一致性,它都会抛出一个异常。
        //      例如,GregorianCalendar 总是在 1 与月份的长度之间生成 DAY_OF_MONTH 值。如果已经设置了任何超出范围的字段值,那么在计算时间或日历字段值时,处于 non-lenient 模式下的 GregorianCalendar 会抛出一个异常。
        //      注意:在(02)步骤中的异常,在使用set()时不会抛出,而需要在使用get()、getTimeInMillis()、getTime()、add() 和 roll() 等函数中才抛出。因为set()只是设置了一个修改标志,而get()等方法才会引起时间的重新计算,此时才会抛出异常!

Calendar使用场景

注:在使用set方法之前,必须先clear一下,否则很多信息会继承自系统当前时间

  1. Calendar和Date的转化

    • Calendar转化为Date
    	Calendar cal=Calendar.getInstance();
     Date date=cal.getTime();
    
    1. Date转化为Calendar
    Date date=new Date();
    Calendar cal=Calendar.getInstance();
    cal.setTime(date);
    
  2. 计算某一月份的最大天数

    public int calcMaxDay(int month){
    	Calendar time=Calendar.getInstance(); 
    	time.clear(); 
    	time.set(Calendar.YEAR,year); 
    	time.set(Calendar.MONTH,month-1);//注意,Calendar对象默认一月为0             
    	int day=time.getActualMaximum(Calendar.DAY_OF_MONTH);//本月份的天数
    	return day;
    }
    
  3. 计算某个月的天数是多少

    /*计算某一个月的天数是多少*/  
    public void maxDay(int year,int month){  
        Calendar calendar=Calendar.getInstance(); 
        calendar.clear();  
        calendar.set(Calendar.YEAR, year);  
        calendar.set(Calendar.MONTH,month-1);//默认1月为0月  
        int day = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);  
        System.out.println(year+"年"+month+"月"+"的最大天数是:"+day);  
    }  
    
  4. 计算某一天是该年或该月的第几个星期

    public void weekNum(int year,int month,int day){ 
        Calendar calendar=Calendar.getInstance();  
        calendar.clear();  
        calendar.set(Calendar.YEAR, year);  
        calendar.set(Calendar.MONTH,month-1);  
        calendar.set(Calendar.DAY_OF_MONTH, day);  
        /*计算某一天是该年的第几个星期*/  
        int weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);  
        System.out.println(year+"年"+month+"月"+day+"日是这年中的第"+weekOfYear+"个星期");  
        /*计算某一天是该月的第几个星期*/  
        int weekOfMonth= calendar.get(Calendar.WEEK_OF_MONTH);  
        System.out.println(year+"年"+month+"月"+day+"日是这个月中的第"+weekOfMonth+"个星期");  
    }  
    
  5. 计算一年中的第几星期是几号

    public void dayNum(int year,int week){  
    	 Calendar calendar=Calendar.getInstance();  
    	calendar.clear();  
    	SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");  
    	calendar.set(Calendar.YEAR, year);  
    	calendar.set(Calendar.WEEK_OF_YEAR, week);  
    	calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);  
    	System.out.println(df.format(calendar.getTime()));  
    }  
    
  6. 计算一年中的第几星期是几号

    public void dayNum(int year,int week){ 
    Calendar calendar=Calendar.getInstance();   
    	calendar.clear();  
    	SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");  
    	calendar.set(Calendar.YEAR, year);  
    	calendar.set(Calendar.WEEK_OF_YEAR, week);  
    	calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);  
    	System.out.println(df.format(calendar.getTime()));  
    }  
    
  7. 查询显示当前的后几天,前几天等

    public void add(int year,int month,int day,int num){  
        calendar.clear();  
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");  
        calendar.set(Calendar.YEAR, year);  
        calendar.set(Calendar.MONTH, month-1);  
        calendar.set(Calendar.DAY_OF_MONTH, day);  
        Date date=calendar.getTime();  
        calendar.add(Calendar.DATE, num);  
        date=calendar.getTime();  
        System.out.println(df.format(date));  
    }  
    /*使用场景比如,发找回密码邮件,设置一天后过期*/  
    
  8. 计算任意两个日期的相隔天数
    使用场景较多,如查询某个时间段注册的新用户,或者某个时间段内销售总额等等

    //传进Calendar对象
     /**计算两个时间之间相隔天数
     * @param startday  开始时间
     * @param endday 结束时间
     * @return
     */
    public int getIntervalDays(Calendar startday,Calendar endday){
    	 //确保startday在endday之前
    	 if(startday.after(endday)){
       	 	Calendar cal=startday;
        	 startday=endday;
       		 endday=cal;
    	 }
    	 //分别得到两个时间的毫秒数
    	long sl=startday.getTimeInMillis();
    	long el=endday.getTimeInMillis();
    
    	long ei=el-sl;    
    	//根据毫秒数计算间隔天数
    	return (int)(ei/(1000*60*60*24));
    }
    
    //传进Date对象
    /**计算两个时间之间相隔天数
    * @param startday  开始时间
    * @param endday 结束时间
     * @return
    */
    public int getIntervalDays(Date startday,Date endday){
    	//确保startday在endday之前
    	if(startday.after(endday)){
        	Date cal=startday;
        	startday=endday;
        	endday=cal;
    	}
    	//分别得到两个时间的毫秒数
    	long sl=startday.getTime();
    	 long el=endday.getTime();
    
     long ei=el-sl;    
    	//根据毫秒数计算间隔天数
    	return (int)(ei/(1000*60*60*24));
    }
    

同理,可以用相同的方法计算出任意两个时间相隔的小时数,分钟数,秒钟数等

注:以上方法是完全按时间计算,有时并不能令人满意,如:
startday=“2006-10-11 20:00:00” endday=“2006-10-12 8:00:00”
计算结果为0,但是我们也许相让计算结果变为1,此时可以用如下方法实现:
在传参之前,先设定endday的时间,如:
endday.set(Calendar.HOUR_OF_DAY, 23);
endday.set(Calendar.MINUTE, 59);
endday.set(Calendar.SECOND, 59);
endday.set(Calendar.MILLISECOND, 59);
这样再传进去startday,endday,则结果就如我们所愿了。不过,如果嫌以上方法麻烦,可以参考以下方法:

改进精确计算相隔天数的方法

    public int getDaysBetween (Calendar d1, Calendar d2) {
        if (d1.after(d2)) {  // swap dates so that d1 is start and d2 is end
            Calendar swap = d1;
            d1 = d2;
            d2 = swap;
        }
        int days = d2.get(Calendar.DAY_OF_YEAR) - d1.get(Calendar.DAY_OF_YEAR);
        int y2 = d2.get(Calendar.YEAR);
        if (d1.get(Calendar.YEAR) != y2) {
            d1 = (Calendar) d1.clone();
            do {
                days += d1.getActualMaximum(Calendar.DAY_OF_YEAR);//得到当年的实际天数
                d1.add(Calendar.YEAR, 1);
            } while (d1.get(Calendar.YEAR) != y2);
        }
        return days;
    } 

获取两个时间之间的时间差

 /**
     * 获取时间差
     *
     * @param time1 time2 需要计算的时间
     * @return
     */
    public static String getTimeDeffer(String time1, String time2) {
        String result = "";
        if (StringUtils.isNotBlank(time1) && StringUtils.isNotBlank(time2)) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                long dt1 = sdf.parse(time1).getTime();
                long dt2 = sdf.parse(time2).getTime();
                long dc = Math.abs(dt2 - dt1);//获取绝对值(非负数〔 正数和0〕的绝对值是它本身, 非正数〔 负数〕的绝对值是它的 相反数。)
                long seconds = dc / 1000;
                long date = seconds / (24 * 60 * 60);     //相差的天数
                long hour = (seconds - date * 24 * 60 * 60) / (60 * 60);//相差的小时数
                long minut = (seconds - date * 24 * 60 * 60 - hour * 60 * 60) / (60);//相差的分钟数
                long second = (seconds - date * 24 * 60 * 60 - hour * 60 * 60 - minut * 60);//相差的秒数
                return (date == 0 ? "" : (date + "天")) + (hour == 0 ? "" : (hour + "小时")) + (minut == 0 ? "" : (minut + "分")) + (second == 0 ? "" : (second + "秒"));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
发布了62 篇原创文章 · 获赞 109 · 访问量 5305

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/102887166