Calendar日期类详解SimpleDateFormat时区Date夏令时常用方法,日期差获取当前时间

Posted ggzx666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Calendar日期类详解SimpleDateFormat时区Date夏令时常用方法,日期差获取当前时间相关的知识,希望对你有一定的参考价值。

Calendar

  • 获取当前时间
  • SimpleDateFormat
  • 获取年月日等
  • 设置特定时间、时区
  • 日期的计算(加减)
  • 计算日期差
  • 计算某年二月有几天
  • Calendar常用方法合集
  • 夏令时是什么

简介:
Java中,Calendar是一个抽象类,所以这个类是不能通过new来直接实现的,并且调用getInstance后y一般返回的是Calendar的子类:GregorianCalendar。


附getInstance中private static Calendar createCalendar(TimeZone zone,Locale aLocale)部分代码

        if (cal == null) 
//	如果没有明确指定已知的日历类型,则执行传统方式来创建日历:为 th_TH 语言环境创建一个佛教日历,
//为 ja_JP_JP 语言环境创建一个 JapaneseImperialCalendar,
//或者为任何其他语言环境创建一个 GregorianCalendar。
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") 
                cal = new BuddhistCalendar(zone, aLocale);
             else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") 
                cal = new JapaneseImperialCalendar(zone, aLocale);
             else 
                cal = new GregorianCalendar(zone, aLocale);
            
        
        return cal;

获取当前时间

	Calendar calendar=Calendar.getInstance();
	calendar.getTime();//只有使用了getTime方法,返回的是Date对象
	//也可以使用calendar.setTime(new Date());

调用抽象类中的静态方法getInstance来返回一个当前的calendar对象。Calendar对象较大较复杂,所以使用的是单例模式,使用单例模式的好处是每次都返回同一个对象,这样保证了每次返回都是同一个对象


源码

    /**
     使用默认时区和区域设置获取日历。 返回的Calendar基于默认时区中的当前时间和默认FORMAT语言环境。
返回:Calendar
     * @link Locale.Category#FORMAT FORMAT locale.
     *
     * @return a Calendar.
     */
    public static Calendar getInstance()
    
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    

如何规范输出日期的格式

		Date date=c.getTime();//返回的是Date对象
		SimpleDateFormat sdf2=new SimpleDateFormat("yyyy年MM月dd分hh时ss分mm秒");
		String strTime=sdf2.format(date);
		System.out.println(strTime);

yyyy:年
MM:月
dd:日
hh:1~12小时制(1-12)
HH:24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)。
K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区

解析Calendar生成方式:通过时区(TimeZone)和区域(Local)来生成时间,因为不同时区有时间差。并且Calendar对象是可变的,可以通过设置时区、区域、年月日来改变Calendar对象中存储的值。
举例:跨国业务中,美国的消费者下单时间和服务器中接收到订单的时间不一样,这里就需要使用到时区和地区的知识(想知道怎么做继续往下看)


获取年月日

		Calendar calendar=Calendar.getInstance();
		calendar.getTime();//只有使用了getTime方法,calendar才会有存储的日期
        System.out.println("年:"+calendar.get(calendar.YEAR));
        System.out.println("一个月的第几周:"+calendar.get(calendar.WEEK_OF_MONTH));
        System.out.println("一周的第几天:"+calendar.get(calendar.DAY_OF_WEEK));
        System.out.println("早为0,晚为1(以中午12点为界限):"+calendar.get(Calendar.AM_PM));
        System.out.println("一天的第几个小时:"+calendar.get(Calendar.HOUR_OF_DAY));
        System.out.println("秒:"+calendar.get(Calendar.SECOND));

        System.out.println("注意:周日是第一天,序号为1:"+calendar.get(Calendar.SUNDAY));
field说明
public final static int ERA = 0;公元前BC(0) 公元后(1)
public final static int YEAR = 1;
public final static int MONTH = 2;日期
public final static int WEEK_OF_YEAR = 3;一年的第几周
public final static int WEEK_OF_MONTH = 4;一个月的第几周
public final static int DATE = 5;一个月的第几天
public final static int DAY_OF_MONTH = 6;一个月的第几天
public final static int DAY_OF_WEEK = 7;一周的第几天
public final static int AM_PM = 9;特别注意:1是日 早上or下午AM:0 PM:1,以中午十二点为界
public final static int HOUR = 10;小时
public final static int HOUR_OF_DAY = 11;一天的第几个小时
public final static int MINUTE = 12;分钟
public final static int SECOND = 13;
public final static int MILLISECOND = 14;毫秒
public final static int ZONE_OFFSET = 15;指示与 GMT 的原始偏移量(以毫秒为单位)
public final static int DST_OFFSET = 16;指示以毫秒为单位的夏令时偏移量
public final static int FIELD_COUNT = 17;字段数量

