Java时间操作类库-Joda-Time

开发 前端
上一周在做一个产品的需求的时候有个动态计算时间段(如现在是13:00,则时间段为15:10-17:10、17:10-19:10、19:10-21:10;即最早的出发时间为当前时间+参数【2h10min】,最迟的时间段为开始时间在20点前结束时间在20点后的时间段),期间大量使用到了日期时间类库,本着熟悉日期时间类库才有了这篇文章,文章最后我会把我如何实现的这个需求的一个算法贴出来。

 前言

上一周在做一个产品的需求的时候有个动态计算时间段(如现在是13:00,则时间段为15:10-17:10、17:10-19:10、19:10-21:10;即最早的出发时间为当前时间+参数【2h10min】,最迟的时间段为开始时间在20点前结束时间在20点后的时间段),期间大量使用到了日期时间类库,本着熟悉日期时间类库才有了这篇文章,文章最后我会把我如何实现的这个需求的一个算法贴出来。

一、JDK8以前版本中的时间类库

1.1 原始时间类库存在的缺陷与不足

我们在使用Java8之前的类库时,都会在处理日期-时间的时候总是不爽,这其中包括且不限于以下的槽点:

在Java 1.0版本中,对时间、日期的操作完全依赖于 java.util.Data 类,只能以毫秒的精度表示时间,无法表示日期。

  • 在易用性方面有着很大的缺陷,年份的起始时间选择是1900年,月份是从0开始。
  • toString 方法返回值不直观,带有时区。

在Java1.1 版本中,废弃了很多Date 类中的很多方法,并且新增了 java.util.Calendar。但是与Date相同,Calendar 类也有类似的问题和设计缺陷,导致在使用这些类写出的代码也很容易出错。

  • 月份依然是从0开始计算。
  • 常用的日期、时间操作需要同时使用Date、Canendar、SimpleDateFormat,比较繁琐。
  • 部分特性只存在于某一个类(解析和格式化日期或时间的DateFormat方法只存在于Date类中)。
  • DateFormat 不是线程安全的,如果两个线程尝试使用同一个formatter 解析日期,可能会得到无法预期的结果。
  • Date 和 Canendar 都是可变的。

1.2 关于SimpleDateFormat 线程不安全的原因

