zoukankan      html  css  js  c++  java
  • 一篇文章概括 Java Date Time 的使用

    本文目的:掌握 Java 中日期和时间常用 API 的使用。
    参考:Jakob Jenkov的英文教程Java Date Time TutorialJavaDoc

    概览

    Java 8 新增 API

    Java 8 部分新类 描述
    Instant 表示时间线上的某一瞬间,用秒和纳秒表示。
    Duration 表示时间差,用秒和纳秒表示。
    LocalDate 表示没有时区信息的日期,例如生日、法定假日等。
    LocalTime 表示没有时区信息的一天中的本地时间。
    LocalDateTime 表示没有时区信息的日期和时间
    ZonedDateTime 表示日期和时间,包括时区信息
    DateTimeFormatter 将日期时间对象格式化为字符串。

    众所周知,在 Java 8 中添加了一个全新的日期时间 API 位于 java.time 包中,主要变化是,自1970年1月1日以来,日期和时间现在不再由单个毫秒数表示,而是由1970年1月1日以来的秒数和纳秒数表示。
    秒数既可以是正的,也可以是负的,用 long 表示。纳秒数始终为正,由 int 表示。

    Java 7 具有以下日期和时间类和方法:

    Java 7 日期时间常用类/方法 描述
    System.currentTimeMillis() 自1970年1月1日起以毫秒为单位返回当前日期和时间的静态方法
    java.util.Date 表示日期和时间的类。这个类中的大多数方法都是不推荐的。
    java.sql.Date 表示日期的类。这个date类与JDBC一起使用。
    java.sql.Timestamp 表示日期和时间的类。这个date和time类与JDBC一起使用。
    java.util.Calendar 日历类的基类。 有方法做日期和时间算术,比如将日期或月份添加到另一个日期。
    java.util.GregorianCalendar 一个 Calendar 类的子类。代表公历,在今天的西方世界大部分地区使用。拥有 Calendar 中的所有做日期和时间算术的方法。
    java.util.TimeZone 一个表示时区的类,在跨时区执行日历计算时非常有用。

    应该使用所有这些类中的哪一个取决于想要做什么,如果你需要做简单的计时, System.currentTimeMillis() 方法就可以了。

    • 如果只需要一个对象来保存日期,例如作为简单域模型对象中的属性,则可以使用 java.util.Date 类。
    • 如果需要读取和写入数据库的日期和时间,则使用 java.sql.Date 和 java.sql.Timestamp 类。
    • 如果您需要进行日期计算,例如将日期或月份添加到另一个日期,或者查看工作日(星期一,星期二等)这些给定日期,或者转换时区之间的日期和时间,请使用 java.util.Calendar 和 java .util.GregorianCalendar 类。

    System.currentTimeMillis()

    currenttimemillis() 静态方法以毫秒为单位返回自1970年1月1日以来的时间。返回的值是long。这里有一个例子:

    long timeNow = System.currentTimeMillis();
    

    这个返回值可以用来初始化 java.util.Date, java.sql.Date, java.sql.Timestamp 和 java.util.GregorianCalendar 对象,它还可以用于在程序中测量时间。

    currenttimemillis() 方法的粒度大于 1 毫秒,这取决于操作系统,还可能更大,许多操作系统以几十毫秒为单位测量时间。如果需要更精确的计时,请使用 System.nanoTime() ,但是这个方法返回的时间是从任意一个时刻计算的,甚至有可能是负数,所以不能用于初始化日期时间对象,只适合用于计算两个时间点的时间差。

    java.util.Date

    用来表示日期,包含年月日时分秒 ,目前该类中的大多数方法都不赞成使用了,一般用 Calendar 类来代替它,但还是有必要简单了解一下。
    下面是一些使用例子:

    Date dateNow = new Date(); // 使用当前日期和时间创建
    

    Date 类的默认构造器,源码是这样的:

    public Date() {
            this(System.currentTimeMillis());
    }
    

    也可以使用一个 long 型的有参构造函数:

    Date date = new Date(long);
    

    Date 类还有一个 getTime() 实例方法,这个方法的返回值就是 new Date(long) 时指定的 long 参数。

    从 Java 8 开始,新增了和 Instant 互相转换的方法,关于 Instant 请参考本文下部分,这里了解就行:

    static Date from(Instant instant);
    Instant toInstant();
    

    java.sql.Date

    此类是上述 java.util.Date 类的子类,所以它继承了 java.util.Date 的所有方法和字段。一般在 JDBC API 中使用它,比如可以在 PreparedStatement 上设置日期,或者从 ResultSet 获取日期,

    和 java.util.Date 最大的区别就是它只记日期,不记时间,即只有年月日,如果构造的时候包含了时间信息,那么时间信息会被舍弃,如果要记时间,需要用到 java.sql.Timestamp 类。

    java.sql.Timestamp

    此类也继承了 java.util.Date,包含的信息有年月日时分秒纳秒,是的,它还扩展了纳秒,一个使用示例如下:

    long time = System.currentTimeMillis();
    java.sql.Timestamp timestamp = new java.sql.Timestamp(time);
    
    timestamp.setNanos(123456);
    int nanos = timestamp.getNanos(); // nanos = 123456
    

    java.util.Calendar 和 GregorianCalendar

    Calendar 抽象类用于执行日期和时间换算,无法使用构造器实例化它,原因是世界上有不止一个日历。
    但是其提供了一个 getInstance() 方法,可以获取对应当前时间的 Calendar 对象:

    Calendar rightNow = Calendar.getInstance();
    

    getInstance() 方法底层是如下这样实现的:

    public static Calendar getInstance() {
            return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }
    

    没错,很容易想到,此方法还有重载的,可以提供部分指定初始化参数的版本,如下:

    getInstance(TimeZone zone);
    getInstance(Locale aLocale);
    getInstance(TimeZone zone, Locale aLocale);
    

    此外,一般可以通过其子类 GregorianCalendar 来访问日期时间信息,一个例子如下:

    Calendar calendar = new GregorianCalendar();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH); 
    int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); // 一月 Jan = 0, 不是 1
    int dayOfWeek  = calendar.get(Calendar.DAY_OF_WEEK);
    int weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
    int weekOfMonth= calendar.get(Calendar.WEEK_OF_MONTH);
    
    int hour = calendar.get(Calendar.HOUR);        // 12 小时制
    int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY); // 24 小时制
    int minute = calendar.get(Calendar.MINUTE);
    int second = calendar.get(Calendar.SECOND);
    int millisecond= calendar.get(Calendar.MILLISECOND);
    
    calendar.set(Calendar.YEAR, 2018);
    calendar.set(Calendar.MONTH, 11); // 11 = december,十二月
    calendar.set(Calendar.DAY_OF_MONTH, 24); // 圣诞节
    

    年月日等的加减

    Calendar calendar = new GregorianCalendar();
    // 加 1 天
    calendar.add(Calendar.DAY_OF_MONTH, 1);
    // 当第二个参数为负数时,表示减,下面就是减 1 天
    calendar.add(Calendar.DAY_OF_MONTH, -1);
    

    Calendar/Date/String 的互相转换

    // Calendar to Date
    Calendar calendar = Calendar.getInstance();
    java.util.Date date = calendar.getTime();
    
    // Date to Calendar
    calendar.setTime(new java.util.Date());
    
    // Calendar to String
    Calendar calendat = Calendar.getInstance();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String dateStr = sdf.format(calendar.getTime());
    
    // String to Calendar
    String str = "2018-12-3";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = sdf.parse(str);
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    
    // Date to String
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String dateStr = sdf.format(new Date());
    
    // String to Date
    String str = "2018-12-3";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date dateParse = sdf.parse(str);
    

    Calendar 容易犯错的地方

    1. Calendar 类的 MONTH 字段不是往常的从 1 到 12 。而是从 0 到 11 ,其中 0 是一月,11 是十二月。
    2. 一周的某一天是从 1 到 7 表示,这点不出意料,但是一周的第一天是星期日而不是星期一,这意味着 1 =星期日,2 =星期一,7 =星期六。
    3. 如果需要进行复杂的日期和时间计算,最好在官方JavaDoc中阅读java.util.Calendar的类文档。 类文档包含有关类的特定行为的更多详细信息。 例如,如果将日期设置为 2018 年 1 月 34 日,那么实际日期是什么?

    java.util.TimeZone

    TimeZone 是一个表示时区的类,在跨时区执行日历计算时非常有用,一般和 Calendar 一起使用。

    注意:在 Java 8 日期时间 API 中,时区由 java.time.ZoneId 类表示。 如果使用的是 Java 8 日期时间类(如 ZonedDateTime 类)的话,则只需要使用 ZoneId 类就行了。 如果使用的是 Calendar (来自Java 7和更早的日期时间API),那么仍然可以使用 java.util.TimeZone 类。

    从 Calendar 中获取TimeZone

    Calendar calendar = new GregorianCalendar();
    // 从 Calendar 获取时区
    TimeZone timeZone = calendar.getTimeZone();
    
    // 为 Calendar 设置时区
    calendar.setTimeZone(timeZone);
    

    创建 TimeZone 对象

    // 获取默认时区对象
    TimeZone timeZone = TimeZone.getDefault();
    // 获取指定时区对象
    TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");
    TimeZone timeZone = TimeZone.getTimeZone("Europe/Copenhagen");
    

    TimeZone.getTimeZone() 方法的参数可以是一个 zone ID ,可以查看 JavaDoc 获取全部 ID 。

    注意:如果 getTimeZone(String zoneID);方法的 zoneID 设置错误(不匹配系统支持的任意值),比如 "Asiannn/Shanghai",那也不会抛出任何异常,而是默默地设置 zoneID 为 GMT0 ,即格林威治时间。

    时区的名称、ID和偏移量

    我们可以查看给定时区的显示名称、ID和时间偏移量,如下所示

    TimeZone timeZone = TimeZone.getDefault();
    System.out.println(timeZone.getDisplayName());
    System.out.println(timeZone.getID());
    System.out.println(timeZone.getOffset(System.currentTimeMillis()));
    

    以上代码将输出:

    中国标准时间
    Asia/Shanghai
    28800000
    

    getOffset() 方法以 int 类型返回该时区在指定日期的 UTC 偏移量(毫秒)。上例中的 28800000 毫秒,也就是 8 h ,我们在东八区(+8)。

    在时区之间转换

    TimeZone timeZoneCN = TimeZone.getTimeZone("Asia/Shanghai");
    TimeZone timeZone0 = TimeZone.getTimeZone("Etc/GMT0");
    
    Calendar calendar = new GregorianCalendar();
    
    calendar.setTimeZone(timeZoneCN);
    long timeCN = calendar.getTimeInMillis();
    System.out.println(calendar.getTimeZone().getDisplayName());
    System.out.println("timeCN = " + timeCN);
    System.out.println("hour = " + calendar.get(Calendar.HOUR_OF_DAY));
    
    calendar.setTimeZone(timeZone0);
    System.out.println(calendar.getTimeZone().getDisplayName());
    long time0 = calendar.getTimeInMillis();
    System.out.println("time0 = " + time0);
    System.out.println("hour = " + calendar.get(Calendar.HOUR_OF_DAY));
    

    以上程序将会输出如下:

    中国标准时间
    timeCN = 1543850448183
    hour = 23
    格林威治时间
    time0 = 1543850448183
    hour = 15
    

    可以看到,以毫秒为单位的时间在两个时区是相同的,但是已从23点变成15点钟了,因为中国标准时间比格林威治时间快 8 小时,如此,我们设置不同时区获取对应时区的正确时间,这样就实现的换算的目的。

    使用 SimpleDateFormat 解析和格式化日期

    java.text.SimpleDateFormat 类可以解析字符串中的日期,也可以格式化字符串中的日期,本文将展示几个例子:

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    
    String dateString = format.format(new Date());
    Date date = format.parse ("2018-12-03");  
    

    作为参数传递给 SimpleDateFormat 类的字符串是一种模式(模板),用于说明如何解析和格式化日期。 在上面的示例中使用了模式“yyyy-MM-dd”,表示年份 4 位数(yyyy),月份 2 位数(MM)和日期 2 位数(dd)的表示形式,"2018-12-03"中使用‘-’分割是因为在模式中也是用‘-’分割字母的。

    以下是常见模式字母列表,具体请看 JavaDoc :

    y   = year   (yy or yyyy)
    M   = month  (MM)
    d   = day in month (dd)
    h   = hour (0-12)  (hh)
    H   = hour (0-23)  (HH)
    m   = minute in hour (mm)
    s   = seconds (ss)
    S   = milliseconds (SSS)
    z   = time zone  text        (e.g. Pacific Standard Time...)
    Z   = time zone, time offset (e.g. -0800)
    
    //一下是一些示例:
    yyyy-MM-dd HH:mm:ss  (2018-12-3 23:59:59)
    HH:mm:ss.SSS (23:59.59.999)
    yyyy-MM-dd HH:mm:ss.SSS   (2009-12-31 23:59:59.999)
    yyyy-MM-dd HH:mm:ss.SSS Z   (2009-12-31 23:59:59.999 +0100)       
    

    如果指定 “dd” 来解析new SimpleDateFormat("yyyy-MM-dd");那么天数肯定被表示为 2 位,比如 3 号就是 03。
    如果是指定 “d” 来解析new SimpleDateFormat("yyyy-MM-d"); 那么天数优先是 1 位,比如 3 号就是 3, 如果超出 1 位,那会自动扩展为 2 位,比如 13 号,那么就是 13 。

    Instant 表示某一瞬间

    Java .time.Instant 类表示时间线上的一个特定时刻,被定义为自原点起的偏移量,原点是1970年1月1日00点格林,也就是格林尼治时间。 时间以每天 86400 秒为单位,从原点向前移动。

    Java.time 这个包是线程安全的,并且和其他大部分类一样,是不可变类。Instant 也不例外。

    使用 Instant 类的工厂方法之一创建实例。例如,要创建一个表示当前时刻的时间点,可以调用 instance .now() ,如下所示:

    Instant now = Instant.now();
    

    Instant 对象包含秒和纳秒,来表示其包含的时间, 自纪元以来的秒数是上完提到的自原点以来的秒数。 纳秒是 Instant 的一部分,不到一秒钟。分别可以通过如下 2 个方法获取:

    long getEpochSecond();
    int getNano();
    

    Instant 运算

    Instant类还有几种方法可用于相对于Instant进行计算。 这些方法中的一些(不是全部)是:

    • plusSeconds()
    • plusMillis()
    • plusNanos()
    • minusSeconds()
    • minusMillis()
    • minusNanos()

    一个例子如下:

    Instant now = Instant.now(); // 现在这一瞬间
    
    Instant later = now.plusSeconds(3); // 3 秒后的瞬间
    Instant earlier = now.minusSeconds(3); // 3 秒前的瞬间
    

    因为 Instant 是不可变的,所以上面的计算方法,是返回一个代表计算结果的新的 Instant 对象。

    Duration 表示时间间隔

    java.time.Duration 表示两个 Instant 之间的一段时间,Duration 实例是不可变的,因此一旦创建它,就不能更改它的值。但可以基于一个 Duration 对象创建新的 Duration 对象。

    创建 Duration 对象

    可以使用 Duration 类的工厂方法之一创建 Duration 对象,有 between()/ofDays()/ofSeconds()/from() 等方法,但其底层都是调用了同一个构造方法,其源码如下:

     private Duration(long seconds, int nanos) {
            super();
            this.seconds = seconds;
            this.nanos = nanos;
        }
    

    下面是一个使用 between() 方法创建的示例:

    Instant first = Instant.now();
    // 其他耗时操作
    Instant second = Instant.now();
    Duration duration = Duration.between(first, second);
    

    访问 Duration 对象的时间信息

    从上述构造器源码可知,Duration 在内部维护两个值:

    • final int nanos;
    • final long seconds;

    请注意没有单独的毫秒部分,只有纳秒和秒。但可以可以将整个时间间隔 Duration 转换为其他时间单位,如纳秒、分钟、小时或天:

    • long toNanos()
    • long toMillis()
    • long toMinutes()
    • long toHours()
    • long toDays()

    toNanos() 与 getNano() 的不同之处在于 getNano() 仅返回持续时间小于一秒的部分(即整个时间段中不到 1 秒的那部分)。 toNanos() 方法返回的是转换为纳秒的整个时间段(即秒部分转成纳秒+纳秒部分)。

    没有 toSeconds() 方法,因为 getSeconds() 方法已经可以获取 Duration 的秒部分。

    Duration 的计算

    Duration 类包含一组可用于基于 Duration 对象执行计算的方法。其中一些方法是:

    • Duration plus(Duration duration)
    • Duration plusNanos(long)
    • Duration plusMillis(long)
    • Duration plusSeconds(long)
    • Duration plusMinutes(long)
    • Duration plusHours(long)
    • Duration plusDays(long)
    • Duration minusXxx(long) 上面所有对应 minus 方法

    这些方法的使用大同小异,一下是一个例子:

    Duration start = ... 
    Duration added = start.plusDays(3); // 加 3 天
    Duration subtracted = start.minusDays(3); // 减 3 天
    

    同样,为了使Duration对象保持不可变,所有计算方法都返回表示计算结果的新的 Duration 对象。

    LocalDate 表示本地日期

    java.time.LocalDate 表示本地日期,没有时区信息。当地的日期可以是生日或法定假日等,与一年中的某一天有关,和一天中的某一时间无关。这个类对象也是不可变的,计算操作会返回一个新的 LocalDate 对象。
    下面是一个创建 LocalDate 对象的例子:

    LocalDate localDate1 = LocalDate.now();
    LocalDate localDate2 = LocalDate.of(2018, 11, 11);
    

    还有很多方法可以创建 LocalDate 对象,我列出一部分下面,具体的请查看 JavaDoc 。

    访问 LocalDate 中的日期信息

    LocalDate 中一共有 3 个日期信息字段,分别是:

    • final int year;
    • final short month;
    • final short day;

    对应一些获取信息的方法:

    • int getYear()
    • Month getMonth()
    • int getDayOfMonth()
    • int getDayOfYear()
    • DayOfWeek getDayOfWeek()

    LocalDate 计算

    • LocalDate plusYears(long yearsToAdd)
    • LocalDate plusMonths(long monthsToAdd)
    • LocalDate plusWeeks(long weeksToAdd)
    • LocalDate plusDays(long daysToAdd)
    • LocalDate minusXxx(long xxxToSubtract) 对应上面 plus 方法的 minus 版本

    下面是一个例子:

    LocalDate localDate = LocalDate.of(2018, 12, 12);
    
    LocalDate localDate1 = localDate.plusYears(3); // 加 3 年
    LocalDate localDate2 = localDate.minusYears(3);
    

    LocalTime 表示本地时间

    java.time.LocalTime 表示没有任何时区信息的特定时间,例如,上午 10 点。同样,这是一个不可变类。
    下面是一个创建 LocalTime 对象的例子:

    LocalTime localTime1 = LocalTime.now();
    LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);
    

    LocalTime 内部维护了 4 个变量维护时间信息:

    • final byte hour;
    • final byte minute;
    • final byte second;
    • final int nano;

    也包含了必要的计算时间的方法,例如 LocalTime plusHours(long hoursToAdd); 其他的和 LocalDate 大同小异,就不展开讲了。

    LocalDateTime 表示本地日期和时间

    java.time.LocalDateTime 类表示没有任何时区信息的本地日期和时间,同样是不可变类。

    查看其源码发现其内部就是维护了一个 LocalDate 对象和一个 LocalTime 对象来表示日期时间信息。

    final LocalDate date;
    final LocalTime time;
    

    所以完全可以把它看成是 LocalDate 和 LocalTime 的结合。
    下面是一个创建 LocalDateTime 对象的例子:

    LocalDateTime localDateTime1 = LocalDateTime.now();
    LocalDateTime localDateTime2 =LocalDateTime.of(2018, 11, 11, 10, 55, 36, 123);
    

    上面第二行代码使用 of() 工厂方法创建对象,其参数分别对应年月日时分秒纳秒。

    其他获取日期时间信息和计算请参考 LocalDate 和 LocalTime 的。

    ZonedDateTime 表示带有时区信息的日期和时间

    java.time.ZonedDateTime 可以用来代表世界上某个特定事件的开始,比如会议、火箭发射等等。
    它同样是不可变类,下面是一个创建此类对象的例子:

    ZonedDateTime zonedDateTime = ZonedDateTime.now();
    ZoneId zoneId = ZoneId.of("UTC+1");
    ZonedDateTime zonedDateTime2 = ZonedDateTime.of(2015, 11, 30, 23, 45, 59, 1234, zoneId);
    

    时区

    时区由 ZoneId 类表示,如前面的示例所示。可以使用 ZoneId.now() 方法创建 ZoneId 对象。也可以使用 of() 方法指定时区信息,下面是一个例子:

    ZoneId zoneId1 = ZoneId.of("UTC+1");
    ZoneId zoneId2 = ZoneId.of("Europe/Paris");
    

    传递给 of() 方法的参数是要为其创建 ZoneId 的时区的ID。在上面的例子中,ID 是“UTC+1”,它是 UTC (格林威治)时间的偏移量。另外也可以直接指定具体的时区 ID 字符串,这在本文开头有介绍。

    ZonedDateTime 相比 LocalDateTime 只是多了地区信息,其内部维护了下面这 3 个变量来表示日期信息和地区:

    • final LocalDateTime dateTime;
    • final ZoneOffset offset;
    • final ZoneId zone;

    所以其他的方法如获取日期时间信息和计算时间,请参考上述。

    DateTimeFormatter

    java.time.DateTimeFormatter 类用于解析和格式化用 Java 8 日期时间 API 中的类表示的日期。

    预定义 DateTimeFormatter 对象

    DateTimeFormatter 类包含一组预定义的(常量)实例,这些实例可以解析和格式化来自标准日期格式的日期。这省去了为 DateTimeFormatter 定义日期格式的麻烦。包含的部分预定义实例如下:

    BASIC_ISO_DATE
    
    ISO_LOCAL_DATE
    ISO_LOCAL_TIME
    ISO_LOCAL_DATE_TIME
    
    ISO_OFFSET_DATE
    
    ISO_ZONED_DATE_TIME
    

    这些预定义的 DateTimeFormatter 实例中的每一个都预先配置为格式化和解析不同格式的日期。 这里不解释所有这些预定义的 DateTimeFormatter 实例。 可以在 JavaDoc 中查看。

    格式化 Date 的例子

    DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
    
    String formattedDate = formatter.format(LocalDate.now());
    System.out.println(formattedDate); // 20181204
    
    String formattedZonedDate = formatter.format(ZonedDateTime.now());
    System.out.println("formattedZonedDate = " + formattedZonedDate);// 20181204+0800
    

    最后一行输出 20181204+0800 代表 UTC+8 时区的 2019 年、第 12 个月(12 月)和第 4 天(第 4 天)。


  • 相关阅读:
    windows设置文件夹显示缩略图
    windows合并文件夹窗口
    crm高速开发之Entity
    在Eclipse中搭建Dagger和Dagger2使用环境
    CCEditBox/CCEditBoxImpl
    failed to sync branch You might need to open a shell and debug the state of this repo.
    五个方法:让站点用心服务于每一位客户
    Mobile first! Wijmo 5 + Ionic Framework之:Hello World!
    ST Nucleo mbed套件开发 一 MBED环境使用 以Nucleo-F401为例 (二)
    关于Windows通过远程桌面訪问Ubuntu
  • 原文地址:https://www.cnblogs.com/czwbig/p/10062909.html
Copyright © 2011-2022 走看看