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

java8 时间使用

程序员文章站 2022-12-05 14:29:41
为什么需要新的时间API 文章来源:https://www.cnblogs.com/guozp/p/10342775.html 在Java 8之前的日期/时间API之前,现有的与日期和时间相关的类存在诸多问题,其中主要有: 1. Java的日期/时间类的定义并不一致,在java.util和java. ......

为什么需要新的时间api

文章来源:
在java 8之前的日期/时间api之前,现有的与日期和时间相关的类存在诸多问题,其中主要有:

  1. java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义
  2. java.util.date同时包含日期和时间,而java.sql.date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  3. 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.dateformat抽象类,但通常情况下,simpledateformat类被用于此类需求。
  4. 所有的日期类都是可变的,因此他们都不是线程安全的,这是java日期类最大的问题之一。
  5. 日期类并不提供国际化,没有时区支持,因此java引入了java.util.calendar和java.util.timezone类,但他们同样存在上述所有的问题。
  6. 在现有的日期和日历类中定义的方法还存在一些其他的问题,但以上问题已经很清晰地表明:java需要一个健壮的日期/时间类。这也是为什么joda time在java日期/时间需求中扮演了高质量替换的重要角色。
  7. 使用java8之前的日期注意
   使用calendar类实现日期和时间字段之间转换
   使用dateformat类来格式化和分析日期字符串
   而date只用来承载日期和时间信息

java8 概念

瞬时时间(instant),持续时间(duration),日期(date),时间(time),时区(time-zone)以及时间段(period)。java 8仍然延用了iso的日历体系,并且与它的前辈们不同,java.time包中的类是不可变且线程安全的。新的时间及日期api位于java.time包中,下面是里面的一些关键的类:

  • instant——它代表的是时间戳(因为它代表了一个确定的时间点,即相对于1970年1月1日的偏移量;但与java.util.date类不同的是其精确到了纳秒级别。
  • duration:持续时间,时间差
  • localdate——不包含具体时间的日期,比如2019-01-14。它可以用来存储生日,周年纪念日,入职日期等。
  • localtime——它代表的是不含日期的时间
  • localdatetime——它包含了日期及时间,不过还是没有偏移信息或者说时区。
  • period:时间段
  • zoneoffset:时区偏移量,比如:+8:00
  • zoneddatetime——这是一个包含时区的完整的日期时间,偏移量是以utc/格林威治时间为基准的。
  • clock:时钟,比如获取目前美国纽约的时间

java8时间api特性

  • 不变性:新的日期/时间api中,所有的类都是不可变的,这对多线程很有好处。
  • 关注点分离:借鉴了joda库的一些优点,新的api将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(date)、时间(time)、日期时间(datetime)、时间戳(unix timestamp)以及时区定义了不同的类。
  • 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。例如要拿到当前实例我们可以使用now()方法,在所有的类中都定义了format()和parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
  • 实用操作:所有新的日期/时间api类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。
  • 可扩展性:新的日期/时间api是工作在iso-8601日历系统上的,但我们也可以将其应用在非ios的日历上。

java8日期/时间api包

  • java.time包:这是新的java日期/时间api的基础包,所有的主要基础类都是这个包的一部分,如:localdate, localtime, localdatetime, instant, period, duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
  • java.time.chrono包:这个包为非iso的日历系统定义了一些泛化的api,我们可以扩展abstractchronology类来创建自己的日历系统。
  • java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。
  • java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withxxx”的格式。
  • java.time.zone包:这个包包含支持不同时区以及相关规则的类。

java8 api介绍和使用

localdate

localdate 依然是一个不可变类,它关注时间中年月日部分

初始化实例
public static localdate now():截断当前系统时间的年月日信息并初始化一个实例对象
public static localdate of(int year, int month, int dayofmonth):显式指定年月日信息
public static localdate ofyearday(int year, int dayofyear):根据 dayofyear 可以推出 month 和 dayofmonth
public static localdate ofepochday(long epochday):相对于格林零时区时间的日偏移量
…………

示例

// 取当前日期:
localdate today = localdate.now(); // -> 2019-01-31
// 根据年月日取日期,12月就是12:
localdate crischristmas = localdate.of(2018, 12, 25); // -> 2018-12-25
// 根据字符串取:
localdate endoffeb = localdate.parse("2018-12-25"); // 严格按照iso yyyy-mm-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式

// 如何获取1周后的日期
localdate onetoday = today.plus(1, chronounit.weeks); // ->2019-02-07
//一年前的日期
localdate previousyear = today.minus(1, chronounit.years);
// 取本月第1天:
localdate firstdayofthismonth = today.with(temporaladjusters.firstdayofmonth()); // 2019-01-01
// 取本月第2天:
localdate seconddayofthismonth = today.withdayofmonth(2); // 2019-01-02
// 取本月最后一天,再也不用计算是28,29,30还是31:
localdate lastdayofthismonth = today.with(temporaladjusters.lastdayofmonth()); // 2019-01-31
// 取下一天:
localdate firstday = lastdayofthismonth.plusdays(1); // 变成了2019-02-01
// 取2019年1月第一个周一
localdate firstmonday = localdate.parse("2019-01-01").with(temporaladjusters.firstinmonth(dayofweek.monday)); // 2019-01-07

在java8中,可以使用monthday,该类不包含年份信息,当然还有一个类是yearmonth

localdate birthday = localdate.of(1990, 10, 12);
monthday birthdaymd = monthday.of(birthday.getmonth(), birthday.getdayofmonth());
monthday today = monthday.from(localdate.of(2019, 10, 12)); 
system.out.println(today.equals(birthdaymd));
//结果
true
temporaladjuster

但是有些时候我们要面临更复杂的时间操作,比如将时间调到下一个工作日,或者是下个月的最后一天,这时候我们可以使用with()方法的另一个重载方法,它接收一个temporaladjuster参数,可以使我们更加灵活的调整日期:

localdate date7 = date.with(nextorsame(dayofweek.sunday));      // 返回下一个距离当前时间最近的星期日
localdate date9 = date.with(lastinmonth(dayofweek.saturday));   // 返回本月最后一个星期六

如果本身api不满足你的需求,你还可以创建自定义的temporaladjuster接口的实现

localtime

类似于 localdate,localtime 专注于时间的处理,它提供小时,分钟,秒,毫微秒的各种处理

初始化localtime实例
public static localtime now():根据系统当前时刻获取其中的时间部分内容
public static localtime of(int hour, int minute):显式传入小时和分钟来构建一个实例对象
public static localtime of(int hour, int minute, int second):通过传入时分秒构造实例
public static localtime of(int hour, int minute, int second, int nanoofsecond):传入时分秒和毫微秒构建一个实例
public static localtime ofsecondofday(long secondofday):传入一个长整型数值代表当前日已经过去的秒数
public static localtime ofnanoofday(long nanoofday):传入一个长整型代表当前日已经过去的毫微秒数

示例

//包含毫秒
localtime now = localtime.now(); // 11:09:09.240
//不包含毫秒  
localtime now = localtime.now().withnano(0)); // 11:09:09
//构造时间  
localtime zero = localtime.of(0, 0, 0); // 00:00:00
localtime mid = localtime.parse("12:00:00"); // 12:00:00
localtime twohour = now.plushours(2);

