从零开始学Java之带你熟练掌握Java中的日期时间相关类

606 阅读17分钟

作者:孙玉昌,昵称【一一哥】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

我们在开发时,除了数字、数学这样的常用API之外,还有日期时间类,更是会被经常使用。比如我们项目中必备的日志功能,需要记录异常等信息产生的时间。还有数据库中的表,也经常需要带有日期时间字段,用于记录本条数据产生和更新的时间。另外当我们需要对某段代码进行调优时,也往往需要知道本段代码的执行时间是多长。诸如此类,项目中有很多地方都需要用到日期和时间,所以今天壹哥必须带各位来学习一下相关的API有哪些。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【5400】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear…

Gitee: 一一哥/从零开始学Java

一. 概念简介

在开始学习今天的知识之前,壹哥有必要先给大家讲解一下与今天内容相关的一些概念,否则可能会让一些小白产生迷惑。

1. 日期和时间的区别

首先我们得搞清楚,日期和时间的概念并不一样。日期是指某一天,它不是连续变化的,可以说是离散的。而时间有两种概念,一种是不带日期的时间,如10:30:01;另一种是带日期的时间,如2023-01-01 15:11:40。只有带日期的时间,才能唯一确定某个时刻,而不带日期的时间是无法确定一个唯一时刻的。

2. 本地时间

本地时间其实就是每个地方,当前所在国家所采用的标准时间。比如我们国家就是采用的北京时间,只要是在中国大陆,我们说晚上8:00见,这个8:00指的就是北京时间。即使我们国家的时区,其实包括了从东五区到东九区共5个时区,但我们全国都是统一采用的东八区的区时,这样各地区人员之间的交流才不会产生歧义。但如果是在别的国家,那这个本地时间,就是他们国家的标准区时了,所以每个国家的标准区时可能是不同的。

3. 时区表示法

我们初中学地里的时候,就学过时区的概念,壹哥这里就不多讲了。在计算机中,如果我们想准确地确定一个时间,需要把本地时间和时区结合在一起才行。其中时区有如下几种表示方式:

  1. GMT UTC 加时区偏移表示法:如GMT+08:00 或 UTC+08:00,就表示东八区的时间。因为北京时区是东八区,领先UTC 8个小时,所以将UTC装换成北京时间时,要加上8小时。GMT(Greenwich Mean Time)是格林威治标准时间,UTC(Universal Time Coordinated)是世界统一时间或世界标准时间, GMT UTC 其实基本是等价的,它们都是英国伦敦的本地时间。但UTC使用了更精确的原子钟计时,每隔几年会有一个闰秒,不过我们在开发时可以忽略两者的误差,因为计算机的时钟在联网时会自动与时间服务器同步时间。
  2. 时区缩写表示法:如CST是China Standard Time的缩写,即中国标准时间。但CST也是美国中部时间Central Standard Time USA的缩写,因此有些缩写容易产生混淆,开发时尽量不要使用缩写形式。
  3. 洲/城市表示法:如Asia/Shanghai,表示上海所在地的时区。我们要特别注意,城市名称并不是任意的城市,而是由国际标准组织规定的城市。

4. 本地化

本地化并不只包括时间这一种信息,还包括一个国家或地区所采用的日期、时间、数字、货币等各种信息的格式,开发时通常使用Locale进行表示。Locale由“语言_国家”的字母缩写构成,如“zh_CN”就表示“中文+中国”,“en_US”表示“英文+美国”。其中语言是小写,国家是大写。

而对于不同国家或地区的Locale日期部分来说,如中国和美国的本地时间表示方式如下:

  • zh_CN:2023-01-24
  • en_US:01/24/2023

5. 夏令时

夏令时(Daylight Saving Time:DST),也叫夏时制,又称“日光节约时制”,是一种为了节约能源而人为规定地方时间的制度。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。

我们国家曾经实行过一段时间夏令时,但在1992年就废除了,不过美国人到现在还在使用。所以涉及到跨国应用开发时,相关时间的换算可能会有点复杂。因为涉及到夏令时,相同的时区,如果表示的方式不同,转换出的时间也是不同的。

6. Epoch Time时间起点

Epoch Time是一个固定的通用时间,即世界标准时间(UTC) 1970-01-01 00:00:00 UTC,它是计算机里时间开始的起点,该起点被记为0,而1970年以前的时间被认为是负数。我们知道,现实世界的时间谁也不知道是从什么时候开始的,但是计算机发明的时间并不长,为了方便大家进行各种开发和计算,于是国际标准委员会就给计算机设置了一个时间的起点。以这个时间为起点,每过去一秒,该数值就加1,这样我们就可以算出对应的公历时间日期(不包括闰秒)。 Epoch Time在不同的编程语言中,会有几种不同的存储方式:

  • 以秒为单位的整数:1574208900,缺点是精度只能到秒;
  • 以毫秒为单位的整数:1574208900123,最后3位表示毫秒数;
  • 以秒为单位的浮点数:1574208900.123,小数点后表示零点几秒。

7. 时间戳

时间戳(timestamp),也称为Unix时间 或 POSIX时间,它是一种时间表示方式。表示从1970年1月1日0时0分0秒(格林尼治时间)开始,一直到现在所经过的秒数或毫秒数。在Java一般是用long类型来存储该值,但在别的编程语言中有可能是使用float类型。 比如1574208900就表示从1970年1月1日零点开始,到2019年11月20日8点15分截止,一共经历了1574208900秒,所以换算成北京时间就是:1574208900 = 北京时间2019-11-20 8:15:00。如果我们要获取当前的时间戳,在Java中可以使用System.currentTimeMillis()方法。

从本质上来说,时间戳就是个时间差值,其值与时区无关。比如在UTC标准下,时间起点的时间戳就是timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00,此时对应的北京时间是timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00

了解了以上这些基本概念之后,我们就可以继续往下学习今天其他的内容了。

二. Date日期时间类

1. 简介

如果我们想在Java中获取当前的时间,可以使用 java.util.Date类 和 java.util.Calendar类来实现。其中,Date类封装了系统的日期和时间信息,Calendar类则会根据系统的日历来填充Date对象。

java.util.Date是一个表示日期和时间的类,代表了系统特定的时间戳。它是按照UTC时间显示的,可以精确到毫秒,源码内部使用long类型进行时间的存储。我们要注意与java.sql.Date区分,后者是用在数据库中的类,且是按照本地时区显示的。Date对象表示的时间,其默认顺序是星期、月、日、小时、分、秒、年

2. 构造方法

java.util.Date类给我们提供了多个构造方法,如下图所示:

但是一般在开发时,我们常用的也没有这么多,一般使用时如下形式:

  • Date():创建Date对象并初始化,该对象可以获取本地的当前时间,该时间会精确到毫秒。
  • Date(long date):构造一个Date对象,并接受一个从1970年1月1日起的毫秒数作为参数。

3. 常用API方法

当我们构造出来一个Date对象之后,就可以使用它的一些API方法进行时间的操作了,这些常用的API方法如下:

序号方法和描述
boolean after(Date date)若调用该方法的Date对象,在指定的日期之后,则返回true,否则返回false。
boolean before(Date date)若调用此方法的Date对象,在指定的日期之前,则返回true,否则返回false。
int compareTo(Date date)比较调用此方法的Date对象和指定的日期。若两者相等则返回0,若该对象在指定日期之前则返回负数,若该对象在指定日期之后则返回正数。
boolean equals(Object date)若调用该方法的Date对象,和指定日期相等时则返回true,否则返回false。
long getTime( )返回自1970年1月1日 00:00:00 GMT以来的毫秒数。
void setTime(long time)用从1970年1月1日00:00:00 以后的time毫秒数,设置时间和日期。
String toString( )把该Date对象转换成dow mon dd hh:mm:ss zzz yyyy格式的字符串,其中dow是指一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。

