一、前言
java标准库中,最早提供了两种处理日期和时间的类,但是由于很多问题,很多方法都已经弃用了,在JAVA8中引入了java.time包,解决了长久以来存在的诸多弊端。java原本自带的java.util.Date和java.util.Calendar类,实际上这两种类会有线程不安全的风险。
二、jdk8之前的api
一、java.lang.System类
1、System类提供的public static longcurrentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。此方法使用于计算时间差
2、计算世界时间的主要标准有
- UTC(Coordinated Universal Time):协调世界时间
- GMT(Greenwich Mean Time):格林尼治时间,也是世界时
- CST(Central Standard Time):古巴时间
二、java.util.Date
一、构造器:
1、Date():使用无参构造器创建的对象可以获取本地当前时间。
2、Date(long date)
二、常用方法
1、getTime():返回自1970年1月1日00:00:00 GMT以来此Date对象表示的毫秒数。
2、toString():把此Date对象转换为以下形式的String:dowmondd hh:mm:ss zzz
yyyy其中:dow是一周中的某一天(Sun, Mon, Tue,Wed, Thu, Fri, Sat),zzz是时间
标准。
3、其他把部分方法都过时了
三、java.text.SimplateDateFormat类
1、Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
他允许进行:
格式化:日期->文本
解析:文本->日期
2、格式化:
1、SimpateDateFormat():使用默认的模式和语言环境创建对象。
2、该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:3的方法
3、public String format(Date date):方法格式化时间对象date
3、解析:
1、public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。
四、栗子
// 产生一个Date实例
Date date= newDate();
// 产生一个formater格式化的实例 SimpleDateFormat formater = new SimpleDateFormat(); // 打印输出默认的格式 System.out.println(formater.format(date));
SimpleDateFormat formater2= new SimpleDateFormat("yyyy年MM月dd日EEE HH:mm:ss");
System.out.println(formater2.format(date));try{
// 实例化一个指定的格式对象
Date date2= formater2.parse("2008年08月08日星期一08:08:08");
// 将指定的日期解析后格式化按指定的格式输出
System.out.println(date2.toString());
} catch(ParseException e)
{
e.printStackTrace();
}
三、java.util.Calendar日历类
一、Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
二、获取Calendar实例的方法
1、使用Calendar.getInstance()方法
2、调用它的子类GregorianCalendar的构造器。
三、一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY、MINUTE、SECOND
1、public void set(int field,int value)
2、public void add(int field,int amount)
3、public final Date getTime()
4、public final void setTime(Date date)
注意:获取月份时:一月是0,二月是1,以此类推,12月是11。获取星期时:周日是1,周二是2,。。。。周六是7
四、栗子
Calendar calendar = Calendar.getInstance(); // 从一个Calendar 对象中获取Date 对象 Date date = calendar.getTime(); System.out.println("data1:+"+date); // 使用给定的Date 设置此Calendar 的时间 date = new Date(234234235235L); System.out.println("data2:+"+date); calendar.setTime(date); calendar.set(Calendar.DAY_OF_MONTH, 8); System.out.println("设置为当前时间日期为8号,时间是:" + calendar.getTime()); calendar.add(Calendar.HOUR, 2); System.out.println("当前时间加2小时后,时间是:" + calendar.getTime()); calendar.add(Calendar.MONTH, -2); System.out.println("当前日期减2个月后,时间是:" + calendar.getTime()data1:+Sat Mar 18 17:04:06 CST 2023
data2:+Sat Jun 04 09:03:55 CST 1977
设置为当前时间日期为8号,时间是:Wed Jun 08 09:03:55 CST 1977
当前时间加2小时后,时间是:Wed Jun 08 11:03:55 CST 1977
当前日期减2个月后,时间是:Fri Apr 08 11:03:55 CST 1977
四、为什么会出现新的日期API
一、jdk8之前的SimpleDateFormat会有线程安全问题
1、栗子
使用十个线程,同一个SimpleDateFormat 对象解析日期文本 public class DateTest { final static SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ try { Date parse = simple.parse("2023-03-18 18:07:04"); System.out.println(parse); } catch (ParseException e) { e.printStackTrace(); } }).start(); } } }
2、结果
3、原因
在simple.parse("2023-03-18 18:07:04");这个方法里,
二、Date类以及Calendar类问题2
Date类以及Calendar类出现之前,枚举类型是还没有出现的,所以在使用Calendar设置日期、月份、时间,(都是直接使用整型放到方法参数里,魔法值)所以在字段中使用整数常量导致整数常量都是可变的,而不是线程安全的,且还不满足开发规范。为了解决这个问题,标准库中就引入了,java.sql.Date作为java.util.Date的子类,但是还是没能彻底解决这个问题。
三、其他问题
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date有用,Calendar则不行。
五、jdk8中新日期时间API
jdk8提供的最新类均生成不可变实例,他们是线程安全的,并且这些类不提供公共构造函数,只能通过工厂方法加以实例化。
一、常用类概述与功能介绍
一、Instant类:返回的当前时间,是格林尼治时间,
instant类对时间轴上的单一瞬时点建模,也就是时间戳。可用于记录应用程序中的事件时间戳。
java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位
二、Duration类
表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性。
//Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());
三、Period类
表示一段时间的年、月、日
//Period:用于计算两个“日期”间隔,以年、月、日衡量
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);
四、LocalDate(yyyy-MM-dd)、LocalTime(秒后面表示的是纳秒)、LocalDateTime
1、表示不可变的日期时间对象,表示使用ISO-8601日历系统的日期、时间、日期和时间。
它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息
LocalDateTime localDateTime = LocalDateTime.of(2023, Month.MARCH,23,12,12,12);
// LocalDateTime对象只是封装了一个时间,并没有时区相关的数据,所以要添加时区信息到对象中。
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
// 更改时区查看其他时区的当前时间
ZonedDateTime tokyoZonedDateTime1 = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
2、java8中日期相关的api中的所有实例都是不可变,一旦创建就无法修改他们,类似于String,这对于线程安全非常有利。
3、plus方法在LocalDate、LocalTime中的应用,以minus开头的就是减少,实际上也是调用的plus方法只是入参传的是负数。
4、with方法在LocalDateTime中的使用
五、ZonedDateTime类:表示的是当前时区的当前时间
具有时区的日期时间的不可变表示,此类存储的所有日期和时间字段,精确到纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。
在java中获取时区,通过ZoneId的getAvailableZoneIds方法可以获取到一个Set集合,集合中封装了600个时区。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历
六、新时间日期API包
java.time–包含值对象的基础包
java.time.chrono–提供对不同的日历系统的访问
java.time.format–格式化和解析时间和日期
java.time.temporal–包括底层框架和扩展特性
java.time.zone–包含时区支持的类
一、格式化与解析日期或时间:
与SimpleDateFormat不同的是,新版本的日期/时间API的格式化与解析不需要再创建转换器对象了,通过时间日期对象的parse/format方法可以直接进行转换.
java.time.format.DateTimeFormatter类:各类提供了三种格式化方法
1、预定义的标准格式。如:
ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
2、本地化相关的格式。如:
ofLocalizedDateTime(FormatStyle.LONG)
通过DateTimeFormatter的ofLocalizedDate的方法也可以调整格式化的方式。
public static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) {
Objects.requireNonNull(dataStyle, message:"dateStyle");
return new DateTimeFormatterBuilder().appendLocalized(dateStyle,timeStyle:"null")
.toFormatter(ResolverStyle.Smart, IsoChronology.Instance);
}
此方法需要传入一个FormatStyle类对象,FormaStyle对象是一个枚举类,其中有几种方式如下:
Full:全显示(年月日+星期) Long:全显示(年月日) Medium:缩略显示(没有年月日汉字) SHORT:精简显示(精简年+月日)
3、自定义的格式。如:
ofPattern(“yyyy-MM-dd hh:mm:ss”)
七、调节器TemporalAdjuster与查询TemporalQuery
一、TemporalAdjuster使用
LocalDate localDate = LocalDate.now();
LocalDate with = localDate.with(TemporalAdjusters.firstDayOfMonth());
返回结果:
2023-05-01
TemporalAdjusters中其他方法类似使用
TemporalAdjusters类中常用静态方法的使用
static TemporalAdjuster firstDayofNextMonth() 下个月的第一天
static TemporalAdjuster firstDayOfNextYear() 下一年的第一天
static TemporalAdjuster firstDayOfYear() 当年的第一天
注意:TemporalAdjusters 是一个接口,with方法实际上传入的是这个接口的实现类对象,TemporalAdjusters并不是TemporalAdjuster的实现类,只不过TemporalAdjusters的静态方法实现了TemporalAdjuster,并且将实现类对象返回了。
二、TemporalAdjusters中DayOfWeek枚举类使用
三、自定义 TemporalAdjuster调节器
通过Java8本身提供的TemporalAdjusters中的方法可以完成一些常用的操作,如果要自定义日期时间的更改逻辑,可以通过实现TemporalAdjuster类接口的方式来完成。
1、创建类实现TemporalAdjuster接口
2、实现TemporalAdjuster中的 adjusterInto()方法,传入一个日期时间对象,完成逻辑之后返回日期事件对象。
3、通过with方法传入自定义调节器对象完成更改。
第一种写法:实现对应接口 public class PayDayAdjuster implements TemporalAdjuster { @Override public Temporal adjustInto(Temporal temporal) { if (Objects.isNull(temporal)) { return null; } LocalDate localDate = LocalDate.from(temporal); int dayOfMonth = localDate.getDayOfMonth(); LocalDate moneyDayOfMonth = dayOfMonth == 15 ? localDate.withDayOfMonth(dayOfMonth) : localDate.withDayOfMonth(15); DayOfWeek dayOfWeek = moneyDayOfMonth.getDayOfWeek(); if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) { moneyDayOfMonth = moneyDayOfMonth.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)); } return moneyDayOfMonth; } }
public class DateTest { public static void main(String[] args) { // 第一种写法 LocalDate localDate1 = LocalDate.now(); PayDayAdjuster payDayAdjuster = new PayDayAdjuster(); LocalDate date1 = LocalDate.from(payDayAdjuster.adjustInto(localDate1)); // 第二种写法 LocalDate localDate2 = LocalDate.now().with(temporal -> { if (Objects.isNull(temporal)) { return null; } LocalDate localDate = LocalDate.from(temporal); int dayOfMonth = localDate.getDayOfMonth(); LocalDate moneyDayOfMonth = dayOfMonth == 15 ? localDate.withDayOfMonth(dayOfMonth) : localDate.withDayOfMonth(15); DayOfWeek dayOfWeek = moneyDayOfMonth.getDayOfWeek(); if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) { moneyDayOfMonth = moneyDayOfMonth.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)); } return moneyDayOfMonth; }); } }
四、TemporalQuery的应用
LocalDate,LocalTime)都有一个方法叫做query,可以针对日期进行查询,R query(TemporalQuery query)这个方法是一个泛型方法,返回的数据就是传入的泛型类的类型,TemporalQuery是一个泛型接口,里面有一个抽象方法是R queryFrom(TemporalAccessor temporal),TemporalAccessor是Temporal的父接口,实际上也就是LocalDate,LocalDateTime相关类的顶级父接口,这个queryFrom的方法的实现逻辑就是,传入一个日期/时间对象通过自定义逻辑返回数据。
public class DateTest3 { public static void main(String[] args) { LocalDate now = LocalDate.now(); System.out.println(getDay(now)); } public static Long getDay(LocalDate now) { return now.query(temporalAccessor -> { // temporalAccessor 转换为当前时间 LocalDate from = LocalDate.from(temporalAccessor); // 取出当前时间是哪一年,设置这一年的五一 LocalDate localDate = LocalDate.of(from.getYear(), Month.MAY, 1); // 判断当前时间是否在当年的五一时间之后,如果是年份加一 if (from.isAfter(localDate)) { localDate = localDate.plus(1, ChronoUnit.YEARS); } // 计算天数差 return ChronoUnit.DAYS.between(from, localDate); }); } }
二、时间类之间的转换
一、java.util.Date转换为java.time.LocalDate
Java8中的java.time中并没有提供太多的内置方式来转换java.util包中用预处理标准日期和时间的类,我们可以使用Instant类作为中介,也可以使用java.sql.Date和java.sql.TimeStamp类提供的方法进行转。
java.time包中并没有提供很多的方式来进行直接转换,但是给之前的Date类,Calendar类在java1.8都提供了一个新的方法,叫做toInstant(),可以将当前对象转换为Instant对象,通过给Instan添加时区信息之后就可以转换为LocalDate对象。
如果想自己指定时区可以使用 LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.of(ZoneId.SHORT_IDS.get("CTT")));ZoneId.SHORT_IDS里面是一个map,封装了时区字符串
二、 java.sql.Date与java.sql.Timestamp的转换方式
java.sql.Date类中提供直接转换为LocalDate的方法,toLocalDate()
java.sql.Timestamp类是时间戳对象,通过传入一个毫秒值对象进行初始化
三、Calendar转换为ZonedDateTime
Calendar对象字Java1.1开始提供了一个方法获取时区对象的方法,getTimeZone(),要将Calendar对象转换为ZonedDateTime需要先获取到时区对象。从Java1.8开始TimeZone类提供了一个方法可以获取到ZonedId。获取到ZonedId之后就可以初始化ZOnedDateTime对象了,ZonedDateTime类有一个ofInstant()方法,可以将一个Instant对象和ZonedId对象作为参数传入构造一个ZonedDateTime对象。
四、Calendar转换为LocalDateTime
java.util.Calendar类转换为java.time.LocalDateTime类
Calendar对象可以获取到年月日时分秒的信息,这些信息可以作为LocalDateTime构造方法的参数