欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

jdk1.8--新日期和时间处理

程序员文章站 2022-07-13 16:46:48
...

java老日期处理API的诟病

Date

java的时间处理类Date一直被人们所诟病,这个类无法表示日期,只能以毫秒的精度表示时间,如果想获得年、月、日等信息需要借助Calender类;另外如果通过Date的构造函数来创建Date非常别扭,比如要创建2018年10月18日这个日期:

Date date = new Date(118,9,18);
System.out.println(date);

输出为:

Thu Oct 18 00:00:00 CST 2018

第一个参数118,表示年份,从1900算起(是不是很别扭);第二参数9,表示月份,其实是表示10月,因为月份是从0算起(是不是更别扭);第三个参数18,表示日,还算正常。

 

Calendar

可见 用Date带参数的构造函数创建日期,后面引入了Calendar.set(year + 1900, month, date)进行替代,具体可见Date的api:

jdk1.8--新日期和时间处理
            
    
    博客分类: jdk1.8 java8LocalDateLocalTime 

具体用法:

Calendar cl = Calendar.getInstance();
cl.set(2018, 9, 18);
System.out.println(cl.getTime());

输出:

Thu Oct 18 08:45:05 CST 2018

可见Calendar对年份的处理进行了改进,但对月份还是处理还是从0开始表示第一个月,即这里的9表示的是10月(依然很别扭)。

作为Date类的补充,Calendar中提供了很多有用的方法,比如获取月份的最大天数,获取年份、月份、日期等api。

另外需要注意的是Date和Calendar都是可变类(相对的 String、Integer等是不可变类)

 

DateFormat

另外你会惊奇的发现,无论是Date还是Calendar类中都没有对日期进行格式化处理的方法,又不得不引入的DateFormat这个API(实现类SimpleDateFormat)。但由于SimpleDateFormat中使用了Calender,Calender前面提到过是可变类设计,很容易引起线程安全问题。比如下面的代码:

public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); 

这行代码在多线程高并发的情况下会有线程安全问题,其实这是一种错误的做法,正确的做法是每次使用时都new一个SimpleDateFormat;或者放到ThreaLocal中。

 

上述是java的Date、Calendar、DateFormat这些API中部分为人们所诟病的问题,对于有经验的老鸟来说,都可以一一化解,但有时也会一不小心就会掉到坑里。这也是java8引入新的日期出来api的直接原因。

 

java新日期和时间API

LocalDate

LocalDate是不可变类设计,所谓不可变类简单的理解就是初始化后就不能被修改,不可变类有很多优良的特性,最通常见到的也就是可以作为HashMap的key(还可以是线程安全的);同样的LocalTime、LocalDateTime都是不可变类的设计,建议在使用jdk1.8开发的项目中使用这些新的日期、时间API。下面首先来看LocalDate的基本用法:

LocalDate ld = LocalDate.of(2018,10,18) ;
int year = ld.getYear();
Month month = ld.getMonth();
int day = ld.getDayOfMonth();
System.out.println(year+"年"+month.getValue()+"月"+day+"日");
//获取当前的星期数
DayOfWeek dow = ld.getDayOfWeek();
int len=ld.lengthOfMonth();

//获取当天在当年中的天数
int lenyear = ld.lengthOfYear();
boolean leap = ld.isLeapYear();
//从系统时钟中获取当前的日期
LocalDate today = LocalDate.now();
// 根据字符串取:
// 严格按照ISO yyyy-MM-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式
LocalDate endOfFeb = LocalDate.parse("2014-02-28");
// 无效日期无法通过:DateTimeParseException: Invalid date
LocalDate.parse("2014-02-29");
// 取本月最后一天,再也不用计算是28,29,30还是31:
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
//还有很多其他有用的方法可以直接查看 LocalDate的api即可。

 

//ChronoField参数方式,ChronoField是枚举类型,枚举值都可以作为参数
//可以根据实际需要进行选择
int now_year = today.get(ChronoField.YEAR);
int now_month = today.get(ChronoField.MONTH_OF_YEAR) ;
int now_day = today.get(ChronoField.DAY_OF_MONTH);
System.out.println(now_year+"年"+now_month+"月"+now_day+"日");