4. 使用方法

4.1 基本使用

接下来我们先通过一个简单的案例,来演示一下Date的基本用法。

import java.util.Date;

/**
 * @author 一一哥Sun
 */
public class Demo01 {
    public static void main(String[] args) {
	//获取当前时间的时间戳
	long currentTimeMillis = System.currentTimeMillis();
	System.out.println("时间戳="+currentTimeMillis);
		
	//获取当前时间对象
	Date date=new Date();
	//Sat Feb 11 12:04:03 IRKT 2023
	System.out.println("当前时间="+date);
	//转为字符串:Sat Feb 11 12:04:03 IRKT 2023
	System.out.println("当前时间="+date.toString());
	//转换为本地时区:2023年2月11日 下午12:04:03
	System.out.println("当前时间,Locale="+date.toLocaleString());
	//转换为GMT时区:11 Feb 2023 04:04:03 GMT
	System.out.println("当前时间,GMT="+date.toGMTString());
    }
}

如果我们想获取当前时间的时间戳,可以使用System.currentTimeMillis()方法。构造出Date对象之后,我们可以直接打印该对象,就能展示出当前时间,但是这个格式并不一定符合我们中国人的阅读习惯,后面我们可以对日期进行格式化操作。

4.2 其他用法

除了上面这些基本用法之外,Date还有其他的一些用法。

import java.util.Date;

/**
 * @author 一一哥Sun
 */
public class Demo01 {
    public static void main(String[] args) {
	//获取当前时间对象
	Date date=new Date();
	//获取年月日
	System.out.println("年="+(date.getYear() + 1900)); // 必须加上1900
        System.out.println("月="+(date.getMonth() + 1)); // 0~11,必须加上1
        System.out.println("日="+date.getDate()); // 1~31,不能加1
        System.out.println("时="+date.getHours()); // 0~23
        System.out.println("分="+date.getMinutes()); // 0~59,不能加1
        System.out.println("秒="+date.getSeconds()); // 0~59,不能加1
        System.out.println("时间戳="+date.getTime()); // 时间戳,毫秒值
		
	//计算自己已经活了多少天,1990年01月31日
	//构造对象的另一个方法,已过时。year:要减去1900,月份从0开始,0-11;日期是1-31
        Date d1 =  new Date(1990-1900, 2-1, 31);
        Date d2 = new Date();
        long time = d2.getTime() - d1.getTime();
        System.out.println("已活天数="+time/1000/60/60/24);
    }
}

另外我们还要注意,getYear()方法返回的年份必须加上1900;getMonth()方法返回的月份是011,分别表示112月,所以要加1;而getDate()方法返回的日期范围是1~31,就不能加1。

在打印本地时区表示的日期和时间时,不同的计算机可能会有不同的展示结果,后面我们可以使用SimpleDateFormat设置出我们想要的日期时间格式。

4.3 统计时间差

有时候我们要统计某个功能的执行时间,此时就可以用该功能结束时的时间,减去开始时的时间,得到一个时间差,这就是该功能的执行时间。

import java.util.Date;

/**
 * @author 一一哥Sun
 */
public class Demo03 {
    public static void main(String[] args) {
	//获取当前时间对象
	//开始时间
	Date startDate=new Date();
	for(int i=0;i<100000;i++) {
            System.out.println("循环次数"+i);
	}
        		
	//结束时间
	Date endDate=new Date();
	//计算时间差
        long time = endDate.getTime() - startDate.getTime();
        System.out.println("10w次循环的执行时间是 "+time+" 毫秒");
    }
}

5. 配套视频

与本节内容配套的视频链接如下:

player.bilibili.com/player.html…

三. Calendar日历类

1. 简介

Calendar类是Java时间类Date的扩展。相比Date,它拥有更强大的功能,主要是多了可以做简单日期和时间运算的功能,且在实现方式上也比Date类更复杂一些。Calendar可以用来计算日期,比如说计算下个月的日期,或者两个月前的日期等。

