Java中日期和时间的应用

原文地址

今天在写项目时用到了Java中关于时间和日期方面的东西,就顺便记录下这方面的几种用法。

如何取得年月日、小时分钟秒?

import java.time.LocalDateTime;
import java.util.Calendar;

public class Test04 {

    public static void main(String[] args) {

        Calendar cal = Calendar.getInstance(); 
        System.out.println(cal.get(Calendar.YEAR)); 
        System.out.println(cal.get(Calendar.MONTH)); // 0 - 11 
        System.out.println(cal.get(Calendar.DATE)); 
        System.out.println(cal.get(Calendar.HOUR_OF_DAY)); 
        System.out.println(cal.get(Calendar.MINUTE)); 
        System.out.println(cal.get(Calendar.SECOND));

        // Java 8
        LocalDateTime dt = LocalDateTime.now(); 
        System.out.println(dt.getYear());  
        System.out.println(dt.getMonthValue()); // 1 - 12 
        System.out.println(dt.getDayOfMonth()); 
        System.out.println(dt.getHour()); 
        System.out.println(dt.getMinute()); 
        System.out.println(dt.getSecond());
    }
}
Output:
2018
5
19
19
34
57
2018
6
19
19
34
57

如何取得从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?

import java.time.Clock;
import java.util.Calendar;

public class Test04 {

    public static void main(String[] args) {

         System.out.println(Calendar.getInstance().getTimeInMillis());  //第一种方式 
         System.out.println(System.currentTimeMillis());  //第二种方式 

         // Java 8 
         System.out.println(Clock.systemDefaultZone().millis());  
    }
}
Output:
1529408325410
1529408325424
1529408325465

如何取得某月的最后一天?

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class Test04 {

    public static void main(String[] args) {

        //Java 8  
        LocalDate today = LocalDate.now();  
        //本月的第一天  
        LocalDate firstday = LocalDate.of(today.getYear(),today.getMonth(),1);  
        //本月的最后一天  
        LocalDate lastDay =today.with(TemporalAdjusters.lastDayOfMonth());  
        System.out.println("本月的第一天"+firstday);  
        System.out.println("本月的最后一天"+lastDay); 
    }
}
Output:
本月的第一天2018-06-01
本月的最后一天2018-06-30

如何格式化日期?

  • Java.text.DataFormat 的子类(如 SimpleDateFormat 类)中的 format(Date)方法可将日期格式化。
  • Java 8 中可以用 java.time.format.DateTimeFormatter 来格式化时间日期,代码如下所示
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class Test04 {

    public static void main(String[] args) {

         SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd"); 
         Date date1 = new Date(); 
         System.out.println(oldFormatter.format(date1)); 

         // Java 8 
         DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); 
         LocalDate date2 = LocalDate.now(); 
         System.out.println(date2.format(newFormatter)); 
    }
}
Output:
2018/06/19
2018/06/19

PS: Java的时间日期API一直以来都是被诟病的东西,为了解决这一问题,Java 8中引入了新的时间日期API,其中包括 LocalDate、LocalTime、LocalDateTime、Clock、Instant 等类,这些的类的设计都使用了不变模式,因此是线程安全的设计。

打印昨天的当前时刻

import java.time.LocalDateTime;
import java.util.Calendar;

public class Test04 {

    public static void main(String[] args) {

        Calendar cal = Calendar.getInstance(); 
        cal.add(Calendar.DATE, -1); 
        System.out.println(cal.getTime());

        // Java 8
         LocalDateTime today = LocalDateTime.now(); 
         LocalDateTime yesterday = today.minusDays(1); 
         System.out.println(yesterday); 
    }
}
Output:
Mon Jun 18 19:50:05 CST 2018
2018-06-18T19:50:05.433

Java8的日期特性?

Java 8日期/时间特性

Java 8日期/时间API是JSR-310 的实现,它的实现目标是克服旧的日期时间实现中所有的缺陷,新的日期/时间
API的一些设计原则是:

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

Java 8日期/时间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包:这个包包含支持不同时区以及相关规则的类。

Java 8日期/时间常用API

  1. java.time.LocalDate
    LocalDate是一个不可变的类,它表示默认格式(yyyy-MM-dd)的日期,我们可以使用now()方法得到当前时间,
    也可以提供输入年份、月份和日期的输入参数来创建一个 LocalDate 实例。该类为now()方法提供了重载方法,我们
    可以传入ZoneId来获得指定时区的日期。该类提供与java.sql.Date相同的功能,对于如何使用该类,我们来看一个简单的例子
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;