localdatetime

localdatetime类是localdate和localtime的结合体,可以通过of()方法直接创建,也可以调用localdate的attime()方法或localtime的atdate()方法将localdate或localtime合并成一个localdatetime

localdatetime ldt1 = localdatetime.of(2017, month.january, 4, 17, 23, 52);

localdate localdate = localdate.of(2017, month.january, 4);
localtime localtime = localtime.of(17, 23, 52);
localdatetime ldt2 = localdate.attime(localtime);

localdatetime也提供用于向localdate和localtime的转化:

localdate date = ldt1.tolocaldate();
localtime time = ldt1.tolocaltime();

instant

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);

时间差

关于时间差的计算,主要涉及到两个类,年月日的日期间差值的计算使用 period 类足以,而时分秒毫秒的时间的差值计算则需要使用duration类。

  • duration:处理两个时间之间的差值
    • duration的内部实现与instant类似,也是包含两部分:seconds表示秒,nanos表示纳秒。两者的区别是instant用于表示一个时间戳(或者说是一个时间点),而duration表示一个时间段,所以duration类中不包含now()静态方法。可以通过duration.between()方法创建
        localdatetime from = localdatetime.of(2019, month.january, 5, 10, 7, 0);    // 2019-01-05 10:07:00
    localdatetime to = localdatetime.of(2019, month.february, 5, 10, 7, 0);     // 2019-02-05 10:07:00
    duration duration = duration.between(from, to);     // 表示从 2019-01-05 10:07:00 到 2019-02-05 10:07: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()方法创建,值得注意的是,由于period是以年月日衡量时间段,所以between()方法只能接收localdate类型的参数:

    period period = period.between(
                localdate.of(2019, 1, 5),
                localdate.of(2019, 2, 5));
  • 示例

 localdate date = localdate.of(2019,01,22);
    localdate date1 = localdate.now();
    period period = period.between(date,date1);
    system.out.println(period.getyears() + "年" +
            period.getmonths() + "月" +
            period.getdays() + "天");

    localtime time = localtime.of(20,30);
    localtime time1 = localtime.of(23,59);
    duration duration = duration.between(time,time1);
    system.out.println(duration.tominutes() + "分钟");

zoneddatetime

无论是我们的 localdate,或是 localtime,甚至是 localdatetime,它们基本是时区无关的,内部并没有存储时区属性,而基本用的系统默认时区。往往有些场景之下,缺乏一定的灵活性。

zoneddatetime 可以被理解为 localdatetime 的外层封装,它的内部存储了一个 localdatetime 的实例,专门用于普通的日期时间处理。此外,它还定义了 zoneid 和 zoneoffset 来描述时区的概念。