Calendar类是一个抽象类,我们在实际使用时需要实现特定的子类,一般使用getInstance()方法创建即可。Calendar类有几个主要的子类,包括java.util.GregorianCalendar和java.util.TimeZone。其中GregorianCalendar类提供了标准的日历系统,可以用来计算未来或过去某天的日期。TimeZone类则可以用来在不同的时区之间,转换日期和时间。

2. Calendar常量字段

Calendar中有以下几个常用的常量字段,用于表示不同的意义。

常量描述
Calendar.YEAR年份
Calendar.MONTH月份
Calendar.DATE日期
Calendar.DAY_OF_MONTH日期,和上面的字段意义完全相同
Calendar.HOUR12小时制的小时
Calendar.HOUR_OF_DAY24小时制的小时
Calendar.MINUTE分钟
Calendar.SECOND
Calendar.DAY_OF_WEEK星期几

3. Calendar常用方法

除了以上常用的常量字段之外,Calendar还有一些常用的方法,如下表所示:

方法描述
void add(int field, int amount)根据日历的规则,为给定的日历字段 field 添加或减去指定的时间量 amount
boolean after(Object when)判断此 Calendar 表示的时间是否在指定时间 when 之后,并返回判断结果
boolean before(Object when)判断此 Calendar 表示的时间是否在指定时间 when 之前,并返回判断结果
void clear()清空 Calendar 中的日期时间值
int compareTo(Calendar anotherCalendar)比较两个 Calendar 对象表示的时间值(从格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒至现在的毫秒偏移量),大则返回 1,小则返回 -1,相等返回 0
int get(int field)返回指定日历字段的值
int getActualMaximum(int field)返回指定日历字段可能拥有的最大值
int getActualMinimum(int field)返回指定日历字段可能拥有的最小值
int getFirstDayOfWeek()获取一星期的第一天。根据不同的国家地区,返回不同的值
static Calendar getInstance()使用默认时区和语言坏境获得一个日历
static Calendar getInstance(TimeZone zone)使用指定时区和默认语言环境获得一个日历
static Calendar getInstance(TimeZone zone,Locale aLocale)使用指定时区和语言环境获得一个日历
Date getTime()返回一个表示此 Calendar 时间值(从格林威治时间1970 年 01 月 01 日 00 时 00 分 00 秒至现在的毫秒偏移量)的 Date 对象
long getTimeInMillis()返回此 Calendar 的时间值,以毫秒为单位
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, int second)设置字段 YEAR、MONTH、DAY_OF_MONTH、HOUR、 MINUTE 和 SECOND 的值
void setFirstDayOfWeek(int value)设置一星期的第一天是哪一天
void setTimeInMillis(long millis)用给定的 long 值设置此 Calendar 的当前时间值

Calendar对象可以调用set()方法将日历翻到任何一个时间,当参数 year取负数时表示是公元前。调用 get()方法可以获取年、月、日等时间信息,field参数的值是前面讲过的Calendar静态常量。

4. 构建Calendar对象

Calendar类是抽象类,所以我们不能通过new的方式来构建Calendar对象。在实际使用时,我们一般是要实现特定的子类,经常是使用getInstance()方法进行创建。

import java.util.Calendar;

/**
 * @author 一一哥Sun
 */
public class Demo04 {
    public static void main(String[] args) {
	//默认是当前日期
	Calendar c1 = Calendar.getInstance();
	System.out.println("c1="+c1);	
	//创建一个代表2023年2月2日的Calendar对象
	Calendar c2 = Calendar.getInstance();
	c2.set(2023, 2-1, 2);
	System.out.println("c2="+c2);
    }
}

5. 获取当前时间

获取到Calendar对象之后,我们可以获取到当前日期对象的年月日时分秒等信息。

import java.util.Calendar;

