Java时间操作类库—Joda-Time

前言

上一周在做一个产品的需求的时候有个动态计算时间段(如现在是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 清理不及时,值被覆盖的情况。

/** * The {@link Calendar} instance used for calculating the date-time fields * and the instant of time. This field is used for both formatting and * parsing. *   * <p>Subclasses should initialize this field to a {@link Calendar} * appropriate for the {@link Locale} associated with this * <code>DateFormat</code>. * @serial */protected Calendar calendar;

@Overridepublic StringBuffer format(Date date, StringBuffer toAppendTo,                           FieldPosition pos){    pos.beginIndex = pos.endIndex = 0;    return format(date, toAppendTo, pos.getFieldDelegate());}

// Called from Format after creating a FieldDelegateprivate StringBuffer format(Date date, StringBuffer toAppendTo,                            FieldDelegate delegate) {    // Convert input date to time field list    calendar.setTime(date);

 // At this point the fields of Calendar have been set.  Calendar // will fill in default values for missing fields when the time // is computed.

 pos.index = start;

 Date parsedDate; try {        parsedDate = calb.establish(calendar).getTime();        // If the year value is ambiguous,        // then the two-digit year == the default start year        if (ambiguousYear[0]) {            if (parsedDate.before(defaultCenturyStart)) {                parsedDate = calb.addYear(100).establish(calendar).getTime();            }        } }}

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以外的优质日期和时间开发库。

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

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

Date date = new Date();DateTime dateTime = new DateTime(date);

Canendar To Joda-Time

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

Joda-Time To Date

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

Joda-Time To Calendar

Calendar calendar = Calendar.getInstance();  dateTime = new DateTime(calendar);  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 类兼容。

/** * Defines an instant in the datetime continuum. * This interface expresses the datetime as milliseconds from 1970-01-01T00:00:00Z. * <p> * The implementation of this interface may be mutable or immutable. * This interface only gives access to retrieve data, never to change it. * <p> * Methods in your application should be defined using <code>ReadableInstant</code> * as a parameter if the method only wants to read the instant without needing to know * the specific datetime fields. * <p> * The {@code compareTo} method is no longer defined in this class in version 2.0. * Instead, the definition is simply inherited from the {@code Comparable} interface. * This approach is necessary to preserve binary compatibility. * The definition of the comparison is ascending order by millisecond instant. * Implementors are recommended to extend {@code AbstractInstant} instead of this interface. * * @author Stephen Colebourne * @since 1.0 */public interface ReadableInstant extends Comparable<ReadableInstant> {

    /**     * Get the value as the number of milliseconds since     * the epoch, 1970-01-01T00:00:00Z.     *     * @return the value as milliseconds     */    long getMillis();                       ······}

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.使用系统时间DateTime dateTime1 = new DateTime();// 2.使用jdk中的dateDate jdkDate1 = new Date();DateTime dateTime2 = new DateTime(jdkDate1);// 3.使用毫秒数指定Date jdkDate2 = new Date();long millis = jdkDate.getTime();DateTime dateTime3 = new DateTime(millis);// 4.使用CalendarCalendar calendar = Calendar.getInstance();DateTime dateTime4 = new DateTime(calendar);// 5.使用多个字段指定一个瞬间时刻(局部时间片段)// year month day hour(midnight is zero) minute second millisecondsDateTime dateTime5 = new DateTime(2000, 1, 1, 0, 0, 0, 0);// 6.由一个DateTime生成另一个DateTimeDateTime dateTime6 = new DateTime(dateTime1);// 7.有时间字符串生成DateTimeString timeString = "2019-01-01T00:00:00-06:00";DateTime dateTime7 = DateTime.parse(timeString);

局部性-ReadablePartial

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

// 显示地提供所含的每个字段LocalDate localDate = new LocalDate(2019, 1, 1);// 6:30:06 PMLocalTime 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的方法处理时间

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

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

  1. plus(long duration) 增加指定毫秒数并返回

  2. plusYears(int years) 增加指定年份并返回

  3. plusMonths(int months) 增加指定月份并返回

  4. plusWeeks(int weeks) 增加指定星期并返回

  5. plusDays(int days) 增加指定天数并返回

  6. plusHours(int hours) 增加指定小时并返回

  7. plusMinutes(int minutes) 增加指定分钟并返回

  8. plusSeconds(int seconds) 增加指定秒数并返回

  9. plusMillis(int millis) 增加指定毫秒并返回

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

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

  1. withCenturyOfEra(int centuryOfEra) 更新时间世纪单位并返回
  2. withYearOfCentury(int yearOfCentury)更新世纪年并返回
  3. withYear(int year) 更新时间年并返回
  4. withWeekyear(int weekyear) 更新时间周数并返回
  5. withMonthOfYear(int monthOfYear)更新时间月份并返回
  6. withDayOfYear(int dayOfYear) 更新时间天数并返回
  7. withDayOfMonth(int dayOfMonth) 更新时间天数并返回
  8. withDayOfWeek(int dayOfWeek) 更新时间天数并返回
  9. withHourOfDay(int hour) 更新时间小时并返回
  10. withMinuteOfHour(int minute) 更新时间分钟并返回
  11. withSecondOfMinute(int second) 更新时间秒数并返回
  12. withMillisOfSecond(int millis) 更新时间毫秒并返回
  13. withMillisOfDay(int millis) 更新时间毫秒并返回
  14. withTimeAtStartOfDay() 获取当天最早时间

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

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

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

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

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

三、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,该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。

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

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

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

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

LocalDate date = LocalDate.parse("2019-01-01");

LocalTime time = LocalTime.parse("18:18:18");

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

3.2.2 LocalDateTime

// 直接创建LocalDateTimeLocalDateTime dt1 = LocalDateTime.of(2019, Month.JANUARY, 1, 18, 18, 18);// 合并日期和时间LocalDate date = LocalDate.parse("2019-01-01");LocalTime time = LocalTime.parse("18:18:18");LocalDateTime dt2 = LocalDateTime.of(date, time);LocalDateTime dt3 = date.atTime(18, 18, 18);LocalDateTime dt4 = date.atTime(time);LocalDateTime dt5 = time.atDate(date);// 从LocalDateTime中提取LocalDate或者LocalTimeLocalDate date1 = dt1.toLocalDate();LocalTime time1 = dt1.toLocalTime();

3.3.3 Instant

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

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

3.3.4 Duration与Period

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

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

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

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

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

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

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

// 直接使用withAttribute的方法修改LocalDate date1 = LocalDate.of(2019, 1, 1);LocalDate date2 = date1.withYear(2019);LocalDate date3 = date2.withDayOfMonth(1);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接口并且自己实现对应的方法即可。

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

3.3.7 解析日期-时间对象

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

// 使用不同的格式器生成字符串LocalDate date = LocalDate.of(2019, 1, 1);String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);// 生成LocalDate对象LocalDate date1 = LocalDate.parse("20190101", DateTimeFormatter.BASIC_ISO_DATE);LocalDate date2 = LocalDate.parse("2019-01-01", DateTimeFormatter.ISO_LOCAL_DATE);
// 使用特定的模式创建格式器DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");LocalDate date1 = LocalDate.of(2019, 1, 1);String formattedDate = date1.format(formatter);LocalDate date2 = LocalDate.parse(formattedDate, formatter);

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

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

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

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

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

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

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

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

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

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

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

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

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

动态计算时间段

需求:如现在是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;

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

public class Test {    // 毫秒    static final long slot = 130 * 60 * 1000;

    private static List<TimeSelectItem> buildStartEndTime(Long now, Long max) {        // now + (2h * n) + 10min  <= max;

        Long n = (max - now - 60 * 1000) / (120 * 60 * 1000);        System.out.println("max:" + max);        System.out.println("now:" + now);        System.out.println(" max - now:" + (max - now));        System.out.println("n:" + n);

        List<TimeSelectItem> timeSelectItems = new ArrayList<>();

        Long startTimestamp = now + slot;        Long endTimestamp = startTimestamp + 120 * 60 * 1000;

        for (int i = 1; i <= n; i++) {            // 起始时间            // startTimestamp = startTimestamp + i * (120 * 60 * 1000);            // 结束时间            endTimestamp = startTimestamp + (120 * 60 * 1000);

            System.out.println(startTimestamp);            System.out.println(endTimestamp);

            TimeSelectItem item = new TimeSelectItem();

            DateTime dt = new DateTime(startTimestamp);            int hour = dt.hourOfDay().get();            int millis = dt.getMinuteOfHour();            String startTag = hour + ":" + millis;

            DateTime dt1 = new DateTime(endTimestamp);            int hour1 = dt1.hourOfDay().get();            long millis1 = dt1.getMinuteOfHour();            String enTag = hour1 + ":" + millis1;

            item.setDisplayName(startTag + " - " + enTag);

            item.setStartTimestamp(startTimestamp);            item.setEndTimestamp(endTimestamp);            timeSelectItems.add(item);

            startTimestamp = endTimestamp;        }        return timeSelectItems;    }

    public static void main(String[] args) {        Long start = DateTime.now().getMillis();        Calendar c = Calendar.getInstance();        c.setTime(new Date());        c.set(Calendar.HOUR_OF_DAY, 20);        c.set(Calendar.MINUTE, 0);        c.set(Calendar.SECOND, 0);

        DateTime dt = new DateTime();        dt.withHourOfDay(20);        Long end = c.getTimeInMillis();

        // List<TimeSelectItem> list = buildStartEndTime(1614747600000L, 1614772800000L);        List<TimeSelectItem> list = buildStartEndTime(1614834000000L, end);        for (TimeSelectItem item : list ) {            System.out.println(item);        }    }}
(0)

相关推荐

  • Java基础之:日期类

    Java基础之:日期类 第一代日期类 Date:精确到毫秒,代表特定的瞬间 SimpleDateFormat:格式和解析日期的类 SimpleDateFormat 是一个以与语言环境有关的方式来格式化 ...

  • 开始使用 java8 的日期工具类

    例如,现有的类(例如java.util.Date和SimpleDateFormatter)不是线程安全的,这会导致用户潜在的并发问题.而新的LocalDate.LocalDateTime.DateTi ...

  • Java常用的时间工具类DateTimeUtils.java对常用的时间操作方法总结

    话不多少,直接上代码!时间工具类DateTimeUtils.java 汇总了以前常用的时间操作方法! 工具类方法列表: 将 String 转为 date 日期字符串格式化输出 Date类型日期转字符串 ...

  • Java时间转换,日期格式化使用 YYYY-MM-dd 的潜在问题

    日期格式化使用 YYYY-MM-dd 的潜在问题 测试逻辑: 创建两个日期格式化,一个是出问题的YYYY-MM-dd,另一个是正确用法yyyy-MM-dd 分别去格式化两个不同的日期:2020年12月 ...

  • 烫发染发同步操作与间隔一段时间操作有区别吗?

    过年前人们都说扎堆做头发,烫染这种事情本就特别费时间,每个五六个小时是做不完的.烫发染发同步操作与间隔操作,对头发的损伤程度是一样的.理论上讲烫发后间隔两周染发.染发后间隔四周烫发比较科学,这是担心烫 ...

  • 2021Java笔试题:java时间格式类型

    一.不少培训班候选人的简历中,缺乏足够的商业项目年限 不少同学会有这样得感受:投出去不少简历,但就没有面试机会,对培训班出身的朋友来说,可能这种情况更甚,原因是,这些简历没过筛选. 没过筛选的原因无非 ...

  • (3条消息) java poi操作word转pdf

    替换word文档内容 package com.docx.test; import org.apache.poi.xwpf.usermodel.*; import org.junit.Test; imp ...

  • (11条消息) impala java api 操作

    前言 上次的impala状况出现后,决定自己做一套impala的管理系统,那么首先面临的一个问题就是获取impala的各种状态,比如任务执行状态.经过一天多的尝试,总结一下. hue:可以使用hue的 ...

  • HBase常用的JAVA API操作

    为了方便以后查看,总结了一些常用的java操作hbase的代码: package com.mcq;import static org.hamcrest.CoreMatchers.describedAs ...

  • MongoDB Java API操作很全的整理以及共享分片模式下的常见操作整理

    MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写,一般生产上建议以共享分片的形式来部署. 但是MongoDB官方也提供了其它语言的客户端操作API.如下图所示: 提供了C.C++ ...

  • 桃树夏季修剪的时间、方法与操作

    在上篇文章中,农技小背篓给大家简单介绍了桃树的疏果与定果管理,以及桃树在不同品种.不同树龄.不同树势情况下留果量的确定,这篇文章呢,农技小背篓再给大家说一下桃树的夏季修剪(如疏枝.摘心.回缩.化控等) ...

  • 怎样减少操作中国平安的时间成本?

    中国平安无底线下跌,跌破70元已是大概率,逐步向前底62.68元靠近,风险释放已近尾声,从概率与赔率已进入操作阶段 那么现在时点怎么办?已有的投资者可在股价间距15%的时候加仓,没有的可建仓,但二者均 ...