• Java 日期、时间类


    Java 原本提供了 Date 和 Calendar 用于处理日期、时间的类,包括创建日期、时间对象,获取系统当前日期、时间等操作。但 Date 不仅无法实现国际化,而且它对不同属性也使用了前后矛盾的偏移量,比如月份与小时都是从0开始的,月份中的天数则是从1开始的,年又是从1900开始的,而 java.util.Calendar 则显得过于复杂,从下面介绍中会看到传统 Java 对日期、时间处理的不足。Java8 吸取了 Joda-Time 库(一个被广泛使用的日期、时间库)的经验,提供了一套全新的日期时间库。

    Date类(过时)

    Java 提供了 Date 类来处理日期、时间(此处的 Date 是指 java.util 包下的 Date 类,而不是 java.sql 包下的 Date 类),Date 对象既包含日期,也包含时间。Date 类从 JDK1.0 起就开始存在了,但正因为它历史悠久,所以它的大部分构造器、方法都己经过时,不再推荐使用了。

    Date 类提供了 6 个构造器,其中 4 个已经 Deprecated(Java 不再推荐使用,使用不再推荐的构造器时编译器会提出警告信息,并导致程序性能、安全性等方面的问题),剩下的两个构造器如下。

    • Date():生成一个代表当前日期时间的 Date 对象。该构造器在底层调用 System.currentTimeMillis() 获得 long 整数作为日期参数。
    • Date(long date):根据指定的 long 型整数来生成一个 Date 对象。该构造器的参数表示创建的 Date 对象和 GMT 1970 年 1 月 1 日 00:00:00 之间的时间差,以毫秒作为计时单位。

    与 Date 构造器相同的是,Date 对象的大部分方法也 Deprecated 了,剩下为数不多的几个方法。

    • boolean after(Date when):测试该日期是否在指定日期 when 之后。
    • boolean before(Date when):测试该日期是否在指定日期 when 之前。
    • long getTime():返回该时间对应的 long 型整数,即从 GMT 1970-01-01 00:00:00 到该 Date 对象之间的时间差,以亳秒作为计时单位。
    • void setTime(long time):设置该 Date 对象的时间。

    下面程序示范了 Date 类的用法。

    public class DateTest {
        public static void main(String[] args) {
            Date d1 = new Date();
            // 获取当前时间之后100ms的时间
            Date d2 = new Date(System.currentTimeMillis() + 100);
            System.out.println(d2);
            System.out.println(d1.compareTo(d2));
            System.out.println(d1.before(d2));
        }
    }

    总体来说,Date 是一个设计相当糟糕的类,因此 Java 官方推荐尽量少用 Date 的构造器和方法。如果需要对日期、时间进行加减运算,或获取指定时间的年、月、日、时、分、秒信息,可使用 Calendar 工具类。

    Calendar 类

    因为 Date 类在设计上存在一些缺陷,所以 Java 提供了 Calendar 类来更好地处理日期和时间。Calendar 是一个抽象类,它用于表示日历。

    历史上有着许多种纪年方法,它们的差异实在太大了,比如说一个人的生日是“七月七日”,那么一种可能是阳(公)历的七月七日,但也可以是阴(农)历的日期。为了统一计时,全世界通常选择最普及、最通用的日历:GregorianCalendar,也就是日常介绍年份时常用的“公元几几年”。Calendar 类本身是一个抽象类,它是所有日历类的模板,并提供了一些所有日历通用的方法:但它本身不能直接实例化,程序只能创建 Calendar 子类的实例,Java 本身提供了一个 GregorianCalendar 类,一个代表格里高利日历的子类,它代表了通常所说的公历。

    当然,也可以创建自己的 Calendar 子类,然后将它作为 Calendar 对象使用(这就是多态)。在 IBM 的 alphaWorks 站点(http://www.alphaworks.ibm.com/tech/calendars)上,IBM 的开发人员实现了多种日历。在 Internet 上,也有对中国农历的实现。读者可以查看上述 Calendar 的源码来学习。

    Calendar 类是一个抽象类,所以不能使用构造器来创建 Calendar 对象。但它提供了几个静态 getInstance() 方法来获取 Calendar 对象,这些方法根据 TimeZone,Locale 类来获取特定的 Calendar,如果不指定 TimeZone、Locale,则使用默认的 TimeZone、Locale 来创建 Calendar。

    Calendar 与 Date 都是表示日期的工具类,它们直接可以自由转换,如下代码所示。

    //创建一个默认的Calendar对象
    Calendar calendar = Calendar.getInstance();
    //从Calendar对象中取出Date对象
    Date date = calendar.getTime();
    //通过Date对象获得对应的calendar对象
    //因为没有构造函数可以接收Date对象
    //所以必须先获得一个calendar实例,然后调用其setTime()方法
    Calendar calendar2 = Calendar.getInstance();
    calendar2.setTime(date);

    Calendar 类提供了大量访问、修改日期时间的方法,常用方法如下。

    • void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
    • int get(int field):返回指定日历字段的值。
    • int getActualMaximum(int field):返回指定日历字段可能拥有的最大值。例如月,最大值为11。
    • int getActualMinimum(int field):返回指定日历字段可能拥有的最小值。例如月,最小值为0。
    • void roll(int field, int amount):与 add() 方法类似,区别在于加上 amount 后超过了该字段所能表示的最大范围时,也不会向上一个字段进位。
    • void set(int field, int value):将给定的日历字段设置为给定值。
    • void set(int year, int month,int day):设置 Calendar 对象的年、月、日三个字段的值。
    • void set(int year, int month,int day, int hourOfDay, int minute, int second):设置 Calendar 对象的年、月、日、时、分、秒6个字段的值。

    上面的很多方法都需要一个 int 类型的 field 参数,field 是 Calendar 类的类变量,如 Calendar.YEAR、Calendar.MONTH 等分别代表了年、月、日、小时、分钟、秒等时间字段。需要指出的是,Calendar.MONTH 字段代表月份,月份的起始值不是1,而是0,所以要设置8月时,用7而不是8。如下程序示范了Calendar 类的常规用法。

    import java.util.*;
    import static java.util.Calendar.*;
    
    public class CalendarTest {
        public static void main(String[] args) {
            Calendar c = Calendar.getInstance();
            // 取出年
            System.out.println(c.get(YEAR));
            // 取出月份
            System.out.println(c.get(MONTH));
            // 取出日
            System.out.println(c.get(DATE));
            // 分别设置年、月、日、小时、分钟、秒
            c.set(2003, 10, 23, 12, 32, 23); // 2003-11-23 12:32:23
            System.out.println(c.getTime());
            // 将Calendar的年前推1年
            c.add(YEAR, -1); // 2002-11-23 12:32:23
            System.out.println(c.getTime());
            // 将Calendar的月前推8个月
            c.roll(MONTH, -8); // 2002-03-23 12:32:23
            System.out.println(c.getTime());
        }
    }

    上面程序中粗体字代码示范了 Calendar 类的用法,Calendar 可以很灵活地改变它对应的日期。

    提示:上面程序使用了静态导入,它导入了 Calendar 类里的所有类变量,所以上面程序可以直接使用 Calendar 类的 YEAR、MONTH、DATE 等类变量。

    Calendar类还有如下几个注意点。

    1. add 与 roll 的区别

    add(int field, int amount) 的功能非常强大,add 主要用于改变 Calendar 的特定字段的值。如果需要增加某字段的值,则让 amount 为正数;如果需要减少某字段的值,则让 amount 为负数即可。

    add(int field, int amount) 有如下两条规则。

    当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大。例如:

    Calendar cal1 = Calendar.getInstance();
    cal1.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
    cal1.add(MONTH, 6); // 2003-8-23 => 2004-2-23
    System.out.println(cal1.getTime());

    如果下一级字段也需要改变,那么该字段会修正到变化最小的值。例如:

    Calendar cal2 = Calendar.getInstance();
    cal2.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
    // 因为进位到后月份改为2月,2月没有31日,自动变成29日
    cal2.add(MONTH, 6); // 2003-8-31 => 2004-2-29
    System.out.println(cal2.getTime());

    对于上面的例子,8-31 就会变成 2-29。因为 MONTH 的下一级字段是 DAY,从 31 到 29 改变最小。所以上面 2003-8-31 的 MONTH 字段增加 6 后,不是变成 2004-3-2,而是变成 2004-2-29。

    roll() 的规则与 add() 的处理规则不同:当被修改的字段超出它允许的范围时,上一级字段不会增大。

    Calendar cal3 = Calendar.getInstance();
    cal3.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
    // MONTH字段“进位”,但YEAR字段并不增加
    cal3.roll(MONTH, 6); // 2003-8-23 => 2003-2-23
    System.out.println(cal3.getTime());

    下一级字段的处理规则与 add() 相似:

    Calendar cal4 = Calendar.getInstance();
    cal4.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
    // MONTH字段“进位”后变成2,2月没有31日,
    // YEAR字段不会改变,2003年2月只有28天
    cal4.roll(MONTH, 6); // 2003-8-31 => 2003-2-28
    System.out.println(cal4.getTime());

    2.设置 Calendar 的容错性

    调用 Calendar 对象的 set() 方法来改变指定时间字段的值时,有可能传入一个不合法的参数,例如为 MONTH 字段设置 13,这将会导致怎样的后果呢?看如下程序。

    public class LenientTest {
        public static void main(String[] args) {
            Calendar cal = Calendar.getInstance();
            // 结果是YEAR字段加1,MONTH字段为1(二月)
            cal.set(MONTH, 13); //
            System.out.println(cal.getTime());
            // 关闭容错性
            cal.setLenient(false);
            // 导致运行时异常
            cal.set(MONTH, 13); //
            System.out.println(cal.getTime());
        }
    }

    上面程序①②两处的代码完全相似,但它们运行的结果不一样:①处代码可以正常运行,因为设置 MONTH 字段的值为13,将会导致 YEAR 字段加1;②处代码将会导致运行时异常,因为设置的 MONTH 字段值超出了 MONTH 字段允许的范围。关键在于程序中粗体字代码行,Calendar 提供了一个 setLenient() 用于设置它的容错性,Calendar 默认支持较好的容错性,通过 setLenient(false) 可以关闭 Calendar 的容错性,让它进行严格的参数检查。

    Calendar 有两种解释日历字段的模式:lenient 模式和 non-lenient 模式。当 Calendar 处于 lenient 模式时,每个时间字段可接受超出它允许范围的值:当 Calendar 处于 non-lenient 模式时,如果为某个时间字段设置的值超出了它允许的取值范围,程序将会抛出异常。

    3.set() 方法延迟修改

    set(f,value) 方法将日历字段 f 更改为 value,此外它还设置了一个内部成员变量,以指示日历字段 f 已经被更改。尽管日历字段 f 是立即更改的,但该 Calendar 所代表的时间却不会立即修改,直到下次调用 get()、getTime()、getTimeInMillis()、add() 或 roll() 时才会重新计算日历的时间:这被称为 set() 方法的延迟修改,采用延迟修改的优势是多次调用 set() 不会触发多次不必要的计算(需要计算出一个代表实际时间的 long 型整数)。

    下面程序演示了 set() 方法延迟修改的效果。

    import java.util.*;
    import static java.util.Calendar.*;
    
    public class LazyTest {
        public static void main(String[] args) {
            Calendar cal = Calendar.getInstance();
            cal.set(2003, 7, 31); // 2003-8-31
            // 将月份设为9,但9月31日不存在。
            // 如果立即修改,系统将会把cal自动调整到10月1日。
            cal.set(MONTH, 8);
            // 下面代码输出10月1日
            //System.out.println(cal.getTime());    //// 设置DATE字段为5
            cal.set(DATE, 5); //
            System.out.println(cal.getTime()); //
        }
    }

    上面程序中创建了代表 2003-8-31 的 Calendar 对象,当把这个对象的 MONTH 字段加 1 后应该得到 2003-10-1(因为9月没有31日),如果程序在①号代码处输出当前 Calendar 里的日期,也会看到输出 2003-10-1,③号代码处将输出2003-10-5。

    如果程序将①处代码注释起来,因为 Calendar 的 set() 方法具有延迟修改的特性,即调用 set() 方法后 Calendar 实际上并未计算真实的日期,它只是使用内部成员变量表记录 MONTH 字段被修改为8,接着程序设置 DATE 字段值为 5,程序内部再次记录 DATE 字段为5一一就是9月5日,因此看到③处输出2003-9-5。

    Java 8 新增的日期、时间包

    Java 8 开始专门新增了一个 java.time 包,该包下包含了如下常用的类。

    • Clock:该类用于获取指定时区的当前日期、时间。该类可取代 System 类的 currentTimeMillis() 方法,而且提供了更多方法来获取当前日期、时间。该类提供了大量静态方法来获取 Clock 对象。
    • Duration:该类代表持续时间。该类可以非常方便地获取一段时间。
    • Instant:代表一个具体的时刻,可以精确到纳秒。该类提供了静态的 now() 方法来获取当前时刻,也提供了静态的 now(Clock ck) 方法来获取 clock 对应的时刻。除此之外,它还提供了一系列 minusXxx() 方法在当前时刻基础上减去一段时间,也提供了 plusXxx() 方法在当前时刻基础上加上一段时间。
    • LocalDate:该类代表不带时区的日期,例如 2007-12-03。该类提供了静态的 now() 方法来获取当前日期,也提供了静态的 now(Clock clock) 方法来获取 clock 对应的日期:除此之外,它还提供了 minusXxx() 方法在当前年份基础上减去几年、几月、几周或几日等,也提供了 plusXxx() 方法在当前年份基础上加上几年、几月、几周或几日等。
    • LocalTime:该类代表不带时区的时间,例如 10:15:30。该类提供了静态的 now() 方法来获取当前时间,也提供了静态的 now(Clock clock) 方法来获取 clock 对应的时间。除此之外,它还提供了 minusXxx() 方法在当前年份基础上减去几小时、几分、几秒等,也提供了 plusXxx() 方法在当前年份基础上加上几小时、几分、几秒等。
    • LocalDateTime:该类代表不带时区的日期、时间,例如2007-12-03T10:15:30。该类提供了静态的 now() 方法来获取当前日期、时间,也提供了静态的 now(Clock ck) 方法来获取 clock 对应的日期、时间。除此之外,它还提供了 minusXxx() 方法在当前年份基础上减去几年、几月、几日、几小时、几分、几秒等,也提供了 plusXxx() 方法在当前年份基础上加上几年、几月、几日、几小时、几分、几秒等。
    • MonthDay:该类仅代表月日,例如--04-12。该类提供了静态的 now() 方法来获取当前月日,也提供了静态的 now(Clock ck) 方法来获取 clock 对应的月日。
    • Year:该类仅代表年,例如2014。该类提供了静态的 now() 方法来获取当前年份,也提供了静态的 now(Clock clock) 方法来获取 clock 对应的年份。除此之外,它还提供了 minusYears() 方法在当前年份基础上减去几年,也提供了 plusYears() 方法在当前年份基础上加上几年。
    • YearMonth:该类仅代表年月,例如2014-04。该类提供了静态的 now() 方法来获取当前年月,也提供了静态的 now(Clock ck) 方法来获取 clock 对应的年月。除此之外,它还提供了 minusXxx() 方法在当前年月基础上减去几年、几月,也提供了 plusXxx() 方法在当前年月基础上加上几年、几月。
    • ZonedDateTime:该类代表一个时区化的日期、时间。
    • ZoneId:该类代表一个时区。
    • DayOfWeek:这是一个枚举类,定义了周日到周六的枚举值。
    • Month:这也是一个枚举类,定义了一月到十二月的枚举值。

    下面通过一个简单的程序来示范这些类的用法。

    import java.time.*;
    
    
    public class NewDatePackageTest {
        public static void main(String[] args) {
            // -----下面是关于Clock的用法-----
            // 获取当前Clock
            Clock clock = Clock.systemUTC();
            // 通过Clock获取当前时刻
            System.out.println("当前时刻为:" + clock.instant());
            // 获取clock对应的毫秒数,与System.currentTimeMillis()输出相同
            System.out.println(clock.millis());
            System.out.println(System.currentTimeMillis());
            // -----下面是关于Duration的用法-----
            Duration d = Duration.ofSeconds(6000);
            System.out.println("6000秒相当于" + d.toMinutes() + "分");
            System.out.println("6000秒相当于" + d.toHours() + "小时");
            System.out.println("6000秒相当于" + d.toDays() + "天");
            // 在clock基础上增加6000秒,返回新的Clock
            Clock clock2 = Clock.offset(clock, d);
            // 可看到clock2与clock1相差1小时40分
            System.out.println("当前时刻加6000秒为:" + clock2.instant());
            // -----下面是关于Instant的用法-----
            // 获取当前时间
            Instant instant = Instant.now();
            System.out.println(instant);
            // instant添加6000秒(即100分钟),返回新的Instant
            Instant instant2 = instant.plusSeconds(6000);
            System.out.println(instant2);
            // 根据字符串中解析Instant对象
            Instant instant3 = Instant.parse("2014-02-23T10:12:35.342Z");
            System.out.println(instant3);
            // 在instant3的基础上添加5小时4分钟
            Instant instant4 = instant3.plus(Duration.ofHours(5).plusMinutes(4));
            System.out.println(instant4);
            // 获取instant4的5天以前的时刻
            Instant instant5 = instant4.minus(Duration.ofDays(5));
            System.out.println(instant5);
            // -----下面是关于LocalDate的用法-----
            LocalDate localDate = LocalDate.now();
            System.out.println(localDate);
            // 获得2014年的第146天
            localDate = LocalDate.ofYearDay(2014, 146);
            System.out.println(localDate); // 2014-05-26
            // 设置为2014年5月21日
            localDate = LocalDate.of(2014, Month.MAY, 21);
            System.out.println(localDate); // 2014-05-21
            // -----下面是关于LocalTime的用法-----
            // 获取当前时间
            LocalTime localTime = LocalTime.now();
            // 设置为22点33分
            localTime = LocalTime.of(22, 33);
            System.out.println(localTime); // 22:33
            // 返回一天中的第5503秒
            localTime = LocalTime.ofSecondOfDay(5503);
            System.out.println(localTime); // 01:31:43
            // -----下面是关于localDateTime的用法-----
            // 获取当前日期、时间
            LocalDateTime localDateTime = LocalDateTime.now();
            // 当前日期、时间加上25小时3分钟
            LocalDateTime future = localDateTime.plusHours(25).plusMinutes(3);
            System.out.println("当前日期、时间的25小时3分之后:" + future);
            // 下面是关于Year、YearMonth、MonthDay的用法示例-----
            Year year = Year.now(); // 获取当前的年份
            System.out.println("当前年份:" + year); // 输出当前年份
            year = year.plusYears(5); // 当前年份再加5年
            System.out.println("当前年份再过5年:" + year);
            // 根据指定月份获取YearMonth
            YearMonth ym = year.atMonth(10);
            System.out.println("year年10月:" + ym); // 输出XXXX-10,XXXX代表当前年份
            // 当前年月再加5年,减3个月
            ym = ym.plusYears(5).minusMonths(3);
            System.out.println("year年10月再加5年、减3个月:" + ym);
            MonthDay md = MonthDay.now();
            System.out.println("当前月日:" + md); // 输出--XX-XX,代表几月几日
            // 设置为5月23日
            MonthDay md2 = md.with(Month.MAY).withDayOfMonth(23);
            System.out.println("5月23日为:" + md2); // 输出--05-23
        }
    }

    该程序就是这些常见类的用法示例,这些 API 和它们的方法都非常简单,而且程序中注释也很清楚,此处不再赘述。

  • 相关阅读:
    Cocos Creator脚本开发事例
    java调用sap的webservice(需要登录验证)
    Lua require 相对路径
    C++学习笔记
    Lua MD5加密字符串
    USE " cc.exports.* = value " INSTEAD OF SET GLOBAL VARIABLE"
    cocos2d-x学习笔记
    麻将胡牌算法研究
    Tomcat服务器配置https双向认证(使用keytool生成证书)
    MySQL密码忘了怎么办?MySQL重置root密码方法
  • 原文地址:https://www.cnblogs.com/jwen1994/p/12293456.html
Copyright © 2020-2023  润新知