目录
一、前言
二、本地日期时间
1、LocalDate
2、LocalTime
3、LocalDateTime
4、格式化日期时间DateTimeFormatter
二、时间戳、日期段、时间段
三、时区日期时间
1、ZonedDateTime
2、ZoneId
四、小结
一、前言
Java8之前,Java日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理,看上去有点混乱。而日期类并不提供国际化,没有时区支持,因此还需要引入java.util.Caleandar类和java.util.TimeZone类,导致时区处理比较繁琐。另外,java.util.Date是非线程安全的,所有的日期类都是可变的,这也是一个痛点。
针对上述种种,Java8重写了一套关于所有处理日期、时间、日期/时间、时区、时刻(instants)、时间段(Duration)、日期段(Period)、时钟(clock)等操作的API,统一放在了java.time包下,结束了以前存在的混乱,繁琐和安全等问题。其中的Local(本地)简化了日期时间的处理,没有时区的问题;Zoned(时区)通过制定的时区处理日期时间。
二、本地日期时间
1、LocalDate
- 它是一个不可变的日期时间对象(线程安全的),表示日期,通常被视为年月日,默认日期格式为:yyyy-MM-dd,(注意:YYYY代表的是week year —— 即当天所在的周属于的年份,存在跨年问题谨慎使用!yyyy代表标准的年份);
- 也可以访问其他日期字段,例如日期,星期几和星期等;
- 该类是对日期的描述,不能存储或表示时间或时区,也不能代表时间线上的即时信息,没有附加信息,如偏移或时区;
- 日期相关API的demo演示如下:
/**
* Java8日期时间类API Demo
*
* @createTime 2020/10/20 21:41
* @author weixiangxiang
*/
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// 创建LocalDate对象的四种方式,默认日期格式为:yyyy-MM-dd
LocalDate currDate = LocalDate.now();// 当前日期
LocalDate cDate1 = LocalDate.of(2020, 10, 12);//指定日期1
LocalDate cDate2 = LocalDate.of(2020, Month.OCTOBER, 9);//指定日期2
LocalDate pDate = LocalDate.parse("2020-10-09");
logger.info("当前日期为:" + currDate + "/" + cDate1 + "/" + pDate);
// 获取日期的年月日周
int year = currDate.getYear(); // 年
int month = currDate.getMonth().getValue(); // 月
int monthValue = currDate.getMonthValue(); // 月
int dayOfWeek = currDate.getDayOfWeek().getValue(); // 周的第几天
int day = currDate.getDayOfMonth(); // 月的第几天
int dayOfYear = currDate.getDayOfYear(); // 年的第几天
logger.info("测试今天的日期:" + year + "/" + month + "/" + day);
// 日期有关的判断比较、加减运算、日期推算 :中plus或plusXXX表示往后推,minus或minusXXX表示往前推,with或withXXX表示更改成指定日期
logger.info("今年是否为闰年:"+currDate.isLeapYear());
logger.info("比较两个日期是否相同-1:"+cDate1.equals(cDate2));
logger.info("比较两个日期是否相同-2:"+cDate1.isEqual(cDate2));
logger.info("cDate1是否在比较的日期之前:"+cDate1.isBefore(cDate2));
logger.info("cDate1是否在比较的日期之后:"+cDate1.isAfter(cDate2));
logger.info("计算相差日期为:" + cDate1.compareTo(cDate2));
logger.info("当前日期加一年:" + currDate.plusYears(1));
logger.info("当前日期加一年:" + currDate.plus(1, ChronoUnit.YEARS));
logger.info("当前日期减一年:" + currDate.minusYears(1));
logger.info("当前日期减一年:" + currDate.minus(1, ChronoUnit.YEARS));
logger.info("当前日期加一个月:" + currDate.plusMonths(1));
logger.info("当前日期加一周:" + currDate.plusWeeks(1));
logger.info("当前日期加一天:" + currDate.plusDays(1));
logger.info("更改当前日期的年:"+currDate.withYear(2016));
logger.info("更改当前日期的月:"+currDate.withMonth(8));
logger.info("更改当前日期的日:"+currDate.withDayOfMonth(7));
logger.info("更改到当前日期的第几天:"+currDate.withDayOfYear(47));
logger.info("取本年的第一天日期:"+currDate.with(TemporalAdjusters.firstDayOfYear()));
logger.info("取本年的最后一天日期:"+currDate.with(TemporalAdjusters.lastDayOfYear()));
logger.info("取明年的第一天日期:"+currDate.with(TemporalAdjusters.firstDayOfNextYear()));
logger.info("取明年的最后一天日期:"+currDate.plusYears(1).with(TemporalAdjusters.lastDayOfYear()));
logger.info("取本月的第一天日期:"+currDate.with(TemporalAdjusters.firstDayOfMonth()));
logger.info("取本月的第n天日期(如n=18):"+currDate.withDayOfMonth(18));
logger.info("取下个月的第一天日期:"+currDate.with(TemporalAdjusters.firstDayOfNextMonth()));
logger.info("取下个月的最后一天日期:"+currDate.plusMonths(1).with(TemporalAdjusters.lastDayOfMonth()));
logger.info("取上个月的第一天日期:"+currDate.minusMonths(1).with(TemporalAdjusters.firstDayOfMonth()));
logger.info("取上个月的第一天日期:"+currDate.minus(1, ChronoUnit.MONTHS).with(TemporalAdjusters.firstDayOfMonth()));
logger.info("取上个月的最后一天日期:"+currDate.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth()));
}
}
2、LocalTime
- 它是一个不可变的时间对象(线程安全的),代表一个时间,通常被看作是时分秒,精度为纳秒,默认时间格式为:HH:mm:ss.nnn,(注意:H代表使用24小时制,h代表使用12小时制);
- 它不能存储或表示日期或时区。 相反,它是在挂钟上看到的当地时间的描述, 它不能代表时间线上的即时信息,而没有附加信息,如偏移或时区;
- 时间相关API的demo演示如下:
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// 创建LocalTime对象的三种方式 ,默认时间格式为:HH:mm:ss.nnn
LocalTime currTime = LocalTime.now();
LocalTime cTime = LocalTime.of(20, 10, 10);
LocalTime pTime = LocalTime.parse("20:19:18");
logger.info("当前时间为(默认精确到毫秒ms):" + currTime); //20:19:18.684
logger.info("指定时间为:" + cTime + " " + pTime); //20:10:10 20:19:18
// 获取小时、分钟、秒、纳秒
logger.info("当前时间报时:" + currTime.getHour() + currTime.getMinute() +
currTime.getSecond() + currTime.getNano());
// 时间常量
logger.info("最大时间:" + LocalTime.MAX); //23:59:59.999999999
logger.info("最小时间:" + LocalTime.MIN); //00:00
logger.info("凌晨零时时间:" + LocalTime.MIDNIGHT); //00:00
logger.info("中午时间:" + LocalTime.NOON); //12:00
// 日期推算:plus或plusXXX表示往后推,minus或minusXXX表示往前推,with或withXXX表示更改成指定日期
logger.info("当前时间往后推1个小时:" + currTime.plus(1,ChronoUnit.HOURS));
logger.info("当前时间往后推5分钟:" + currTime.plusMinutes(5));
logger.info("当前时间往前推10秒:" + currTime.minusSeconds(10));
logger.info("当前时间往前推5纳秒:" + currTime.minus(5,ChronoUnit.NANOS));
}
}
3、LocalDateTime
- 它是一个不可变的日期时间对象,代表日期时间,通常被视为年 - 月 - 日 - 时 - 分 - 秒,默认日期时间格式为:yyyy-MM-ddTHH:mm:ss.nnn;
- 该类不存储或表示时区,它不能代表时间线上的即时信息,而没有附加信息,如偏移或时区;
- 日期时间相关API的demo演示如下:
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// 创建LocalDateTime对象的三种方式 ,默认日期时间格式为:yyyy-MM-ddTHH:mm:ss.nnn
LocalDateTime currDateTime = LocalDateTime.now();
LocalDateTime cDateTime =LocalDateTime.of(2020,10,9,23,50,0,0);
LocalDateTime pDateTime = LocalDateTime.parse("2020-10-09T23:57:43.280");
logger.info("当前日期时间为:" + currDateTime); // 如2020-10-09T23:57:43.280
logger.info("当前日期时间为:" + cDateTime); // 如2020-10-09T23:50
logger.info("当前日期时间为:" + pDateTime); // 如2020-10-09T23:57:43.280
// LocalDate、LocalTime与LocalDateTime转换
LocalDateTime ld1 = currDate.atTime(currTime);
LocalDateTime ld2 = currTime.atDate(currDate);
logger.info("当前日期时间为:" + ld1); // 如2020-10-09T23:19:18.684
logger.info("当前日期时间为:" + ld2); // 如2020-10-09T23:19:18.684
}
}
4、格式化日期时间DateTimeFormatter
- 该类位于java.time.format包下,用于打印和解析日期时间对象;
- 有三种格式化类型:(使用模版预定义常量 —— 如BASIC_ISO_DATE、使用模式字母 —— 如yyyy-MM-dd HH:mm:s、使用本地化样式 —— 如long或medium);
- 格式化方法 —— format(),解析方法 —— parse() 。格式化可以使用DateTimeFormatter预置的格式,也可以通过DateTimeFormatter.ofPattern方法来指定格式。
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// 从对应模版的常量指定需要的输出格式
logger.info("指定格式为yyyyMMdd:" + LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE));
logger.info("指定格式为yyyy-MM-ddTHH:mm:ss.nnn:" + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
logger.info("指定格式为yyyy-MM-ddTHH:mm:ss.nnn:" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// 自定义输出格式,常见的有以下三种
logger.info("指定格式为yyyyMMddHHmmss:" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now())); // 如20201010011859
logger.info("指定格式为yyyy-MM-dd HH:mm:ss:" + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now())); // 如2020-10-10 01:18:59
logger.info("指定格式为yyyy/MM/dd HH:mm:ss:" + DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now())); // 如2020/10/10 01:18:59
logger.info("解析:" + LocalDateTime.parse("20201010011830", DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); // 如2020-10-10T01:18:30
}
}
二、时间戳、日期段、时间段
- 时间戳Instant:它是以Unix元年(默认传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算;
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// 可以通过now()/ofEpochMilli()/ofEpochSecond()/parse()等方式创建时间戳对象
Instant instant = Instant.now();
logger.info("当前时间(UTC时区)为:" + instant); // 如2020-10-09T16:24:26.195Z
logger.info("获取时间戳为:" + instant.toEpochMilli()); // 如1602260666195
logger.info("当前时间偏移量6h运算:" + instant.atOffset(ZoneOffset.ofHours(6))); // 如2020-10-09T22:24:26.195+06:00
logger.info("以Unix元年为起点,进行偏移量120s运算:" + Instant.ofEpochSecond(120)); // 如1970-01-01T00:02:00Z
}
}
- 日期段Period:以年,月和日为单位建立数量或时间量,这个类是不可变的和线程安全的。
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// 均可通过通过of方法、ofXXX方法和between方法创建Period
Period period = Period.of(2020, 10, 10);
logger.info("当前日期:" + period); //P2020Y10M10D
logger.info("当前日期年份:" + period.getYears());
logger.info("当前日期月份:" + period.getMonths());
logger.info("当前日期几号:" + period.getDays());
Period bPeriod = Period.between(cDate1,cDate2);
logger.info("当前日期:" + bPeriod); //P7D
logger.info("间隔年数:" + bPeriod.getYears()); //0
logger.info("间隔月数:" + bPeriod.getMonths()); //0
logger.info("间隔天数:" + bPeriod.getDays()); //7
}
}
- 时间段Duration:该类以秒和纳秒为单位建立数量或时间量,它可以使用其他基于持续时间的单位进行访问,例如分钟和小时。这个类是不可变的和线程安全的。
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
LocalDateTime cDateTime =LocalDateTime.of(2020,10,9,23,50,0,0);
LocalDateTime pDateTime = LocalDateTime.parse("2020-10-09T23:57:43.280");
// 均可通过通过of方法、ofXXX方法和between方法创建Duration
Duration duration = Duration.between(cDateTime,pDateTime); //from - to
logger.info("间隔天数:" + duration.toDays()); //7
logger.info("间隔小时数:" + duration.toHours()); //168
logger.info("间隔毫秒数:" + duration.toMillis()); //605263280
Duration durationDay = Duration.of(7, ChronoUnit.DAYS);
Duration durationHours = Duration.ofHours(2);
Duration durationMin = Duration.ofMinutes(60);
Duration durationMill = Duration.of(10000,ChronoUnit.MILLIS);
// 距当前时间比较产生的间隔
logger.info("间隔小时数:" + durationDay); //PT168H
logger.info("间隔分钟数:" + durationHours.toMinutes()); //120
logger.info("间隔分钟数:" + durationMin.toMinutes()); //60
logger.info("间隔分钟数:" +durationMill); //PT10S
}
}
三、时区日期时间
1、ZonedDateTime
- 表示不可变的具有时区的日期时间,此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。例如,值“2020-10-03T10:15:30+01:00 Europe/Paris”可以存储在ZonedDateTime;
- ZonedDateTime拥有相当于三个独立对象的状态,一个
LocalDateTime
,一个ZoneId
和ZoneOffset
。偏移量ZoneOffset
和本地日期时间LocalDateTime
用于在必要时定义一个时间戳。 区域ID用于获取偏移量如何以及何时更改的规则。偏移不能自由设置,因为区域控制哪些偏移是有效的。
public class XxxTest{
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// ZoneId表示不同的时区,大约有40不同的时区。
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
logger.info("获取当前时区:" + ZoneId.systemDefault()); // 如Asia/Shanghai
// 创建时区日期时间对象
logger.info("创建当前时区的日期时间:" + ZonedDateTime.now());
logger.info("创建当前时区的日期时间:" + ZonedDateTime.parse("2020-10-10T01:26:23.765+08:00[Asia/Shanghai]"));
// LocalDateTime转换成特定的时区
ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Shanghai"));
logger.info("获取特定时区:" + zonedDateTime); // 如2020-10-10T01:26:23.765+08:00[Asia/Shanghai]
}
}
2、ZoneId
- 用于识别用于在Instant和LocalDateTime之间转换的规则,有两种不同类型的ID:固定偏移量 —— 从UTC /格林威治完全解析的偏移量,对所有本地日期时间使用相同的偏移量;地理区域 —— 适用于查找UTC /格林威治的偏移量的特定规则集的区域。大多数固定偏移量由ZoneOffset表示。
- 更详细的解释可以去查看ZoneId的API说明。
四、小结
至此,Java8新增的日期时间API已分析完毕,需要更详细的方法说明或解释,可以去查找API文档了解,日期时间API的核心就是LocalDate、LocalTime、LocalDateTime、Instant、Period、Duration了,掌握并熟练的使用它们是后端必备的,也可以通过这些类提供的API封装成自己的日期时间工具类,满足项目中对各种日期时间的需求。不积硅步无以至千里,点滴付出终将有所收获,共同进步 ~