public class Test04 {

    public static void main(String[] args) {

        //Current Date         
        LocalDate today = LocalDate.now();         
        System.out.println("Current Date="+today);           

        //Creating LocalDate by providing input arguments         
        LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);         
        System.out.println("Specific Date="+firstDay_2014);           

        //Try creating date by providing invalid inputs         
        //LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);         
        //Exception in thread "main" java.time.DateTimeException:          
        //Invalid date 'February 29' as '2014' is not a leap year           

        //Current date in "Asia/Kolkata", you can get it from ZoneId javadoc         
        LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));         
        System.out.println("Current Date in IST="+todayKolkata);           

        //java.time.zone.ZoneRulesException: Unknown time-zone ID: IST         
        //LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));           

        //Getting date from the base date i.e 01/01/1970         
        LocalDate dateFromBase = LocalDate.ofEpochDay(365); 
        System.out.println("365th day from base date= "+dateFromBase);           
        LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);         
        System.out.println("100th day of 2014="+hundredDay2014);
    }
}
Output:
Current Date=2018-06-19
Specific Date=2014-01-01
Current Date in IST=2018-06-19
365th day from base date= 1971-01-01
100th day of 2014=2014-04-10
  1. java.time.LocalTime
    LocalTime 是一个不可变的类,它的实例代表一个符合人类可读格式的时间,默认格式是 hh:mm:ss.zzz。像 LocalDate一样,该类也提供了时区支持,同时也可以传入小时、分钟和秒等输入参数创建实例,我们来看一个简单的 程序,演示该类的使用方法
import java.time.LocalTime;
import java.time.ZoneId;

public class Test04 {

    public static void main(String[] args) {

        //Current Time         
        LocalTime time = LocalTime.now();         
        System.out.println("Current Time="+time);           

        //Creating LocalTime by providing input arguments         
        LocalTime specificTime = LocalTime.of(12,20,25,40);         
        System.out.println("Specific Time of Day="+specificTime);           

        //Try creating time by providing invalid inputs         
        //LocalTime invalidTime = LocalTime.of(25,20);         
        //Exception in thread "main" java.time.DateTimeException:          
        //Invalid value for HourOfDay (valid values 0 - 23): 25   

        //Current date in "Asia/Kolkata", you can get it from ZoneId javadoc         
        LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));         
        System.out.println("Current Time in IST="+timeKolkata);           

        //java.time.zone.ZoneRulesException: Unknown time-zone ID: IST         
        //LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));           

        //Getting date from the base date i.e 01/01/1970 
         LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);         
         System.out.println("10000th second time= "+specificSecondTime); 
    }
}
Output:
Current Time=19:59:10.640
Specific Time of Day=12:20:25.000000040
Current Time in IST=17:29:10.640
10000th second time= 02:46:40
  1. java.time.LocalDateTime
    LocalDateTime 是一个不可变的日期-时间对象,它表示一组日期-时间,默认格式是 yyyy-MM-dd-HH-mm
    ss.zzz。它提供了一个工厂方法,接收LocalDate和LocalTime输入参数,创建LocalDateTime实例。我们来看一个简单的例子
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneOffset;

public class Test04 {

    public static void main(String[] args) {

        //Current Date         
        LocalDateTime today = LocalDateTime.now(); 
        System.out.println("Current DateTime="+today);           

        //Current Date using LocalDate and LocalTime        
        today = LocalDateTime.of(LocalDate.now(), LocalTime.now());         
        System.out.println("Current DateTime="+today);           

        //Creating LocalDateTime by providing input arguments         
        LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30); 
        System.out.println("Specific Date="+specificDate);           

        //Try creating date by providing invalid inputs         
        //LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);         //Exception in thread "main" java.time.DateTimeException:          //Invalid value for HourOfDay (valid values 0 - 23): 25           //Current date in "Asia/Kolkata", you can get it from ZoneId javadoc         LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));         System.out.println("Current Date in IST="+todayKolkata);           //java.time.zone.ZoneRulesException: Unknown time-zone ID: IST 
        //LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));           

        //Getting date from the base date i.e 01/01/1970         
        LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);         System.out.println("10000th second time from 01/01/1970= "+dateFromBase); 
    }
}
Output:
Current DateTime=2018-06-19T20:01:18.850
Current DateTime=2018-06-19T20:01:18.850
Specific Date=2014-01-01T10:10:30
10000th second time from 01/01/1970= 1970-01-01T02:46:40

在所有这三个例子中,我们已经看到如果我们提供了无效的参数去创建日期/时间,那么系统会抛出
java.time.DateTimeException,这是一种运行时异常,我们并不需要显式地捕获它。