设置特定时间、时区

		Calendar calendar=Calendar.getInstance();
		calendar.getTime();//只有使用了getTime方法,calendar才会有存储的日期
        calendar.set(Calendar.YEAR,2022);
        calendar.set(Calendar.MONTH,5);
        calendar.set(Calendar.DAY_OF_MONTH,2);
        System.out.println(calendar.getTime());
        calendar.set(1990,3,5);//这里是四月嗷
        System.out.println(calendar.getTime());
			//结果
			//Thu Jun 02 23:32:48 CST 2022
			//Thu Apr 05 23:32:48 CST 1990

时区有哪些

        for (String timezone: TimeZone.getAvailableIDs()) 
            System.out.println(timezone);
        
        //很多,大家运行一下就知道了

点击跳转至:世界时区

时区有什么用:不同时区的时间不一样,假如平台有海外业务,如果在存入数据库的时候没有考虑时区问题,即数据库只保存年月日,那么可能会发生2021.10.1 18:00:00服务器接收到了订单,并且立刻数据库中存入一笔时间2021.10.1 19:00:00的订单记录,这里订单时间相当于变成"一小时后的下单记录时间",所以我们要考虑转换时区来保证数据库时间存储的正确性。(虽然可以在数据库中加入timezone这个字段,但是可能会麻烦一些)

不同时区的时间转换

	public class TimeZoneTransform 

    private static String dateTransformBetweenTimeZone(Date sourceDate, DateFormat formatter,
                                                       TimeZone sourceTimeZone, TimeZone targetTimeZone) 
        Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset();
        return getTime(new Date(targetTime), formatter);
    

    private static String getTime(Date date, DateFormat formatter) 
        return formatter.format(date);
    

    private static String getTimeZone() 
        Calendar cal = Calendar.getInstance();
        // getOffset will access to offset and contains DaylightTime
        int timeZone = cal.getTimeZone().getOffset(System.currentTimeMillis()) / (3600000);
        if (timeZone >= 0) 
            return String.valueOf("+" + timeZone);
        
        return String.valueOf(timeZone);
    

    public static String getGMTTime(Date date, SimpleDateFormat formatter) 
        TimeZone srcTimeZone = TimeZone.getTimeZone("GMT" + getTimeZone());
        TimeZone destTimeZone = TimeZone.getTimeZone("GMT+8");

        return dateTransformBetweenTimeZone(date, formatter, srcTimeZone, destTimeZone);
    

    public static void main(String[] args) 
        System.out.println(getGMTTime(new Date(System.currentTimeMillis()), new SimpleDateFormat()));
    


	

Set有延迟性
set(f, value) 将日历字段 f 更改为 value。此外,它设置了一个内部成员变量,以指示日历字段 f 已经被更改。尽管日历字段 f 是立即更改的,但是直到下次调用 get()、getTime()、getTimeInMillis()、add() 或 roll() 时才会重新计算日历的时间值(以毫秒为单位)。因此,多次调用 set() 不会触发多次不必要的计算。
通俗的来说就是

    public void set(int field, int value)
    
    //areFieldsSet:如果与当前设置的时间同步,则为真。如果为 false,则下一次获取字段值的尝试将强制从当前值重新计算所有字段
        if (areFieldsSet && !areAllFieldsSet) 
            computeFields();
        
        internalSet(field, value);//这里的操作仅仅只是更改存储的值,但是无法判断某些不合法的值,如11月没有31号
        isTimeSet = false;
        areFieldsSet = false;
        isSet[field] = true;
        stamp[field] = nextStamp++;
        if (nextStamp == Integer.MAX_VALUE) 
            adjustStamp();
        
    

看到这里可能你会很懵,不知道它的延迟性可能会引发什么情况,下面来说一个情况,首先你给calendar设置了9月31号(不报错并且会存储,当调用上述几种方法后会计算然后变成十月一日),此时假如调用了上述几种方法后,设置为五日,就变成了十月五日;假如没调用那几种方法,再设置五日,就是九月五日。
简要来说:假如你阴差阳错把天数设置超过范围,并且你设置的月份是你认为正确的,此时假如你调用了get等方法,那么calendar会把日期计算成一个系统认为合法的日期(月份和天数可能都不是你想要的)。此时如果你只修改了日期,那么你月份仍然是错误的。

        cal.setLenient(true);
        cal.set(2021,8,31);//这里是9月嗷
//        System.out.println(cal.getTime());//调用此部分就刷新了数据
//        cal.set(Calendar.MONTH,9);
        cal.set(Calendar.DATE,10);
        System.out.println(cal.getTime());
        //假如注释那两行,结果Fri Sep 10 19:56:31 GMT+08:00 2021
        //假如不注释,结果:Sun Oct 10 19:57:13 GMT+08:00 2021

日期的计算(加减)

        Calendar calendar=Calendar.getInstance();
        calendar.setTime(new Date());
                //日期的加减
        calendar.add(Calendar.YEAR,10);
        calendar.add(Calendar.MONTH,-2);//可以为负数的嗷,其他的字段就不展示了

其他的字段操作自己探索就好了

