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
 }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容