同时我们也看到,能够通过传入ZoneId得到日期/时间数据,你可以从它的Javadoc中得到支持的Zoneid的列
表,当运行以上类时,可以得到以上输出

  1. java.time.Instant
    Instant类是用在机器可读的时间格式上的,它以Unix时间戳的形式存储日期时间,我们来看一个简单的程序
import java.time.Duration;
import java.time.Instant;

public class Test04 {

    public static void main(String[] args) {

        //Current timestamp         
        Instant timestamp = Instant.now();         
        System.out.println("Current Timestamp = "+timestamp);           

        //Instant from timestamp         
        Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());         
        System.out.println("Specific Time = "+specificTime);           

        //Duration example 
        Duration thirtyDay = Duration.ofDays(30);         
        System.out.println(thirtyDay);
    }
}
Output:
Current Timestamp = 2018-06-19T12:04:25.946Z
Specific Time = 2018-06-19T12:04:25.946Z
PT720H
  1. 日期 API 工具
    我们早些时候提到过,大多数日期/时间API类都实现了一系列工具方法,如:加/减天数、周数、月份数,等等。还有其他的工具方法能够使用TemporalAdjuster调整日期,并计算两个日期间的周期。
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class Test04 {

    public static void main(String[] args) {

         LocalDate today = LocalDate.now();           

         //Get the Year, check if it's leap year         
         System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear()); 

         //Compare two LocalDate for before and after         
         System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));

         //Create LocalDateTime from LocalDate         
         System.out.println("Current Time="+today.atTime(LocalTime.now()));           

         //plus and minus operations         
         System.out.println("10 days after today will be "+today.plusDays(10));         
         System.out.println("3 weeks after today will be "+today.plusWeeks(3));         
         System.out.println("20 months after today will be "+today.plusMonths(20));           
         System.out.println("10 days before today will be "+today.minusDays(10)); 
         System.out.println("3 weeks before today will be "+today.minusWeeks(3));         
         System.out.println("20 months before today will be "+today.minusMonths(20));           

         //Temporal adjusters for adjusting the dates 
         System.out.println("First date of this month= "+today. with(TemporalAdjusters.firstDayOfMonth()));         
         LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());         
         System.out.println("Last date of this year= "+lastDayOfYear);           
         Period period = today.until(lastDayOfYear);         
         System.out.println("Period Format= "+period);         
         System.out.println("Months remaining in the year= "+period.getMonths()); 
    }
}
Output:
Year 2018 is Leap Year? false
Today is before 01/01/2015? false
Current Time=2018-06-19T20:07:09.031
10 days after today will be 2018-06-29
3 weeks after today will be 2018-07-10
20 months after today will be 2020-02-19
10 days before today will be 2018-06-09
3 weeks before today will be 2018-05-29
20 months before today will be 2016-10-19
First date of this month= 2018-06-01
Last date of this year= 2018-12-31
Period Format= P6M12D
Months remaining in the year= 6
  1. 解析和格式化
    将一个日期格式转换为不同的格式,之后再解析一个字符串,得到日期时间对象,这些都是很常见的。我们来看一下简单的例子。
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Test04 {

    public static void main(String[] args) {

         //Format examples         
        LocalDate date = LocalDate.now();         

        //default format         
        System.out.println("Default format of LocalDate="+date);         

        //specific format         
        System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));         
        System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));           
        LocalDateTime dateTime = LocalDateTime.now();         

        //default format         
        System.out.println("Default format of LocalDateTime="+dateTime);         

        //specific format     
        System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
        System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));           
        Instant timestamp = Instant.now();         

        //default format         
        System.out.println("Default format of Instant="+timestamp);   
    }
}
Output:
Default format of LocalDate=2018-06-19
19::六月::2018
20180619
Default format of LocalDateTime=2018-06-19T20:12:55.592
19::六月::2018 20::12::55
20180619
Default format of Instant=2018-06-19T12:12:55.592Z

Java8之前的日期和时间的缺点

最开始的时候,Date 既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂
后来从 JDK 1.1 开始,这三项职责分开了:
1)使用 Calendar 类实现日期和时间字段之间转换;
2)使用 DateFormat 类来格式化和分析日期字符串;
3)而 Date 只用来承载日期和时间信息。
原有 Date 中的相应方法已废弃。不过,无论是 Date,还是 Calendar,都用着太不方便了,这是 API 没有设
计好的地方。

难用的year 和 month
我们看下面的代码:

Date date = new Date(2012,1,1); 2. System.out.println(date); 

输出 Thu Feb 01 00:00:00 CST 3912

观察输出结果,year 是 2012+1900,而 month,月份参数我不是给了 1 吗?怎么输出二月(Feb)了?
应该曾有人告诉你,如果你要设置日期,应该使用 java.util.Calendar,像这样…

Calendar calendar = Calendar.getInstance();
calendar.set(2013, 8, 2); 

这样写又不对了,calendar 的 month 也是从 0 开始的,表达 8 月份应该用 7 这个数字,要么就干脆用枚举

calendar.set(2013, Calendar.AUGUST, 2); 

注意上面的代码,Calendar 年份的传值不需要减去 1900(当然月份的定义和 Date 还是一样),这种不一致真是让人抓狂!有些人可能知道,Calendar 相关的 API 是 IBM 捐出去的,所以才导致不一致。

java.util.Date 与 java.util.Calendar 中的所有属性都是可变的

下面的代码,计算两个日期之间的天数….

import java.util.Calendar;

public class Test04 {

    public static void main(String[] args) {

        Calendar birth = Calendar.getInstance(); 
        birth.set(1975, Calendar.MAY, 26); 
        Calendar now = Calendar.getInstance(); 
        System.out.println(daysBetween(birth, now)); 
        System.out.println(daysBetween(birth, now)); // 显示 0? 
    }

    public static long daysBetween(Calendar begin, Calendar end) { 
        long daysBetween = 0; 
        while(begin.before(end)) { 
            begin.add(Calendar.DAY_OF_MONTH, 1); 
            daysBetween++; 
        } 
        return daysBetween;  
    }
}
Output:
15731
0

daysBetween 有点问题,如果连续计算两个 Date 实例的话,第二次会取得 0,因为 Calendar 状态是可变的,考虑到重复计算的场合,最好复制一个新的 Calendar

    public static long daysBetween(Calendar begin, Calendar end) { 
        Calendar calendar = (Calendar) begin.clone(); // 复制 
        long daysBetween = 0; 
        while(calendar.before(end)) { 
            calendar.add(Calendar.DAY_OF_MONTH, 1); 
            daysBetween++; 
        } 
        return daysBetween; 
    }

以上种种,导致目前有些第三方的 java 日期库诞生,比如广泛使用的 JODA-TIME,还有 Date4j 等,虽然第三方库已经足3 / 8够强大,好用,但还是有兼容问题的,比如标准的 JSF 日期转换器与 joda-time API 就不兼容,你需要编写自己的转换器,所以标准的 API 还是必须的,于是就有了 JSR310

JSR310介绍

JSR 310 实际上有两个日期概念。

第一个是 Instant,它大致对应于 java.util.Date类,因为它代表了一个确定的时间点,即相对于标准 Java 纪元(1970 年 1 月 1日)的偏移量;但与 java.util.Date 类不同的是其精确到了纳秒级别。

第二个对应于人类自身的观念,比如 LocalDate 和 LocalTime。他们代表了一般的时区概念,要么是日期(不
包含时间),要么是时间(不包含日期),类似于 java.sql 的表示方式。此外,还有一个MonthDay,它可以存储某人的生日(不包含年份)。每个类都在内部存储正确的数据而不是像 java.util.Date 那样利用午夜 12 点来区分日期,利用 1970-01-01 来表示时间。

目前 Java8 已经实现了 JSR310 的全部内容。新增了 java.time 包定义的类表示了日期-时间概念的规则,包
括 instants,durations, dates, times, time-zones and periods。这些都是基于 ISO日历系统,它又是遵循 Gregorian 规则的。最重要的一点是值不可变,且线程安全.

JSR310规范Joda-Time的区别

其实 JSR310 的规范领导者 Stephen Colebourne,同时也是 Joda-Time 的创建者,JSR310 是在 Joda
Time 的基础上建立的,参考了绝大部分的 API,但并不是说JSR310=JODA-Time,下面几个比较明显的区别是:
1. 最明显的变化就是包名(从 org.joda.time 以及 java.time)
2. JSR310 不接受 NULL 值,Joda-Time 视 NULL 值为 0
3. JSR310 的计算机相关的时间(Instant)和与人类相关的时间(DateTime)之间的差别变得更明显
4. JSR310 所有抛出的异常都是 DateTimeException 的子类。虽然 DateTimeException 是一个
RuntimeException

总结

Java.time

流畅的API
实例不可变
线程安全

Java.util.Calendar以及Date

不流畅的API
实例可变
非线程安全

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值