java8处理日期和时间

自省:java8用了好久了,虽然一直知道有新的时间API,但是一直用java.util.Date用习惯了,也没有特意的去了解java8的时间类java.time,虽然效果同样达到了,但不是当前最适当的方式,以后要吸取教训。

在java8中,java.time包下主要包含下面几个主要的类:

Instant:时间戳,相当于java.util的Date
LocalDate:只包含日期,比如:2016-10-20
LocalTime:只包含时间,比如:23:12:10
LocalDateTime:包含日期和时间,比如:2016-10-20 23:14:21
Duration:计算两个“时间”的间隔
Period:用于计算两个“日期”的间隔
ZoneOffset:时区偏移量,比如:+8:00
ZonedDateTime:可以得到特定时区的日期/时间
Clock:时钟,比如获取目前美国纽约的时间

时间格式化以DateTimeFormatter代替SimpleDateFormat

DateTimeFormatter:时间格式化

方法前缀的含义,统一了api:

of:静态工厂方法(用类名去调用)。
parse:静态工厂方法,关注于解析(用类名去调用)。
now: 静态工厂方法,用当前时间创建实例(用类名去调用)
get:获取某些东西的值。
is:检查某些东西的是否是true。
with:返回一个部分状态改变了的时间日期对象拷贝(单独一个with方法,参数为TemporalAdjusters类型)。
plus:返回一个时间增加了的、时间日期对象拷贝(如果参数是负数也能够有minus方法的效果)。
minus:返回一个时间减少了的、时间日期对象拷贝。
to:把当前时间日期对象转换成另外一个,可能会损失部分状态。
at:把这个对象与另一个对象组合起来,例如: date.atTime(time)。
format :根据某一个DateTimeFormatter格式化为字符串。

通过以上一系列方法,java.time完成了加、减、格式化、解析、从日期/时间中提取单独部分等任务。

java.time包里面的类实例如果用了上面的方法而被修改了,那么会返回一个新的实例过来,而不像Calendar那样可以在同一个实例进行不同的修改,体现了不可变。

下面讲解具体的类

Instant :时间戳,相当于java.util的Date

Instant用于表示一个时间戳,它与我们常使用的System.currentTimeMillis()有些类似,不过Instant可以精确到纳秒(Nano-Second),System.currentTimeMillis()方法只精确到毫秒(Milli-Second)。如果查看Instant源码,发现它的内部使用了两个常量,seconds表示从1970-01-01 00:00:00开始到现在的秒数,nanos表示纳秒部分(nanos的值不会超过999,999,999)。Instant除了使用now()方法创建外,还可以通过ofEpochSecond方法创建:

Instant instant = Instant.ofEpochSecond(120, 100000);

ofEpochSecond()方法的第一个参数为秒,第二个参数为纳秒,上面的代码表示从1970-01-01 00:00:00开始后两分钟的10万纳秒的时刻,控制台上的输出为:

1970-01-01T00:02:00.000100Z

Duration : 计算两个“时间”的间隔

这个很好理解,看下面的了栗子就懂了

LocalDateTime from = LocalDateTime.of(2019, Month.JANUARY, 21, 15, 56, 0);    // 2019-01-21 15:56:00
LocalDateTime to = LocalDateTime.of(2019, Month.FEBRUARY, 21, 15, 56, 0);     // 2019-02-21 15:56:00
Duration duration = Duration.between(from, to);     // 表示从 2019-01-21 15:56:00 到 2019-02-21 15:56:00

long days = duration.toDays();              // 这段时间的总天数
long hours = duration.toHours();            // 这段时间的小时数
long minutes = duration.toMinutes();        // 这段时间的分钟数
long seconds = duration.getSeconds();       // 这段时间的秒数
long milliSeconds = duration.toMillis();    // 这段时间的毫秒数
long nanoSeconds = duration.toNanos();      // 这段时间的纳秒数

Duration对象还可以通过of()方法创建,该方法接受一个时间段长度,和一个时间单位作为参数

Duration duration1 = Duration.of(5, ChronoUnit.DAYS);       // 5天
Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS);  // 1000毫秒

Period : 用于计算两个“日期”的间隔

Period在概念上和Duration类似,区别在于Period是以年月日来衡量一个时间段,比如2年3个月6天

Period period = Period.of(2, 3, 6);

由于Period是以年月日衡量时间段,所以between()方法只能接收LocalDate类型的参数:

// 2019-01-21 到 2019-02-21 这段时间
Period period = Period.between(
                LocalDate.of(2019, 1, 21),
                LocalDate.of(2019, 2, 21));

ZoneId : 时区

获取所有合法的“区域/城市”字符串 :

Set<String> zoneIds = ZoneId.getAvailableZoneIds();

获取系统默认时区 :

ZoneId systemZoneId = ZoneId.systemDefault();

创建时区 :

ZoneId shanghaiZoneId = ZoneId.of("Africa/Bangui");

LocalDateTime:包含日期和时间,比如:2016-10-20 23:14:21

获取当前时间 :

LocalDateTime localDateTime = LocalDateTime.now();//2019-01-21T16:15:52.863

创建特定日期 (多种自定义):

LocalDateTime localDateTime = LocalDateTime.of(2019,01,21,16,22,34);

获取获取年、月、日信息 :

LocalDateTime.now().getYear();//2019
LocalDateTime.now().getMonth();//JANUARY
LocalDateTime.now().getDayOfYear();//21
LocalDateTime.now().getDayOfMonth();//21
LocalDateTime.now().getDayOfWeek();//MONDAY
LocalDateTime.now().getHour();//16

能够自定义时间 :

LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
System.out.println(time); //2017-01-01T01:01:01