由于 parse 方法使用的贡献变量 calendar 不是线程安全的。在 format (subFormat) 方法中进行了 calendar 的赋值,在 parse 进行了值得处理,因此在并发的情况下会造成 calendar 清理不及时,值被覆盖的情况。

  1. /** 
  2.  * The {@link Calendar} instance used for calculating the date-time fields 
  3.  * and the instant of time. This field is used for both formatting and 
  4.  * parsing. 
  5.  *   
  6.  * <p>Subclasses should initialize this field to a {@link Calendar} 
  7.  * appropriate for the {@link Locale} associated with this 
  8.  * <code>DateFormat</code>. 
  9.  * @serial 
  10.  */ 
  11. protected Calendar calendar; 
  12.  
  13. @Override 
  14. public StringBuffer format(Date date, StringBuffer toAppendTo, 
  15.                            FieldPosition pos){ 
  16.     pos.beginIndex = pos.endIndex = 0; 
  17.     return format(date, toAppendTo, pos.getFieldDelegate()); 
  18.  
  19. // Called from Format after creating a FieldDelegate 
  20. private StringBuffer format(Date date, StringBuffer toAppendTo, 
  21.                             FieldDelegate delegate) { 
  22.     // Convert input date to time field list 
  23.     calendar.setTime(date); 
  24.  
  25.  // At this point the fields of Calendar have been set.  Calendar 
  26.  // will fill in default values for missing fields when the time 
  27.  // is computed. 
  28.  
  29.  pos.index = start; 
  30.  
  31.  Date parsedDate; 
  32.  try { 
  33.         parsedDate = calb.establish(calendar).getTime(); 
  34.         // If the year value is ambiguous, 
  35.         // then the two-digit year == the default start year 
  36.         if (ambiguousYear[0]) { 
  37.             if (parsedDate.before(defaultCenturyStart)) { 
  38.                 parsedDate = calb.addYear(100).establish(calendar).getTime(); 
  39.             } 
  40.         } 
  41.  } 

 1.3 如何解决上述线程不安全问题?

  1. 使用ThreadLocal 为每个线程都创建一个线程独享 SimpleDateFormat 变量;
  2. 需要的时候创建局部变量;
  3. 使用 org.apacle.commons.lang3.time.DateFormatUtils
  4. 使用Joda-Time (后面介绍)

二、Joda-Time 日期时间类库

2.1 简介

Joda-Time 是Joda提供的一个遵循Apache2.0 开源协议的 JDK以外的优质日期和时间开发库。

  1. Joda除Joda-Time之外的项目有Joda-Money、Joda-Beans、Joda-Convert、Joda-Collect Joda官网 

2.1.1 为什么使用Joda-Time

  1. 使用方便:Calendar 访问“正常的”日期困难,并且缺乏简单的防腐,Joda-Time 拥有简单的字段访问,比如获得年的 getYear() 和 获得星期 中的天 getDayOfWeek() 。
  2. 易于扩展:JDK支持通过使用子类实现多个日历系统,但是这是非常笨重的,并且在实现中很难选出另一个日历系统。Joda-Time 支持基于 Chronology 类实现多个可插拔的日历系统。
  3. 功能全面:Joda-Time 提供了所有的日期和时间计算的必须功能,它提供了即装即用的特性。
  4. 最新的时区计算:时区的实现基于公共时区信息数据库,每年更新数次。新版本的Joda-Time 包括了这个数据库的所有更改,应尽早进行必要的更新,手动更新区域数据很容易。
  5. 日历支持:提供了8种日历系统。
  6. 互通性:内部使用毫秒进行标识,这与JDK或者其他公共的时间表示相一致。
  7. 性能良好:支持针对所有访问的域进行最小的计算。
  8. 良好的测试覆盖率:有全方位的测试人员保证库的质量、
  9. 具有完整文档:有一个完整的用户指南,该指南提供了一个概述,涵盖常见的使用场景。javadoc 非常详细,涵盖API的其余部分。
  10. 发展:自2002年以来积极发展,是一个成熟的可靠的代码库,一些相关的项目目前也是可用的。
  11. 开源:遵循Apache 2.0开源协议发布。

2.1.2 Joda-Time 的关键优点

  1. LocalDate:只包含日期
  2. LocalTime:只包含时间
  3. Instant:时间轴上的时间点
  4. DateTime:时区中完整的日期和时间
  5. DateTimeZone:更好的时区
  6. Duration和Period:持续时间
  7. Interval:两个时间点之间的时间
  8. 全面并且灵活的时间格式化与转换

正因为Joda-Time 与 Java8 之前的时间类库相比,具备了如此多的优点,所以 Joda-Time 成为事实上的标准的Java日期和时间库。

2.2 特性解读

2.2.1 Joda-Time和JDK的互操作性

互操作性是指:Joda 类能够生成 java.util.Date 的实例(以及Calendar),这可以让我们保留现有对JDK的依赖,又能够使用Joda处理复杂的日期/时间计算。

Date To Joda-Time

  1. Date date = new Date(); 
  2. DateTime dateTime = new DateTime(date); 

 Canendar To Joda-Time

  1. Calendar calendar = Calendar.getInstance(); 
  2. DateTime dateTime = new DateTime(calendar); 

 Joda-Time To Date

  1. Date date = new Date();   
  2. DateTime dateTime = new DateTime(date);                        
  3. Date date2 = dateTime.toDate(); 

 Joda-Time To Calendar

  1. Calendar calendar = Calendar.getInstance();   
  2. dateTime = new DateTime(calendar);   
  3. Calendar calendar2 = dateTime.toCalendar(Locale.CHINA);  

 2.2.2 Joda的关键日期/时间概念理解

Joda 使用了以下概念,使得它们可以应用到任何日期/时间库:

不可变性(Immutability)

Joda-Time与java.lang.String类似,它们的实例均无法修改(因为任意对其值改变的操作都会生成新的对象),这也代表了它们是线程安全的。

瞬时性(Instant)

如接口 org.joda.time.ReadableInstant 中所表示的那样,Instant 表示的是一个精确的时间点,是从 epoch:1970-01-01T00:00:00Z 开始计算的毫秒数,这也的设计也使得其子类都可以与JDK Date 以及 Calendar 类兼容。

  1. /** 
  2.  * Defines an instant in the datetime continuum. 
  3.  * This interface expresses the datetime as milliseconds from 1970-01-01T00:00:00Z. 
  4.  * <p> 
  5.  * The implementation of this interface may be mutable or immutable. 
  6.  * This interface only gives access to retrieve data, never to change it. 
  7.  * <p> 
  8.  * Methods in your application should be defined using <code>ReadableInstant</code> 
  9.  * as a parameter if the method only wants to read the instant without needing to know 
  10.  * the specific datetime fields. 
  11.  * <p> 
  12.  * The {@code compareTo} method is no longer defined in this class in version 2.0. 
  13.  * Instead, the definition is simply inherited from the {@code Comparable} interface. 
  14.  * This approach is necessary to preserve binary compatibility. 
  15.  * The definition of the comparison is ascending order by millisecond instant. 
  16.  * Implementors are recommended to extend {@code AbstractInstant} instead of this interface. 
  17.  * 
  18.  * @author Stephen Colebourne 
  19.  * @since 1.0 
  20.  */ 
  21. public interface ReadableInstant extends Comparable<ReadableInstant> { 
  22.  
  23.     /** 
  24.      * Get the value as the number of milliseconds since 
  25.      * the epoch, 1970-01-01T00:00:00Z. 
  26.      * 
  27.      * @return the value as milliseconds 
  28.      */ 
  29.     long getMillis(); 
  30.                        ······ 

 DateTime 类继承图如下:


局部性(Partial)

瞬时性表达的是与epoch相对的时间上的一个精确时刻,而一个局部时间指的是一个时间的一部分片段,其可以通过一些方法使得时间产生变动(本质上还是生成了新的类),这样可以把它当做重复周期中的一点,用到多个地方。

年表(Chronology)

Joda-Time的设计核心就是年表(org.joda.time.Chronology),从根本上将,年表是一种日历系统,是一种计算时间的特殊方式,并且在其中执行日历算法的框架。Joda-Time支持的8种年表如下所示:

  • ISO(默认) - org.joda.time.chrono.ISOChronology
  • GJ - org.joda.time.chrono.GJChronology
  • Gregorian - org.joda.time.chrono.GregorianChronology
  • Julian - org.joda.time.chrono.JulianChronology
  • Coptic - org.joda.time.chrono.CopticChronology
  • Buddhist - org.joda.time.chrono.BuddhistChronology
  • Ethiopic - org.joda.time.chrono.EthiopicChronology
  • Islamic - org.joda.time.chrono.IslamicChronology

以上的每一种年表都可以作为特定日历系统的计算引擎,是可插拔的实现。

时区(Time zone)

具体定义详见百科解释,在实际编码过程中任何严格的时间计算都必须涉及时区(或者相对于GMT),Joda-Time中对应的核心类为org.joda.time.DateTimeZone,虽然日常的使用过程中,并未涉及到对时区的操作,但是DateTimeZone如何对DateTime产生影响是比较值得注意的,此处不进行赘述。

2.3 具体使用方法

上面介绍我完了Joda-Time的一些概念,接下来具体使用我们来进行说明:

2.3.1 创建 Joda-Time 对象

瞬时性-ReadableInstant

  1. // 1.使用系统时间 
  2. DateTime dateTime1 = new DateTime(); 
  3. // 2.使用jdk中的date 
  4. Date jdkDate1 = new Date(); 
  5. DateTime dateTime2 = new DateTime(jdkDate1); 
  6. // 3.使用毫秒数指定 
  7. Date jdkDate2 = new Date(); 
  8. long millis = jdkDate.getTime(); 
  9. DateTime dateTime3 = new DateTime(millis); 
  10. // 4.使用Calendar 
  11. Calendar calendar = Calendar.getInstance(); 
  12. DateTime dateTime4 = new DateTime(calendar); 
  13. // 5.使用多个字段指定一个瞬间时刻(局部时间片段) 
  14. // year month day hour(midnight is zero) minute second milliseconds 
  15. DateTime dateTime5 = new DateTime(2000, 1, 1, 0, 0, 0, 0); 
  16. // 6.由一个DateTime生成另一个DateTime 
  17. DateTime dateTime6 = new DateTime(dateTime1); 
  18. // 7.有时间字符串生成DateTime 
  19. String timeString = "2019-01-01T00:00:00-06:00"
  20. DateTime dateTime7 = DateTime.parse(timeString); 

 局部性-ReadablePartial

当程序中处理的日期、时间并不需要是完整时刻的时候,可以创建一个局部时间,比如只希望专注于年/月/日, 或者一天中的时间,或者是一周中的某天。Joda-Time中有表示这些时间的是org.joda.time.ReadablePartial接口,实现它的两个类LocalDate和LocalTime是分别用来表示年/月/日和一天中的某个时间的。

  1. // 显示地提供所含的每个字段 
  2. LocalDate localDate = new LocalDate(2019, 1, 1); 
  3. // 6:30:06 PM 
  4. LocalTime localTime = new LocalTime(18, 30, 6, 0); 

 LocalDate是替代了早期Joda-Time版本中使用的org.joda.time.YearMonthDay,LocalTime是替代早期版本的org.joda.time.TimeOfDay。(均已被标注为过时状态)。

时间跨度

Joda-Time提供了三个类用于表示时间跨度(在某些业务需求中,它们可能会非常有用)。

Duration

这个类表示以毫秒为单位的绝对精度,提供标准数学转换的方法,同时把时间跨度转换为标准单位。

Period

这个类表示以年月日单位表示。

Interval

这个类表示一个特定的时间跨度,使用一个明确的时刻界定这段时间跨度的范围。Interval 为半开 区间,所以由其封装的时间跨度包括这段时间的起始时刻,但是不包含结束时刻。

2.3.2 使用Joda-Time的方法处理时间

  1. DateTime today = new DateTime(); 
  2. // 获取777秒之前的时间 
  3. DateTime dateTime1 = today.minus(777 * 1000); 
  4. // 获取明天的时间 
  5. DateTime tomorrow = today.plusDays(1); 
  6. // 获取当月第一天的日期 
  7. DateTime dateTime2 = today.withDayOfMonth(1);  
  8. // 获取当前时间三个月后的月份的最后一天 
  9. DateTime dateTime3 = today.plusMonths(3).dayOfMonth().withMaximumValue(); 

 下面列出部分DateTime方法列表: plus/minus开头的方法(比如:plusDay, minusMonths):用来返回在DateTime实例上增加或减少一段时间后的实例

  • plus(long duration) 增加指定毫秒数并返回
  • plusYears(int years) 增加指定年份并返回
  • plusMonths(int months) 增加指定月份并返回
  • plusWeeks(int weeks) 增加指定星期并返回
  • plusDays(int days) 增加指定天数并返回
  • plusHours(int hours) 增加指定小时并返回
  • plusMinutes(int minutes) 增加指定分钟并返回
  • plusSeconds(int seconds) 增加指定秒数并返回
  • plusMillis(int millis) 增加指定毫秒并返回

与之相反的是minus前缀的 plus是增加 minus是减少

with开头的方法:用来返回在DateTime实例更新指定日期单位后的实例

  • withCenturyOfEra(int centuryOfEra) 更新时间世纪单位并返回
  • withYearOfCentury(int yearOfCentury)更新世纪年并返回
  • withYear(int year) 更新时间年并返回
  • withWeekyear(int weekyear) 更新时间周数并返回
  • withMonthOfYear(int monthOfYear)更新时间月份并返回
  • withDayOfYear(int dayOfYear) 更新时间天数并返回
  • withDayOfMonth(int dayOfMonth) 更新时间天数并返回
  • withDayOfWeek(int dayOfWeek) 更新时间天数并返回
  • withHourOfDay(int hour) 更新时间小时并返回
  • withMinuteOfHour(int minute) 更新时间分钟并返回
  • withSecondOfMinute(int second) 更新时间秒数并返回
  • withMillisOfSecond(int millis) 更新时间毫秒并返回
  • withMillisOfDay(int millis) 更新时间毫秒并返回
  • withTimeAtStartOfDay() 获取当天最早时间

判断DateTime对象大小状态的一些操作方法

  • compareTo(DateTime d) 比较两时间大小 时间大于指定时间返回 1 时间小于指定时间返回-1 相等返回0
  • equals(DateTime d) 比较两时间是否相等
  • isAfter(long instant) 判断时间是否大于指定时间
  • isAfterNow() 判断时间是否大于当前时间
  • isBefore(long instant) 判断时间是否小于指定时间
  • isBeforeNow() 判断时间是否小于当前时间
  • isEqual(long instant) 判断时间是否等于指定时间
  • isEqualNow() 判断时间是否等于当前时间

2.3.3 以Joda-Time的方式格式化时间

  1. // 传入的格式化模板只需与JDK SimpleDateFormat兼容的格式字符串即可 
  2. public static String convert(Date date,String dateFormat){ 
  3.     return new DateTime(date).toString(dateFormat); 
  4. // 将JDK中的Date转化为UTC时区的DateTime 
  5. DateTime dateTime = new DateTime(new Date(), DateTimeZone.UTC); 
  6. // 将String转换为DateTime 
  7. public static Date convertUTC2Date(String utcDate){ 
  8.     DateTime dateTime =DateTime.parse(utcDate, DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); 
  9.     return dateTime.toDate(); 
  10.  } 

 更多使用方法请参考官方文档。

三、JAVA 8中新的时间类库

3.1 简介

由于JDK之前版本的类库的缺陷和糟糕的使用体验,再加上已经成为事实标准Joda-Time的影响力,Oracle决定在JAVA API中提供高质量的日期和时间支持,这也就是整合了大部分Joda-Time特性的JDK 8新的时间类库。(Joda-Time的作者实际参与开发,并且实现了JSR310的全部内容,新的API位于java.time下。常用的类有以下几个:LocalDate、LocalTime、Instant、Duration和Period。)

由于JDK 8 新的时间类库大量借鉴了Joda-Time的设计思想乃至命名,因此如果你是Joda-Time的使用者,那你可以无学习成本的使用新的API(当然,它们之间也存在些许差别需要注意到)。

3.2 使用方法

3.2.1 使用LocalDate 和LocalTime

首先是LocalDate,该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。

  1. // 使用指定的日期创建LocalDate 
  2. LocalDate date = LocalDate.of(2019, 1, 1); 
  3. // 获取当前日期 
  4. LocalDate today = LocalDate.now(); 
  5. // 获取今日的属性 
  6. int year = date.getYear(); 
  7. Month month = date.getMonth(); 
  8. int day = date.getDayOfMonth(); 
  9. DayOfWeek dow = date.getDayOfWeek(); 
  10. int len = date.lengthOfMonth(); 
  11. boolean leap = date.isLeapYear(); 
  12. // 通过ChronoField的枚举值获取需要的属性字段 
  13. int year = date.get(ChronoField.YEAR); 

 接着是LocalTime,它表示了一天内的某个时刻。

  1. LocalTime time = LocalTime.of(18, 18, 18); 
  2. int hour = time.getHour(); 
  3. int minute = time.getMinute(); 
  4. int second = time.getSecond(); 

 LocalDate和LocalTime都可以通过使用静态方法parse来解析字符串进行创建。

  1. LocalDate date = LocalDate.parse("2019-01-01"); 
  2.  
  3. LocalTime time = LocalTime.parse("18:18:18"); 

 也可以向parse方法传递一个DateTimeFormatter,该类的实例定义了如何格式化一个日期或者时间对象。它其实是老版java.util.DateFormat的替代品。

3.2.2 LocalDateTime

  1. // 直接创建LocalDateTime 
  2. LocalDateTime dt1 = LocalDateTime.of(2019, Month.JANUARY, 1, 18, 18, 18); 
  3. // 合并日期和时间 
  4. LocalDate date = LocalDate.parse("2019-01-01"); 
  5. LocalTime time = LocalTime.parse("18:18:18"); 
  6. LocalDateTime dt2 = LocalDateTime.of(datetime); 
  7. LocalDateTime dt3 = date.atTime(18, 18, 18); 
  8. LocalDateTime dt4 = date.atTime(time); 
  9. LocalDateTime dt5 = time.atDate(date); 
  10. // 从LocalDateTime中提取LocalDate或者LocalTime 
  11. LocalDate date1 = dt1.toLocalDate(); 
  12. LocalTime time1 = dt1.toLocalTime(); 

 3.3.3 Instant

Instant类是为了方便计算机理解的而设计的,它表示一个持续时间段上某个点的单一大整型数,实际上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算(最小计算单位为纳秒)。

  1. // 传递一个秒数已创建该类的实例 
  2. Instant.ofEpochSecond(3); 
  3. // 传递一个秒数+纳秒 2 秒之后再加上100万纳秒(1秒) 
  4. Instant.ofEpochSecond(2, 1_000_000_000); 

 3.3.4 Duration与Period

Duration是用于比较两个LocalTime对象或者两个Instant之间的时间差值。

  1. Duration d1 = Duration.between(time1, time2); 
  2. Duration d1 = Duration.between(dateTime1, dateTime2); 
  3. Duration d2 = Duration.between(instant1, instant2); 

 Period是用于对年月日的方式对多个时间进行比较。

  1. Period tenDays = Period.between(LocalDate.of(2019, 1, 1), lcalDate.of(2019, 2, 2)); 

当然,Duration和Period类都提供了很多非常方便的工厂类,直接创建对应的实例。

  1. Duration threeMinutes = Duration.ofMinutes(3); 
  2. Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES); 
  3. Period tenDays = Period.ofDays(10); 
  4. Period threeWeeks = Period.ofWeeks(3); 
  5. Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1); 

 3.3.5 操作、解析和格式化日期

  1. // 直接使用withAttribute的方法修改 
  2. LocalDate date1 = LocalDate.of(2019, 1, 1); 
  3. LocalDate date2 = date1.withYear(2019); 
  4. LocalDate date3 = date2.withDayOfMonth(1); 
  5. LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 1); 

 所有声明了Temporal接口的类LocalDate、LocalTime、LocalDateTime以及Instant,它们都使用get和with方法,将对象值的读取和修改区分开,如果使用了不支持的字段访问字段,会抛出一个UnsupportedTemporalTypeException异常。类似的,plus方法和minus方法都声明于Temporal接口。通过这些方法,对TemporalUnit对象加上或者减去一个数字,我们能非常方便地将Temporal对象前溯或者回滚至某个时间段,通过ChronoUnit枚举我们可以非常方便地实现TemporalUnit接口。

3.3.6 更多定制化的处理时间

向重载的with方法传递一个定制化的TemporalAdjuster对象,可以更加灵活地处理日期。时间和日期的API已经提供了大量预定义的TemporalAdjuster,可以通过TemporalAdjuster类的静态工厂方法访问它们。这些方法的名称非常直观,方法名就是问题描述。某些情况下,如果你需要定义自己的TemporalAdjuster,只需要声明TemporalAdjuster接口并且自己实现对应的方法即可。

  1. LocalDate date1 = LocalDate.of(2014, 3, 18); 
  2. LocalDate date2 = date1.with(TemporalAdjuster.nextOrSame(DayOfWeek.SUNDAY)); 
  3. LocalDate date3 = date2.with(TemporalAdjuster.lastDayOfMonth()); 

 3.3.7 解析日期-时间对象

日常工作中,格式化以及解析日期-时间对象是另一个非常重要的功能,而新的java.time.format包就是特别为我们达到这个目的而设计的。这其中,最重要的类是DateTimeFormatter。所有的DateTimeFormatter实例都能用于以一定的格式创建代表特定日期或时间的字符串。(与老的java.util.DateFormat相比较,所有的DateTimeFormatter实例都是线程安全的)

  1. // 使用不同的格式器生成字符串 
  2. LocalDate date = LocalDate.of(2019, 1, 1); 
  3. String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); 
  4. String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); 
  5. // 生成LocalDate对象 
  6. LocalDate date1 = LocalDate.parse("20190101", DateTimeFormatter.BASIC_ISO_DATE); 
  7. LocalDate date2 = LocalDate.parse("2019-01-01", DateTimeFormatter.ISO_LOCAL_DATE); 

  1. // 使用特定的模式创建格式器 
  2. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); 
  3. LocalDate date1 = LocalDate.of(2019, 1, 1); 
  4. String formattedDate = date1.format(formatter); 
  5. LocalDate date2 = LocalDate.parse(formattedDate, formatter); 

 3.3.8 处理不同的时区和日历系统