/**
 * @author 一一哥Sun
 */
public class Demo05 {
    public static void main(String[] args) {
	// 获取当前时间
        Calendar c = Calendar.getInstance();
        int y = c.get(Calendar.YEAR);
        //月份要加1
        int m = 1 + c.get(Calendar.MONTH);
        int d = c.get(Calendar.DAY_OF_MONTH);
        int w = c.get(Calendar.DAY_OF_WEEK);
        int hh = c.get(Calendar.HOUR_OF_DAY);
        int mm = c.get(Calendar.MINUTE);
        int ss = c.get(Calendar.SECOND);
        int ms = c.get(Calendar.MILLISECOND);
        //2023-2-11 7 18:10:59.847
        System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms);
    }
}

我们要注意,Calendar是通过get()方法获取年月日等信息的,其中返回的年份不必转换,返回的月份仍要加1,返回的星期要特别注意,1~7分别表示周日、周一、……周六。

6. 设置时间

我们通过Calendar.getInstance()方法获取到Calendar对象后,获取到的其实就是当前时间。如果我们想设置某个特定的日期和时间,需要先用clear()方法清除掉之前所有的字段。

import java.util.Calendar;

/**
 * @author 一一哥Sun
 */
public class Demo06 {
    public static void main(String[] args) {
	// 设置时间
        Calendar c = Calendar.getInstance();
        // 清除所有
        c.clear();
        // 设置2023年
        c.set(Calendar.YEAR, 2023);
        // 设置2月(0~11)
        c.set(Calendar.MONTH, 1);
        // 设置2日
        c.set(Calendar.DATE, 2);
        // 设置时间
        c.set(Calendar.HOUR_OF_DAY, 21);
        c.set(Calendar.MINUTE, 22);
        c.set(Calendar.SECOND, 23);
        //Thu Feb 02 21:22:23 IRKT 2023
        System.out.println("date="+c.getTime());
    }
}

我们可以利用Calendar.getTime()方法,将一个Calendar对象转换成Date对象,后面我们就可以用SimpleDateFormat进行格式化操作了。

7. 配套视频

与本节内容配套的视频链接如下:

player.bilibili.com/player.html…

四. GregorianCalendar类

1. 简介

Java中除了有Calendar类实现了公历日历,还有一个子类GregorianCalendar。在GregorianCalendar类中,定义了两个字段:AD和BC,分别代表公历定义的两个时代。GregorianCalendar中的属性和方法与Calendar类似,壹哥就不再赘述了,接下来我们直接通过一个案例来进行展示其用法。

2. 基本用法

这里我们设计一个案例,来判断当前年份是闰年还是平年。

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * @author 一一哥Sun
 */
public class Demo05 {
    public static void main(String[] args) {
	//定义一个月份数组
	String months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
	int year;	
	// 使用当前时间和日期,初始化Gregorian日历对象,默认为本地时间和时区
	GregorianCalendar gcalendar = new GregorianCalendar();
	// 显示当前时间和日期的信息
	System.out.print("Date:");
	System.out.print(months[gcalendar.get(Calendar.MONTH)]);
	System.out.print(" " + gcalendar.get(Calendar.DATE) + " ");
	System.out.println(year = gcalendar.get(Calendar.YEAR));

	System.out.print("Time:");
	System.out.print(gcalendar.get(Calendar.HOUR) + ":");
	System.out.print(gcalendar.get(Calendar.MINUTE) + ":");
	System.out.println(gcalendar.get(Calendar.SECOND));

	//判断当前年份是否为闰年
	if (gcalendar.isLeapYear(year)) {
            System.out.println("当前年份是闰年");
        } else {
            System.out.println("当前年份是平年");
	}
    }
}

------------------------------正片已结束,来根事后烟----------------------------

五. 结语

至此,壹哥就把Date和扩展类Calendar给大家讲解完毕,今天的内容其实并不难,大家需要把一些常用的构造方式及方法、常量记一下即可。如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。