點贊再看,養成習慣,公衆號搜一搜【一角錢技術】關注更多原創技術文章。本文 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。