在新的日期-时间类库中,为了最大程度上的减少在处理时区带来的繁琐和复杂而使用了新的java.time.ZoneId类(与其他日期和时间类一样,ZoneId类也是无法修改的) 来替代老版的java.util.TimeZone。时区是按照一定的规则将区域划分成标准时间相同的区间。在ZoneRules这个类中包含了40个这样的实例。可以简单地通过调用ZoneId的getRules()得到指定时区的规则。每个特定的ZoneId对象都由一个地区ID标识,地区ID都为“{区域}/{城市}”的格式。比如:

  1. ZoneId romeZone = ZoneId.of("Asia/Shanghai"); 

Java 8中在原先的TimeZone中加入了新的方法toZoneId,其作用是将一个老的时区对象转换为ZoneId:

  1. ZoneId zoneId = TimeZone.getDefault().toZoneId(); 

得到的ZoneId对象后可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点:

  1. LocalDate date = LocalDate.of(2019, Month.JANUARY, 1); 
  2. ZonedDateTime zdt1 = date.atStartOfDay(romeZone); 
  3. LocalDateTime dateTime = LocalDateTime.of(2019, Month.JANUARY, 18, 13, 45); 
  4. ZonedDateTime zdt2 = dateTime.atZone(romeZone); 
  5. Instant instant = Instant.now(); 
  6. ZonedDateTime zdt3 = instant.atZone(romeZone); 

 通过ZoneId,还可以将LocalDateTime转换为Instant:

  1. LocalDateTime dateTime = LocalDateTime.of(2019, Month.JANUARY, 18, 13, 45); 
  2. Instant instantFromDateTime = dateTime.toInstant(romeZone); 

 同样可以通过反向的方式得到LocalDateTime对象:

  1. Instant instant = Instant.now(); 
  2. LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone); 

 与Joda-Time所不同的是,Java8中的日期-时间类库提供了4种其他的日历系统,这些日历系统中的每一个都有一个对应的日志类,分别是ThaiBuddhistDate、MinguoDate 、JapaneseDate 以及HijrahDate 。所有这些类以及LocalDate 都实现了ChronoLocalDate接口,能够对公历的日期进行建模。利用LocalDate对象,你可以创建这些类的实例。同样的,利用它们提供的静态工厂方法,你可以创建任何一个Temporal对象的实例。

  1. LocalDate date = LocalDate.of(2019, Month.JANUARY, 1); 
  2. JapaneseDate japaneseDate = JapaneseDate.from(date); 

 参考资料