zoneddatetime 和 localdatetime 的一个很大的不同点在于,后者内部并没有存储时区,所以对于系统的依赖性很强,往往换一个时区可能就会导致程序中的日期时间不一致。
而后者则可以通过传入时区的名称,使用 zoneid 进行匹配存储,也可以通过传入与零时区的偏移量,使用 zoneoffset 存储时区信息。

初始化实例
public static zoneddatetime now():系统将以默认时区计算并存储日期时间信息
public static zoneddatetime now(zoneid zone):指定时区
public static zoneddatetime of(localdate date, localtime time, zoneid zone):指定日期时间和时区
public static zoneddatetime of(localdatetime localdatetime, zoneid zone)
public static zoneddatetime ofinstant(instant instant, zoneid zone):通过时刻和时区构建实例对象
等等

示例

 zoneddatetime zoneddatetime = zoneddatetime.now();
    system.out.println(zoneddatetime); 
    //->2019-01-31t16:27:23.179+08:00[asia/shanghai]

    localdatetime localdatetime = localdatetime.now();
    zoneid zoneid = zoneid.of("america/los_angeles");
    zoneddatetime zoneddatetime1 = zoneddatetime.of(localdatetime,zoneid);
    system.out.println(zoneddatetime1);
    // ->2019-01-31t16:27:23.179-08:00[america/los_angeles]


    instant instant = instant.now();
    zoneid zoneid1 = zoneid.of("gmt");
    zoneddatetime zoneddatetime2 = zoneddatetime.ofinstant(instant,zoneid1);
    system.out.println(zoneddatetime2);
    // ->2019-01-31t08:27:23.183z[gmt]

  • 第一个输出应使用了当前系统日期和时间以及默认的时区。
  • 第二个小例子,localdatetime 实例保存了时区无关的当前日期时间信息,也就是这里的年月日时分秒,接着构建一个 zoneddatetime 实例并传入一个美国时区(西七区)。你会发现输出的日期时间为西七区的 16 点 27 分。

    像这种关联了时区的日期时间就很能够解决那种,换时区导致程序中时间错乱的问题。因为我关联了时区,无论你程序换到什么地方运行了,日期+时区 本就已经唯一确定了某个时刻,就相当于我在存储某个时刻的时候,说明了这是某某时区的某某时间,即便你换了一个地区,也不至于把这个时间按自己当前的时区进行解析并直接使用。

  • 第三个小例子,构建 zoneddatetime实例的时候,给定一个时刻和一个时区,而这个时刻值就是相对于给定时区的标准时间所经过的毫秒数。

    有关 zoneddatetime 的其他日期时间的处理方法和 localdatetime 是一样的,因为 zoneddatetime 是直接封装了一个 localdatetime 实例对象,所以所有相关日期时间的操作都会间接的调用 localdatetime 实例的方法,我们不再赘述。

格式化日期时间

java 8 的新式日期时间 api 中,datetimeformatter 作为格式化日期时间的主要类,它与之前的 dateformat 类最大的不同就在于它是线程安全的,如果需要的话,可以赋值给一个静态变量。

datetimeformatter类提供了许多预定义的格式器,你也可以自定义自己想要的格式。当然根据约定,它还有一个parse()方法是用于将字符串转换成日期的,如果转换期间出现任何错误,它会抛出datetimeparseexception异常。类似的,dateformatter类也有一个用于格式化日期的format()方法,它出错的话则会抛出datetimeexception异常

再说一句,“mmm d yyyy”与“mmm dd yyyy”这两个日期格式也略有不同,前者能识别出"jan 2 2018"与"jan 14 2018"这两个串,而后者如果传进来的是"jan 2 2018"则会报错,因为它期望月份处传进来的是两个字符。为了解决这个问题,在天为个位数的情况下,你得在前面补0,比如"jan 2 2018"应该改为"jan 02 2018"。

public static void main(string[] a){
    datetimeformatter formatter = datetimeformatter.ofpattern("yyyy年mm月dd日 hh:mm:ss");
    localdatetime localdatetime = localdatetime.now();
    system.out.println(formatter.format(localdatetime));

    string str = "2008年08月23日 23:59:59";
    datetimeformatter formatter2 = datetimeformatter.ofpattern("yyyy年mm月dd日 hh:mm:ss");
    localdatetime localdatetime2 = localdatetime.parse(str,formatter2);
    system.out.println(localdatetime2);

}

java8 时间与老版本时间转换

因为java8之前date是包含日期和时间的,而localdate只包含日期,localtime只包含时间,所以与date在互转中,势必会丢失日期或者时间,或者会使用起始时间。如果转localdatetime,那么就不存在信息误差。

/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);


// calendar to instant
instant time = calendar.getinstance().toinstant();
system.out.println(time);

// timezone to zoneid
zoneid defaultzone = timezone.getdefault().tozoneid();
system.out.println(defaultzone);
 
// zoneddatetime from specific calendar
zoneddatetime gregoriancalendardatetime = new gregoriancalendar().tozoneddatetime();
system.out.println(gregoriancalendardatetime);
 
 
gregoriancalendar gc = gregoriancalendar.from(gregoriancalendardatetime);
system.out.println(gc);

来源: