点赞再看,养成习惯,公众号搜一搜【一角钱技术】关注更多原创技术文章。本文 GitHub org_hejianhui/JavaStudy 已收录,有个人系列文章。html
上一周在作一个产品的需求的时候有个动态计算时间段(如如今是13:00,则时间段为15:10-17:十、17:10-19:十、19:10-21:10;即最先的出发时间为当前时间+参数【2h10min】,最迟的时间段为开始时间在20点前结束时间在20点后的时间段),期间大量使用到了日期时间类库,本着熟悉日期时间类库才有了这篇文章,文章最后我会把我如何实现的这个需求的一个算法贴出来。java
咱们在使用Java8以前的类库时,都会在处理日期-时间的时候老是不爽,这其中包括且不限于如下的槽点:git
在Java 1.0版本中,对时间、日期的操做彻底依赖于 java.util.Data 类,只能以毫秒的精度表示时间,没法表示日期。github
在Java1.1 版本中,废弃了不少Date 类中的不少方法,而且新增了 java.util.Calendar。可是与Date相同,Calendar 类也有相似的问题和设计缺陷,致使在使用这些类写出的代码也很容易出错。算法
因为 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;
@Override
public 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 FieldDelegate
private 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();
}
}
}
}
复制代码
Joda-Time 是Joda提供的一个遵循Apache2.0 开源协议的 JDK之外的优质日期和时间开发库。express
Joda除Joda-Time以外的项目有Joda-Money、Joda-Beans、Joda-Convert、Joda-Collect Joda官网api
getYear()
和 得到星期 中的天 getDayOfWeek()
。Chronology
类实现多个可插拔的日历系统。正由于Joda-Time 与 Java8 以前的时间类库相比,具有了如此多的优势,因此 Joda-Time 成为事实上的标准的Java日期和时间库。安全
互操做性是指:Joda 类可以生成 java.util.Date 的实例(以及Calendar),这可让咱们保留现有对JDK的依赖,又可以使用Joda处理复杂的日期/时间计算。markdown
Date date = new Date();
DateTime dateTime = new DateTime(date);
复制代码
Calendar calendar = Calendar.getInstance();
DateTime dateTime = new DateTime(calendar);
复制代码
Date date = new Date();
DateTime dateTime = new DateTime(date);
Date date2 = dateTime.toDate();
复制代码
Calendar calendar = Calendar.getInstance();
dateTime = new DateTime(calendar);
Calendar calendar2 = dateTime.toCalendar(Locale.CHINA);
复制代码
Joda 使用了如下概念,使得它们能够应用到任何日期/时间库:
Joda-Time与java.lang.String相似,它们的实例均没法修改(由于任意对其值改变的操做都会生成新的对象),这也表明了它们是线程安全的。
如接口 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 类继承图以下:
瞬时性表达的是与epoch相对的时间上的一个精确时刻,而一个局部时间指的是一个时间的一部分片断,其能够经过一些方法使得时间产生变更(本质上仍是生成了新的类),这样能够把它当作重复周期中的一点,用到多个地方。
Joda-Time的设计核心就是年表(org.joda.time.Chronology),从根本上将,年表是一种日历系统,是一种计算时间的特殊方式,而且在其中执行日历算法的框架。Joda-Time支持的8种年表以下所示:
以上的每一种年表均可以做为特定日历系统的计算引擎,是可插拔的实现。
具体定义详见百科解释,在实际编码过程当中任何严格的时间计算都必须涉及时区(或者相对于GMT),Joda-Time中对应的核心类为org.joda.time.DateTimeZone,虽然平常的使用过程当中,并未涉及到对时区的操做,可是DateTimeZone如何对DateTime产生影响是比较值得注意的,此处不进行赘述。
上面介绍我完了Joda-Time的一些概念,接下来具体使用咱们来进行说明:
// 1.使用系统时间
DateTime dateTime1 = new DateTime();
// 2.使用jdk中的date
Date jdkDate1 = new Date();
DateTime dateTime2 = new DateTime(jdkDate1);
// 3.使用毫秒数指定
Date jdkDate2 = new Date();
long millis = jdkDate.getTime();
DateTime dateTime3 = new DateTime(millis);
// 4.使用Calendar
Calendar calendar = Calendar.getInstance();
DateTime dateTime4 = new DateTime(calendar);
// 5.使用多个字段指定一个瞬间时刻(局部时间片断)
// year month day hour(midnight is zero) minute second milliseconds
DateTime dateTime5 = new DateTime(2000, 1, 1, 0, 0, 0, 0);
// 6.由一个DateTime生成另外一个DateTime
DateTime dateTime6 = new DateTime(dateTime1);
// 7.有时间字符串生成DateTime
String timeString = "2019-01-01T00:00:00-06:00";
DateTime dateTime7 = DateTime.parse(timeString);
复制代码
当程序中处理的日期、时间并不须要是完整时刻的时候,能够建立一个局部时间,好比只但愿专一于年/月/日, 或者一天中的时间,或者是一周中的某天。Joda-Time中有表示这些时间的是org.joda.time.ReadablePartial接口,实现它的两个类LocalDate和LocalTime是分别用来表示年/月/日和一天中的某个时间的。
// 显示地提供所含的每一个字段
LocalDate localDate = new LocalDate(2019, 1, 1);
// 6:30:06 PM
LocalTime localTime = new LocalTime(18, 30, 6, 0);
复制代码
LocalDate是替代了早期Joda-Time版本中使用的org.joda.time.YearMonthDay,LocalTime是替代早期版本的org.joda.time.TimeOfDay。(均已被标注为过期状态)。
Joda-Time提供了三个类用于表示时间跨度(在某些业务需求中,它们可能会很是有用)。
这个类表示以毫秒为单位的绝对精度,提供标准数学转换的方法,同时把时间跨度转换为标准单位。
这个类表示以年月日单位表示。
这个类表示一个特定的时间跨度,使用一个明确的时刻界定这段时间跨度的范围。Interval 为半开 区间,因此由其封装的时间跨度包括这段时间的起始时刻,可是不包含结束时刻。
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实例上增长或减小一段时间后的实例
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实例更新指定日期单位后的实例
判断DateTime对象大小状态的一些操做方法
// 传入的格式化模板只需与JDK SimpleDateFormat兼容的格式字符串便可
public static String convert(Date date,String dateFormat){
return new DateTime(date).toString(dateFormat);
}
// 将JDK中的Date转化为UTC时区的DateTime
DateTime dateTime = new DateTime(new Date(), DateTimeZone.UTC);
// 将String转换为DateTime
public static Date convertUTC2Date(String utcDate){
DateTime dateTime =DateTime.parse(utcDate, DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
return dateTime.toDate();
}
复制代码
更多使用方法请参考官方文档。
因为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(固然,它们之间也存在些许差异须要注意到)。
首先是LocalDate,该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。
// 使用指定的日期建立LocalDate
LocalDate 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的替代品。
// 直接建立LocalDateTime
LocalDateTime 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或者LocalTime
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
复制代码
Instant类是为了方便计算机理解的而设计的,它表示一个持续时间段上某个点的单一大整型数,实际上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算(最小计算单位为纳秒)。
// 传递一个秒数已建立该类的实例
Instant.ofEpochSecond(3);
// 传递一个秒数+纳秒 2 秒以后再加上100万纳秒(1秒)
Instant.ofEpochSecond(2, 1_000_000_000);
复制代码
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);
复制代码
// 直接使用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接口。
向重载的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());
复制代码
平常工做中,格式化以及解析日期-时间对象是另外一个很是重要的功能,而新的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);
复制代码
在新的日期-时间类库中,为了最大程度上的减小在处理时区带来的繁琐和复杂而使用了新的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);
复制代码
Joda-Time 简介 Joda Time项目和java8时间api
需求:如如今是13:00,则时间段为15:10-17:十、17:10-19:十、19:10-21:10;即最先的出发时间为当前时间+参数【2h10min】,最迟的时间段为开始时间在20点前结束时间在20点后的时间段),求解共有多少个时间段?
分析:
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);
}
}
}
复制代码
文章持续更新,能够公众号搜一搜「 一角钱技术 」第一时间阅读, 本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。