可以看到LocalDate的使用非常简单,而且对应的构造函数的参数更符合人们的习惯,创建LocalDate一般有三种方式:of方法、parse方法、now方法。根据不同的情况自行选择即可。

 

LocalTime

LocalTime跟LocalDate的功能基本相同,特性完全相同(如 不可变性),只是前者用于表示时间,后者由于表示日期而已。

//参数顺序分别为 时、分、秒
LocalTime t1 = LocalTime.of(9,34,20);
int h = t1.getHour();
int m = t1.getMinute();
int s = t1.getSecond();
LocalTime t2 = LocalTime.now();
//顺序也是时分秒,以冒号间隔,格式必须为xx:xx:xx,不足10必须补0
LocalTime t3 = LocalTime.parse("09:34:20");
System.out.println(t3);

创建LocalTime也是三种方式:of方法、parse方法、now方法。

 

LocalDateTime

LocalDate和LocalTime分别表示日期、时间,LocalDateTime则是二者的合体,创建方式也与二者基本相同。

//of方法有两种参数格式
LocalDateTime dt1 = LocalDateTime.of(2018,10,22,9,10,20);
LocalDateTime dt2 = LocalDateTime.of(today,t2);
LocalDateTime dt3 = LocalDateTime.now();
System.out.println(dt3);
//atTime(知道日期,设置时间)和atDate方法(知道时间,设置日期)
LocalDateTime dt4 = today.atTime(t2);
LocalDateTime dt5 = t2.atDate(today);
//从LocalDateTime中提取Date和time
LocalDate date_x = dt5.toLocalDate();
LocalTime day_x = dt5.toLocalTime();

 

Instant

上述年月日时分秒时方便人类识别时间的方式,对于机器而已就不太合适需要一个转换,这就是Instant的作用:它是以Unix元年(UTC时间1970年1月1日)为基准,当前时间的秒数。

//Unix元年时间 + 2秒
Instant ins1 = Instant.ofEpochSecond(2);
System.out.println(ins1.getEpochSecond());
//Unix元年时间+3秒-12纳秒
Instant ins2 = Instant.ofEpochSecond(3,-12);
System.out.println(ins2);
//当前时间
Instant ins3 = Instant.now();
System.out.println(ins3.getEpochSecond());
//与System.currentTimeMillis()对比
System.out.println(System.currentTimeMillis());

 

Duration和Period

Duration用于计算两个时间之间的间隔,不能用于日期:

LocalTime localTime1 = LocalTime.now();
Thread.sleep(1);
LocalTime localTime2 = LocalTime.now();
//参数可以是LocalTime 也可以是Instant
Duration du1 = Duration.between(localTime1,localTime2);
System.out.println(du1.getNano());

 

要计算日期之间的间隔,要用Period:

//参数为两个LocalDate
Period tenDays = Period.between(LocalDate.of(2014, 3, 8),
LocalDate.of(2014, 3, 18));
System.out.println(tenDays.getDays());

 

Duration类和Period类共享了很多相似的方法:

jdk1.8--新日期和时间处理
            
    
    博客分类: jdk1.8 java8LocalDateLocalTime 

jdk1.8--新日期和时间处理
            
    
    博客分类: jdk1.8 java8LocalDateLocalTime 

 

平时大家都是使用的mysql数据库,mysql数据库中的日期时间字段与新api的对应转换关系如下:

SQL -> Java --------------------------

date -> LocalDate

time -> LocalTime

timestamp -> LocalDateTime

 

操纵日期

修改日期(本质上不是修改,而是创建一个新的LocalDate),首先来看LocalDate的withAttribute用法:

LocalDate ld1 = LocalDate.of(2018,10,23);
LocalDate ld2 = ld1.withYear(2017);
LocalDate ld3 = ld2.withDayOfMonth(25);
LocalDate ld4 = ld3.with(ChronoField.MONTH_OF_YEAR,8);
System.out.println(ld4);

 

日期的加减(plus和minus)