//使用plus方法增加年份
//改变时间后会返回一个新的实例nextYearTime
LocalDateTime nextYearTime = time.plusYears(1); 
System.out.println(nextYearTime); //2018-01-01T01:01:01

//使用minus方法减年份
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
LocalDateTime lastYearTime = time.minusYears(1);
System.out.println(lastYearTime); //2016-01-01T01:01:01

//使用with方法设置月份
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
LocalDateTime changeTime = time.withMonth(12);
System.out.println(changeTime); //2017-12-01T01:01:01

//判断当前年份是否闰年
System.out.println("isLeapYear :" + time.isLeapYear());

//判断当前日期属于星期几
LocalDateTime time = LocalDateTime.now();
DayOfWeek dayOfWeek = time.getDayOfWeek();
System.out.println(dayOfWeek); //WEDNESDAY

LocalDateLocalTimeLocalDateTime类似,不多说


其他使用场景:

判断两个日期是否相等

LocalDate date1 = LocalDate.of(2019, 01, 21);
if(date1.equals(LocalDate.now())){
    System.out.printf("Today %s and date1 %s are same date %n", LocalDate.now(), date1);
}

//输出:Today 2019-01-21 and date1 2019-01-21 are same date

检查像生日这种周期性事件

类似每月账单、结婚纪念日、保险缴费日这些周期性事件。使用MonthDay类。这个类组合了月份和日,去掉 了年,这意味着你可以用它判断每年都会发生事件。

LocalDate dateOfBirth = LocalDate.of(1993, 01, 21);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(LocalDate.now());
if(currentMonthDay.equals(birthday)){
    System.out.println("Many Many happy returns of the day !!");
}else{
    System.out.println("Sorry, today is not your birthday");
}

//输出: Many Many happy returns of the day !!

判断日期是早于还是晚于另一个日期

LocalDate tomorrow = LocalDate.of(2019, 1, 22);
if(tomorrow.isAfter(LocalDate.now())){
    System.out.println("Tomorrow comes after today");//Tomorrow comes after today
}

LocalDate yesterday = LocalDate.now().minus(1, ChronoUnit.DAYS);
if(yesterday.isBefore(LocalDate.now())){
    System.out.println("Yesterday is day before today");//Yesterday is day before today
}

java8 时间类与Date类的相互转化

//Date与Instant的相互转化
Instant instant  = Instant.now();
Date date = Date.from(instant);
Instant instant2 = date.toInstant();
        
//Date转为LocalDateTime
Date date2 = new Date();
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date2.toInstant(), ZoneId.systemDefault());
        
//LocalDateTime转Date
LocalDateTime localDateTime3 = LocalDateTime.now();
Instant instant3 = localDateTime3.atZone(ZoneId.systemDefault()).toInstant();
Date date3 = Date.from(instant);

//LocalDate转Date
//因为LocalDate不包含时间,所以转Date时,会默认转为当天的起始时间,00:00:00
LocalDate localDate4 = LocalDate.now();
Instant instant4 = localDate4.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
Date date4 = Date.from(instant);

日期的格式化和解析DateTimeFormatter

Java8DateTimeFormatter也是线程安全的,而SimpleDateFormat并不是线程安全。

*DateTimeFormatterSimpleDateFormat对比 *

  1. Date转String

    //使用Date和SimpleDateFormat
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("G yyyy年MM月dd号 E a hh时mm分ss秒");
    String format = simpleDateFormat.format(new Date());
    System.out.println(format); 
    //打印: 公元 2017年03月21号 星期二 下午 06时38分20秒
    
   //使用jdk1.8 LocalDateTime和DateTimeFormatter
   LocalDateTime now = LocalDateTime.now();
   DateTimeFormatter pattern = DateTimeFormatter.ofPattern("G yyyy年MM月dd号 E a hh时mm分ss秒");
   String format = now.format(pattern);
   System.out.println(format);
   //打印: 公元 2017年03月21号 星期二 下午 06时38分20秒
  1. String转Date

    //使用Date和SimpleDateFormat
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date = simpleDateFormat.parse("2017-12-03 10:15:30");
    System.out.println(simpleDateFormat.format(date));
    //打印 2017-12-03 10:15:30
    
    //使用jdk1.8 LocalDateTime和DateTimeFormatter
    DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    //严格按照ISO yyyy-MM-dd验证,03写成3都不行
    LocalDateTime dt = LocalDateTime.parse("2017-12-03 10:15:30",pattern); 
    System.out.println(dt.format(pattern));
    

使用SimpleDateFormat的正确姿势

方法一

在需要执行格式化的地方都新建SimpleDateFormat实例,使用局部变量来存放SimpleDateFormat实例

public static  String formatDate(Date date)throws ParseException{
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  return sdf.format(date);
}

这种方法可能会导致短期内创建大量的SimpleDateFormat实例,如解析一个excel表格里的字符串日期。

方法二

为了避免创建大量的SimpleDateFormat实例,往往会考虑把SimpleDateFormat实例设为静态成员变量,共享SimpleDateFormat对象。这种情况下就得对SimpleDateFormat添加同步。

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static String formatDate(Date date)throws ParseException{
  synchronized(sdf){
    return sdf.format(date);
  }  
}

这种方法的缺点也很明显,就是在高并发的环境下会导致解析被阻塞。

方法三(推荐

要在高并发环境下能有比较好的体验,可以使用ThreadLocal来限制SimpleDateFormat只能在线程内共享,这样就避免了多线程导致的线程安全问题。

 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

public static String format(Date date) {
    return threadLocal.get().format(date);
}

//打印
 public static void main(String[] args) {
        System.out.println(format(new Date()));//2019-01-21
 }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值