jdk1.8--新日期和时间处理
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:
具体用法:
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类共享了很多相似的方法:
平时大家都是使用的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对象):
当然如果上述方法都不满足你的要求的化,还可以自己实现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())));
上一篇: 稳定实用国外服务器需要注意七大要点
下一篇: Java的类加载器ClassLoader