Joda-Time 简介 Joda Time项目和java8时间api

动态计算时间段

需求:如现在是13:00,则时间段为15:10-17:10、17:10-19:10、19:10-21:10;即最早的出发时间为当前时间+参数【2h10min】,最迟的时间段为开始时间在20点前结束时间在20点后的时间段),求解共有多少个时间段?

分析:

  1. 第一个时间段的开始时间:当前时间+参数【2h10min】,中间的时间段是2h;
  2. 通过理解这句:最迟的时间段为开始时间在20点前结束时间在20点后的时间段,我们可以假设最大的时间变量为 max
  3. 假设当前时间为now,总共有n个时间段,可以推导出公式:now + (2h * n) + 10min <= max;

注意:计算过程都转换成毫秒

  1. public class Test { 
  2.     // 毫秒 
  3.     static final long slot = 130 * 60 * 1000; 
  4.  
  5.     private static List<TimeSelectItem> buildStartEndTime(Long now, Long max) { 
  6.         // now + (2h * n) + 10min  <= max
  7.  
  8.         Long n = (max - now - 60 * 1000) / (120 * 60 * 1000); 
  9.         System.out.println("max:" + max); 
  10.         System.out.println("now:" + now); 
  11.         System.out.println(" max - now:" + (max - now)); 
  12.         System.out.println("n:" + n); 
  13.  
  14.         List<TimeSelectItem> timeSelectItems = new ArrayList<>(); 
  15.  
  16.         Long startTimestamp = now + slot; 
  17.         Long endTimestamp = startTimestamp + 120 * 60 * 1000; 
  18.  
  19.         for (int i = 1; i <= n; i++) { 
  20.             // 起始时间 
  21.             // startTimestamp = startTimestamp + i * (120 * 60 * 1000); 
  22.             // 结束时间 
  23.             endTimestamp = startTimestamp + (120 * 60 * 1000); 
  24.  
  25.             System.out.println(startTimestamp); 
  26.             System.out.println(endTimestamp); 
  27.  
  28.             TimeSelectItem item = new TimeSelectItem(); 
  29.  
  30.             DateTime dt = new DateTime(startTimestamp); 
  31.             int hour = dt.hourOfDay().get(); 
  32.             int millis = dt.getMinuteOfHour(); 
  33.             String startTag = hour + ":" + millis; 
  34.  
  35.             DateTime dt1 = new DateTime(endTimestamp); 
  36.             int hour1 = dt1.hourOfDay().get(); 
  37.             long millis1 = dt1.getMinuteOfHour(); 
  38.             String enTag = hour1 + ":" + millis1; 
  39.  
  40.             item.setDisplayName(startTag + " - " + enTag); 
  41.  
  42.             item.setStartTimestamp(startTimestamp); 
  43.             item.setEndTimestamp(endTimestamp); 
  44.             timeSelectItems.add(item); 
  45.  
  46.             startTimestamp = endTimestamp; 
  47.         } 
  48.         return timeSelectItems; 
  49.     } 
  50.  
  51.     public static void main(String[] args) { 
  52.         Long start = DateTime.now().getMillis(); 
  53.         Calendar c = Calendar.getInstance(); 
  54.         c.setTime(new Date()); 
  55.         c.set(Calendar.HOUR_OF_DAY, 20); 
  56.         c.set(Calendar.MINUTE, 0); 
  57.         c.set(Calendar.SECOND, 0); 
  58.  
  59.  
  60.         DateTime dt = new DateTime(); 
  61.         dt.withHourOfDay(20); 
  62.         Long end = c.getTimeInMillis(); 
  63.         
  64.         // List<TimeSelectItem> list = buildStartEndTime(1614747600000L, 1614772800000L); 
  65.         List<TimeSelectItem> list = buildStartEndTime(1614834000000L, end); 
  66.         for (TimeSelectItem item : list ) { 
  67.             System.out.println(item); 
  68.         } 
  69.     } 

 【编辑推荐】

 

责任编辑:姜华 来源: 今日头条
相关推荐

2014-12-22 10:14:31

Java8

2010-03-18 11:06:18

Python stuc

2009-08-17 17:42:57

C#数据库操作类

2023-07-13 08:26:49

Java罗汉增强类

2023-04-10 09:11:27

HutoolJava工具

2011-12-07 15:58:25

JavaNIO

2016-12-13 14:03:54

JAVA操作工具

2020-10-15 17:38:00

Time Wheel

2019-11-20 08:56:51

Java工具类库IO

2021-09-29 11:15:56

PyAutoGUIPython键鼠操作

2009-01-04 11:55:09

Java数组Java常用工具Java类

2012-04-17 11:21:50

Java

2011-12-12 10:19:00

JavaNIO

2011-12-12 10:33:47

JavaNIO

2009-07-22 16:27:24

iBATIS配置类iBATIS操作类

2023-10-11 07:00:44

高可用程序客户端

2009-07-31 16:45:23

ASP.NET数据库操

2017-03-22 11:18:44

2017Java类库

2009-06-25 16:03:29

Netbeans调试

2010-04-30 14:39:03

Oracle数据库
点赞
收藏

51CTO技术栈公众号