plus和minus方法可以对日期进行增减,注意这里也不是修改,而是重新创建一个新的LocalDate:

LocalDate date1 = today.plusWeeks(1);//加一周
LocalDate date2 = date1.plusYears(1);//加一年
LocalDate date3 = date2.minusMonths(1);//减一月
//自定义加减
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
System.out.println(date4);

 

TemporalAdjuster的使用

使用日期的带TemporalAdjusters工厂方法参数的重载with方法可以很方便的创建需要的日期:

//静态导入方法

import static java.time.temporal.TemporalAdjusters.*;

//下一个周日
LocalDate ld5 = ld4.with(nextOrSame(DayOfWeek.SUNDAY));
System.out.println(ld5);
LocalDate ld6 = ld5.with(lastDayOfMonth());
System.out.println(ld6);
//本月第4周的周日
LocalDate ld7 = ld6.with(dayOfWeekInMonth(4,DayOfWeek.SUNDAY));
System.out.println(ld7);

TemporalAdjusters中提供的其他工厂方法列表(这些方法都返回一个具体的TemporalAdjuster对象):

jdk1.8--新日期和时间处理
            
    
    博客分类: jdk1.8 java8LocalDateLocalTime 

当然如果上述方法都不满足你的要求的化,还可以自己实现TemporalAdjuster接口,然后作为参数传递给LocalDate的with方法。

 

格式化处理日期和时间

文章开头提到过,以前对Date的格式化只能使用DateFormat,但DateFormat是线程不安全的,所以很容易踩坑。

在新的api中,可以使用LocalDate和LocalTime的format方法,结合DateTimeFormatter进行格式化处理:

//使用已定义的DateTimeFormatter格式
//LocalDate转string
String s1 = today.format(DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(s1);
String s2 = today.format(DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(s2);
//String转LocalDate
LocalDate s3 = LocalDate.parse("20181023",DateTimeFormatter.BASIC_ISO_DATE);
LocalDate s4 = LocalDate.parse("2018-08-13",DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(s3);
System.out.println(s4);
//自己指定格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s5 = today.format(formatter);
System.out.println(s5);

//国际化处理

DateTimeFormatter italianFormatter =
DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate dd = LocalDate.of(2014, 3, 18);
String formattedDate = dd.format(italianFormatter);
System.out.println(formattedDate);

//使用DateTimeFormatterBuilder自定义DateTimeFormatter
DateTimeFormatter italianFormatter2 = new DateTimeFormatterBuilder() .appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);

 

时区和历法

新的java.time.ZoneId类是老版java.util.TimeZone的替代品,同样ZoneId是不可变的。

ZoneId romeZone = ZoneId.of("Europe/Rome");
//TimeZone转换为ZoneId
ZoneId zoneId = TimeZone.getDefault().toZoneId();

 

有了ZoneId之后,就可以用于创建ZonedDateTime:

LocalDate ldate = LocalDate.of(2014,Month.APRIL,18);
ZonedDateTime zdate = ldate.atStartOfDay(romeZone);
System.out.println(zdate);
LocalDateTime ldatetime = LocalDateTime.of(2014,10,24,13,13,13);
ZonedDateTime zdate2 = ldatetime.atZone(romeZone);
System.out.println(zdate2);
Instant nowInstant = Instant.now();
ZonedDateTime zdate3 = nowInstant.atZone(romeZone);
System.out.println(zdate3);

 

日本日历系统

LocalDate dated = LocalDate.of(2014, Month.MARCH, 18);
JapaneseDate japaneseDate = JapaneseDate.from(dated);
System.out.println(japaneseDate);
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
ChronoLocalDate now = japaneseChronology.dateNow();
System.out.println(now);

 

*日历

HijrahDate ramadanDate = HijrahDate.now().with(ChronoField.DAY_OF_MONTH, 1)
.with(ChronoField.MONTH_OF_YEAR, 9);
System.out.println("Ramadan starts on " +
IsoChronology.INSTANCE.date(ramadanDate) +
" and ends on " +
IsoChronology.INSTANCE.date(
ramadanDate.with(
TemporalAdjusters.lastDayOfMonth())));