java的日期时间处理,在java8之前主要使用Date和Calender类来进行构造,在java8后,在java.time包中引入了更多的类来处理日期时间。具有更好的可读性、易用性和线程安全性。
旧日期时间处理类
Date
java.util.Date类表示特定的时间瞬间,精度为毫秒级,其实现了Serializable, Cloneable和Comparable三个接口。有以下构造函数:
- Date() : 代表当前实际
- Date(long milliseconds) : 根据给定毫秒数创建,毫秒数从January 1, 1970, 00:00:00 GMT开始计算。
- Date(int year, int month, int date)
- Date(int year, int month, int date, int hrs, int min)
- Date(int year, int month, int date, int hrs, int min, int sec)
- Date(String s)
后面的4个构造函数都标记为已过时,但是这里还是要说以下其中参数year是要减去1900,也就是实际的年份是参数year+1900。月份是0-11.
如下
Date date = new Date(2024,2,28);
System.out.println(date);
//输出Fri Mar 28 00:00:00 CST 3924。是3924年部署2024年。
无参构造函数实际上是使用System.currentTimeMillis()获取当前秒数来构造时间。
Date对象有以下几个常用的方法:
- **boolean after(Date date) : **检测当前对象表示时间是否晚于给定时间
- **boolean before(Date date) : **检测当前对象表示时间是否早于给定时间
- **int compareTo(Date date) : **时间比较,相等返回0,小于0表示早于给定时间,反之晚于。
- long getTime() : 获取该日期标识的毫秒数
- void setTime(long time) : 重新根据毫秒数设置时间.
Calendar
由于使用Date构造函数初始化特定的时间不太方便,又有了Calendar类。Calendar提供了对日期和时间进行计算、格式化和解析等功能。Calendar
类是一个抽象类,不能直接实例化,通常通过调用 Calendar.getInstance()
方法来获取 Calendar
对象。
Calendar将日期分成多个属性部分,使用常量来进行标识:
年份相关:
Calendar.YEAR
:年份Calendar.MONTH
:月份(注意月份从 0 开始,即 0 表示一月,11 表示十二月)
月份相关:
Calendar.JANUARY
-Calendar.DECEMBER
:一月至十二月的常量
周相关:
Calendar.DAY_OF_WEEK
:星期几(周日至周六分别对应 1 到 7)Calendar.DAY_OF_WEEK_IN_MONTH
:月中第几周的周几Calendar.WEEK_OF_YEAR
:年中的周数Calendar.WEEK_OF_MONTH
:月中的周数
天数相关:
Calendar.DAY_OF_MONTH
:月份中的天数Calendar.DAY_OF_YEAR
:年份中的天数
时间相关:
Calendar.HOUR
:12 小时制的小时Calendar.HOUR_OF_DAY
:24 小时制的小时Calendar.MINUTE
:分钟Calendar.SECOND
:秒Calendar.MILLISECOND
:毫秒
可以使用get()方法对这些日期属性进行获取,add()方法对日期进行加减操作。set()设置对应的值。
get():
Calendar c = Calendar.getInstance();
System.out.println(c.get(Calendar.YEAR));
System.out.println(c.get(Calendar.MONTH));
System.out.println(c.get(Calendar.DAY_OF_MONTH));
System.out.println(c.get(Calendar.HOUR_OF_DAY));
System.out.println(c.get(Calendar.MINUTE));
add()
Calendar c = Calendar.getInstance();
c.add(Calendar.DAY_OF_MONTH,5);//天数+5
c.add(Calendar.YEAR,1);//年份+1
System.out.println(c.getTime());//getTime()返回Date类型
add方法指定的值可以是负数,表示减去对应的当量。
set():
Calendar c = Calendar.getInstance();
c.set(Calendar.DAY_OF_MONTH,1);
c.set(Calendar.MONTH,4);
System.out.println(c.getTime());
//这里set年份是自然年,月份有对应的常量值
c.set(2024,Calendar.MARCH,1);
System.out.println(c.getTime());
另外Calendar还支持时区设置
Calendar c2 = Calendar.getInstance(TimeZone.getTimeZone("America/New_York");
System.out.println(c2.getTime());
SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//日期格式化成字符串
String dataStr = sdf.format(new Date());
System.out.println(dataStr);
//字符串转换成日期
Date date = sdf.parse(dataStr);
System.out.println(date);
SimpleDateFormat创建时候需要指定一个日期pattern格式,支持常用格式如下:
y
:年份(如 2024)M
:月份(1-12)d
:日期(1-31)H
:小时(0-23)m
:分钟(0-59)s
:秒(0-59)S
:毫秒E
:星期几(如 “星期一”)D
:一年中的第几天(1-365)F
:一个月中的第几个星期几(1-5,每月的第一个星期几)w
:一年中的第几个星期(1-53)W
:一个月中的第几个星期(1-5)
这里要注意:这些格式是区分大小写的,像y和Y代表的就是不同的,Y代表的周年数。
如下:
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd");
Date date = sdf.parse("2023-12-31");
System.out.println(date);
sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.format(date));
最后输出的是:2023-01-01。
另外一点需要注意SimpleDateFormat是线程不安全的,这是在于其内部维护了一个 Calendar 对象来进行日期时间的格式化和解析操作。如果它们共享同一个 SimpleDateFormat
实例,就会共享同一个 Calendar
对象,这样就可能导致 Calendar
对象的状态被多个线程同时修改,从而破坏了线程安全性。多线程情况下最好是每个线程创建一个对应的SimpleDateFormat实例进行日期操作。
新日期时间处理类
在java8之前使用上面说的几个类进行日期处理。Java 8 引入了全新的日期时间处理类,主要位于 java.time
包中。其中最常用的类包括:
LocalDate
:表示一个不含时区的日期,例如 2024-02-29。LocalTime
:表示一个不含时区的时间,例如 12:30:00。LocalDateTime
:表示一个不含时区的日期时间,例如 2024-02-29T12:30:00。ZonedDateTime
:表示一个带时区的日期时间。Instant
:表示时间戳,通常用于机器时间。Duration
:表示时间段,例如持续几小时、几分钟等。Period
:表示日期段,例如持续几天、几个月等。
LocalDate用来表示日期,LocalTime用来表示时间,LocalDateTime表示一个完整的日期时间。这三个类中的操作方法差不多,都可以根据指定的值来构造日期时间,日期计算和格式化输出。
LocalDate
//当前日期
LocalDate date = LocalDate.now();
System.out.println(date.toString());//2024-02-29
//指定日期创建
date = LocalDate.of(2024, 4, 1);
System.out.println(date.toString());//2024-04-01
//字符串转换
date = LocalDate.parse("2024-03-01");
//2024年3月1日
System.out.println(date.getYear()+"年"+date.getMonthValue()+"月"+date.getDayOfMonth()+"日");
//日期格式化
System.out.println(date.format(DateTimeFormatter.ofPattern("yy年MM月dd日")));
//日期计算:天数+1
System.out.println(date.plusDays(1));
//日期计算,按指定单位计算
System.out.println(date.minus(1, ChronoUnit.MONTHS));
//判断是否是闰年
date.isLeapYear();
//日期比较
date.isAfter(LocalDate.parse("2023-12-31"));
LocalTime
LocalTime time = LocalTime.now();
System.out.println(time);
time = LocalTime.of(12,30,0);
System.out.println(time);
time = LocalTime.parse("12:31:30");
System.out.println(time.getHour());
//时间计算
System.out.println(time.plusHours(1));
System.out.println(time.minus(5,ChronoUnit.MINUTES));
//时间比较
time.isAfter(LocalTime.parse("07:30"));
time = LocalTime.MAX;//23:59:59.99
time = LocalTime.MIN;//00:00
//格式化
System.out.println(time.format(DateTimeFormatter.ofPattern("HH:mm:ss")));
LocalDateTime
LocalDateTime dateTime = LocalDateTime.now();
//日期转字符串
String dataStr = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(" " + dataStr);
//根据LocalDate和LocalTime获取时间
dateTime = LocalDateTime.of(LocalDate.of(2024,2,29),LocalTime.now());
//根据年月日时分秒获取时间
dateTime = LocalDateTime.of(2024,2,29,12,30,29);
//字符串转日期
dateTime = LocalDateTime.parse("2024-02-29T06:30:00");
LocalDateTime.parse("2024-02-29 06:30:00",DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateTime.getDayOfYear());
//日期计算
dateTime.plusDays(1);
dateTime.minus(1,ChronoUnit.MONTHS);
//日期比较
dateTime.isEqual(LocalDateTime.now());
ZonedDateTime
带时区的时间
//所有可用的时区
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
for (String zoneId : allZoneIds) {
System.out.println(zoneId);
}
//根据LocalDateTime和ZoneId创建ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.now();
System.out.println(zdt);//2024-02-29T15:42:41.095+08:00[Asia/Shanghai]
zdt = ZonedDateTime.of(LocalDateTime.now(),ZoneId.of("Asia/Tokyo"));
System.out.println(zdt);//2024-02-29T15:42:41.096+09:00[Asia/Tokyo]
System.out.println(zdt.getHour());//15
zdt =ZonedDateTime.parse("2024-02-29T10:30:30+08:00[Asia/Shanghai]");
//时区转换
zdt =zdt.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(zdt);//2024-02-29T11:30:30+09:00[Asia/Tokyo]
OffsetDateTime odt = OffsetDateTime.of(LocalDateTime.now(),ZoneOffset.of("+02:00"));
System.out.println(odt);//2024-02-29T15:42:41.119+02:00
withZoneSameInstant()方法可以进行时区转换,自动按时区差值进行计算对应的小时。
Instant
当前时点时间戳时间,从1970年开始计算秒数。
Instant instant = Instant.now();
//获取秒数
System.out.println(instant.getEpochSecond());
//获取纳秒数
System.out.println(instant.getNano());
Period & Duration
Period 和Duration都表示时间之间的间隔,只不过Period是以月、日、年这种为单位表示的一段时间,用来计算LocalDate,Duration是以秒,纳秒为单位表示持续一段时间,用来计算LocalTime。
LocalDate startDate = LocalDate.parse("2024-01-15");
LocalDate endDate = startDate.plus(Period.ofDays(45));
System.out.println("结束日期:"+endDate);
Period period = Period.between(startDate, endDate);
System.out.println("相差:"+period.getYears()+"年"+period.getMonths()+"月"+period.getDays()+"日");
System.out.println("相差:"+ChronoUnit.DAYS.between(startDate,endDate)+"天");
/**
输出:
结束日期:2024-02-29
相差:0年1月14日
相差:45天
*/
LocalTime startTime = LocalTime.of(12,30,0);
LocalTime endTime = startTime.plus(Duration.ofMinutes(15)).plus(Duration.ofHours(1));
System.out.println("结束时间:"+endTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")));
Duration duration = Duration.between(startTime, endTime);
System.out.println(duration.getSeconds());
/**
结束时间:13:45:00
相差:4500
相差:4500
*/
这里看到计算两个日期时间差用Period时最后计算的结果是进行按对应时间属性进行计算的,差xx年xx月xx日这种,不会返回相隔实际天数,完全转换成相隔天数需要使用ChronoUnit.DAYS。
兼容旧日期时间
LocalDateTime可以兼容早期的Date和Calendar类
LocalDateTime dateTime = LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault());
Calendar calendar = Calendar.getInstance();
dateTime = LocalDateTime.ofInstant(calendar.toInstant(),ZoneId.systemDefault());
//根据时间戳秒创建时间
dateTime = LocalDateTime.ofEpochSecond(new Date().getTime()/1000,0,ZoneOffset.ofHours(8));