计算日期差


   //计算相隔天数的方法
   public int getDaysBetween (Calendar d1, Calendar d2)
      if (d1.after(d2))  // swap dates so that d1 is start and d2 is end
           java.util.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;
    

这里有个大佬讲的很全

获取某年二月有几天

虽然获取某年二月有几天很容易,但是这里仅是提供一个思路

	      calendar.set(Calendar.YEAR,2021);
        calendar.set(Calendar.MONTH,1);
        System.out.println(calendar.getTime());
        System.out.println(calendar.getActualMaximum(Calendar.DATE));
        //输出28

下面来解释getActualMaximum方法:返回当前日期时,该字段的最大值
还有另一个长得很像的方法getMaximum:用于获取此Calendar的给定字段的最大值,例如使用getMaximum(Calendar.DATE),那么返回的是31,因为十二个月中DATE最大是31

常见方法

上面涉及到的不在写了

abstract void add(int field, int amount)根据日历的规则,为给定的日历字段添加或减去指定的时间量。
boolean after(Object when)判断此 Calendar 表示的时间是否在指定 Object 表示的时间之后,返回判断结果。
boolean before(Object when)判断此 Calendar 表示的时间是否在指定 Object 表示的时间之前,返回判断结果。
void clear()将此 Calendar 的所日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。
void clear(int field)将此 Calendar 的给定日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。
int compareTo(Calendar anotherCalendar)比较两个 Calendar 对象表示的时间值(从历元至现在的毫秒偏移量)。
protected void complete()填充日历字段中所有未设置的字段。
boolean equals(Object obj)将此 Calendar 与指定 Object 比较。
int get(int field)返回给定日历字段的值。
int getActualMaximum(int field)给定此 Calendar 的时间值,返回指定日历字段可能拥有的最大值。
int getActualMinimum(int field)给定此 Calendar 的时间值,返回指定日历字段可能拥有的最小值
static Locale[] getAvailableLocales()返回所有语言环境的数组,此类的 getInstance 方法可以为其返回本地化的实例。
int getFirstDayOfWeek()获取一星期的第一天
Date getTime()返回一个表示此 Calendar 时间值(从历元至现在的毫秒偏移量)的 Date 对象。
long getTimeInMillis()返回此 Calendar 的时间值,以毫秒为单位。
boolean isLenient()判断日期/时间的解释是否为宽松的。
boolean isSet(int field)确定给定日历字段是否已经设置了一个值,其中包括因为调用 get 方法触发内部字段计算而导致已经设置该值的情况。
abstract void roll(int field, boolean up)在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。
void roll(int field, int amount)向指定日历字段添加指定(有符号的)时间量,不更改更大的字段。
void setFirstDayOfWeek(int value)设置一星期的第一天是哪一天
void setLenient(boolean lenient)指定日期/时间解释是否是宽松的。
void setTime(Date date)使用给定的 Date 设置此 Calendar 的时间。
void setTimeInMillis(long millis)用给定的 long 值设置此 Calendar 的当前时间值。
void setTimeZone(TimeZone value)使用给定的时区值来设置时区。

宽松模式:是否允许字段的值超过范围,即设置HOUR=25就是明天的1点
注意:当 Calendar 处于 non-lenient 模式时,如果其日历字段中存在任何不一致性,它都会抛出一个异常。

        calendar.setLenient(true);
        calendar.set(Calendar.YEAR,2021);
        calendar.set(Calendar.MONTH,13);
        System.out.println(calendar.getTime());
        //结果:Sat Feb 05 01:14:31 CST 2022

roll和add的区别:
2021.9.12对MONTH字段-10,会是roll:2021.10.12,
add:2020.10.12
roll:对字段MONTH回滚时,只回滚该字段,不更改其更大的字段。
add:对字段MONTH回滚时,超出范围时,会更新其更大的字段

在宽松模式下也可以通过add方法来计算两个日期之间的时间差

什么是夏令时

什么是夏令时?

通俗简单的讲:在夏天日光充裕的时候,人们为了充分利用日光时间人为的将时钟拨快了一个小时。
出发点和目的都很简单:夏天亮的早,白天时间长,调整为夏令时促使人们早睡早起,以充分利用光照资源,从而节约照明用电。
例如:晚上两点的时候,时间调到三点,那么这天你会少睡一小时,这样到了晚上,那么你会提前一小时睡觉,那么就省了电。
有关Calendar的夏令时部分请看这位大佬的博客
添加链接描述

以上是关于Calendar日期类详解SimpleDateFormat时区Date夏令时常用方法,日期差获取当前时间的主要内容,如果未能解决你的问题,请参考以下文章

Calendar日期类详解SimpleDateFormat时区Date夏令时常用方法,日期差获取当前时间

Java小白入门200例58之日期Calendar类详解

Date类和Calendar类应用到计算活了多少天和判断闰年与平年

Calendar类

Java学习笔记4.5.1 日期时间 - Date类与Calendar类

java如何通过calendar类获取当前系统日期