团队和做的直观图_直观,可靠的日期和时间处理,终于出现在Java中
團隊和做的直觀圖
日期和時間的概念是許多應(yīng)用程序的基礎(chǔ)。 諸如生日,租期,活動時間戳記和商店營業(yè)時間之類的東西都是基于日期和時間的,但是Java SE沒有很好的API來處理它們。 使用Java SE 8,有一組新的程序包-java.time-提供了結(jié)構(gòu)良好的API以涵蓋日期和時間。
歷史
當(dāng)Java在1.0版中首次啟動時,對日期和時間的唯一支持是java.util.Date類。 大多數(shù)開發(fā)人員注意到該類的第一件事是它不代表“日期”。 它所做的表示其實原因很簡單-在時間點瞬時基于精確到毫秒,從1970-01-01Z的時代測量。 但是,由于標(biāo)準(zhǔn)的toString()表單會在JVM的默認(rèn)時區(qū)中顯示日期和時間,因此某些開發(fā)人員錯誤地認(rèn)為它是可識別時區(qū)的類。
當(dāng)需要改進1.1版時,Date類被認(rèn)為無法修復(fù)。 結(jié)果,添加了新的API- java.util.Calendar 。 可悲的是,Calendar類并沒有比java.util.Date更好。 這些類面臨的一些問題是:
- 可變的。 諸如日期和時間之類的類應(yīng)該是不變的。
- 抵消。 日期中的年份從1900開始,兩個類別中的月份都從零開始。
- 命名。 日期不是“日期”。 日歷不是“日歷”。
- 格式化。 格式化程序僅適用于Date ,不適用于Calendar ,并且不是線程安全的
在2001年左右, Joda-Time項目開始了。 Joda-Time的目的很簡單-為Java提供高質(zhì)量的日期和時間庫。 花費了一段時間,但最終推出了Joda-Time 1.0版,它成為一個非常廣泛使用和流行的庫。 隨著時間的流逝,提供JDK之類的Joda-Time這樣的庫的需求不斷增長。 在來自巴西的Michael Nascimento Santos的幫助下,JSR-310開始了,正式的過程是為JDK創(chuàng)建和集成新的日期和時間API。
總覽
新的java.time API包含5個軟件包:
- java.time-包含值對象的基本包
- java.time.chrono-提供對不同日歷系統(tǒng)的訪問
- java.time.format-允許格式化和解析日期和時間
- java.time.temporal-低級框架和擴展功能
- java.time.zone-時區(qū)支持類
大多數(shù)開發(fā)人員將主要使用基本和格式包,以及臨時包。 因此,盡管有68種新的公共類型,但是大多數(shù)開發(fā)人員只會積極使用其中的三分之一。
日期
LocalDate類是新API中最重要的類之一。 它是表示日期的不可變值類型。 沒有時間或時區(qū)的表示。
Joda-Time熟悉“本地”術(shù)語,該術(shù)語最初來自ISO-8601日期和時間標(biāo)準(zhǔn) 。 它特別涉及到缺少時區(qū)。 實際上,本地日期是對日期的描述,例如“ 2014年4月5日”。 該特定的本地日期將根據(jù)您在地球上的位置在時間軸上的不同點開始。 因此,本地日期將從澳大利亞在倫敦開始的10個小時開始,在舊金山開始的18小時開始。
LocalDate類旨在具有所有通常需要的方法:
LocalDate date = LocalDate.of(2014, Month.JUNE, 10); int year = date.getYear(); // 2014 Month month = date.getMonth(); // JUNE int dom = date.getDayOfMonth(); // 10 DayOfWeek dow = date.getDayOfWeek(); // Tuesday int len = date.lengthOfMonth(); // 30 (days in June) boolean leap = date.isLeapYear(); // false (not a leap year)在示例中,我們看到一個使用工廠方法創(chuàng)建的日期(所有構(gòu)造函數(shù)都是私有的)。 然后查詢一些基本信息。 請注意, Month和DayOfWeek枚舉旨在使代碼更具可讀性和可靠性。
在下一個示例中,我們將了解如何操作實例。 由于該類是不可變的,因此每次操作都會導(dǎo)致一個新實例,而原始實例不受影響。
LocalDate date = LocalDate.of(2014, Month.JUNE, 10); date = date.withYear(2015); // 2015-06-10 date = date.plusMonths(2); // 2015-08-10 date = date.minusDays(1); // 2015-08-09這些更改相對簡單,但是通常需要對日期進行更復(fù)雜的更改。 該java.time API包括一個機制來處理這一點- TemporalAdjuster。 時間調(diào)整器背后的想法是提供一種預(yù)包裝的實用程序,該實用程序能夠處理日期,例如獲取與該月的最后一天相對應(yīng)的實例。 API中提供了常用的,但您可以添加自己的。 使用調(diào)節(jié)器非常簡單,但是可以從靜態(tài)導(dǎo)入中受益:
import static java.time.DayOfWeek.* import static java.time.temporal.TemporalAdjusters.* LocalDate date = LocalDate.of(2014, Month.JUNE, 10); date = date.with(lastDayOfMonth()); date = date.with(nextOrSame(WEDNESDAY));看到正在使用調(diào)節(jié)器的立即React通常是對代碼與預(yù)期業(yè)務(wù)邏輯的接近程度的評價。 實現(xiàn)日期和時間業(yè)務(wù)邏輯非常重要。 我們要看的最后一件事是大量的日期手動操作。 如果您要在代碼庫中執(zhí)行多次常規(guī)操作,請考慮編寫一次自己的調(diào)節(jié)器,并讓您的團隊將其作為預(yù)先編寫的,經(jīng)過預(yù)先測試的組件來使用。
日期和時間為值
值得花點時間考慮一下是什么使LocalDate類成為一個值。 值是簡單的數(shù)據(jù)類型,其中兩個相等的實例完全可以替換-對象標(biāo)識沒有實際含義。 String類是值的典范示例-我們通過equals()關(guān)心兩個字符串是否為真,而在==時我們不在乎它們是否相同。
大多數(shù)日期和時間類也應(yīng)該是值,并且java.time API滿足了這一期望。 因此,從來沒有充分的理由使用==比較兩個LocalDate實例,實際上Javadoc建議不要這樣做。
對于那些想了解更多信息的人,請參閱我最近對VALJO的定義,該定義為Java中的值對象定義了一組嚴(yán)格的規(guī)則,包括不可變性,工廠方法以及equals,hashCode,toString和compareTo的良好定義。
備用日歷系統(tǒng)
與java.time中的所有主要日期和時間類一樣, LocalDate類固定于單個日歷系統(tǒng)-ISO-8601標(biāo)準(zhǔn)中定義的日歷系統(tǒng)。
在ISO-8601日歷系統(tǒng)是事實上的世界歷法系統(tǒng),也描述為proleptic公歷。 標(biāo)準(zhǔn)年為365天,leap年為366天。 年每4年發(fā)生一次,但不是每100年發(fā)生一次,除非可以被400整除。為進行一致的計算,第一年的前一年被視為零年。
使用此日歷系統(tǒng)作為默認(rèn)日歷的第一個影響是日期不一定與GregorianCalendar的結(jié)果兼容。 在GregorianCalendar中 ,有來自割接儒略歷系統(tǒng)的陽歷其中15日發(fā)生在默認(rèn)情況下1582年十月在該日期之前一個,儒略歷被使用,其中有一個閏年每4年沒有失敗。 在該日期之后,將使用公歷,它具有我們今天使用的更復(fù)雜的leap年系統(tǒng)。
鑒于日歷系統(tǒng)的這種變化是歷史事實,為什么新的java.time API不能對其建模? 原因是,今天大多數(shù)使用此類歷史日期的Java應(yīng)用程序都是不正確的,因此繼續(xù)下去將是一個錯誤。 原因是,盡管羅馬的梵蒂岡城于1582年10月15日更改了日歷系統(tǒng),但世界上大多數(shù)其他國家并沒有這樣做 。 特別是大英帝國,包括早期的美國,直到近200年后才于1752年9月14日改變。 俄羅斯直到1918年2月14日才改變,而瑞典的改變尤其混亂。 因此,事實上,1918年之前的日期的含義很容易被解釋,并且對格里高利日歷的一次轉(zhuǎn)換的信念是錯誤的。 因此,選擇LocalDate不具有切換功能是一個非常合理的選擇。 應(yīng)用程序需要其他上下文信息,才能準(zhǔn)確地解釋儒略歷和公歷之間的特定歷史日期。
在所有主要類別中都集中使用ISO-8601事實上的世界日歷系統(tǒng)的第二個影響是需要一組額外的類來處理其他日歷系統(tǒng)。 Chronology界面是備用日歷系統(tǒng)的主要入口點,允許您按語言環(huán)境名稱查找它們。 Java SE 8還提供了其他四個日歷系統(tǒng)-泰國佛教徒,Minguo,日語和Hirah。 其他日歷系統(tǒng)可以由應(yīng)用程序提供。
每個日歷系統(tǒng)都有一個專用的日期類,因此有ThaiBuddhistDate , MinguoDate,JapaneseDate和HijrahDate 。 如果構(gòu)建高度本地化的應(yīng)用程序(例如日本政府的應(yīng)用程序),則使用這些文件。 額外的接口ChronoLocalDate用作這四個接口的基本抽象,加上LocalDate,可在不知道其運行的日歷系統(tǒng)的情況下編寫代碼。 盡管存在這種抽象,但意圖是很少使用它。
理解為什么很少使用抽象對于正確使用整個java.time API 至關(guān)重要 。 事實是,當(dāng)檢查當(dāng)今的應(yīng)用程序時,大多數(shù)嘗試以日歷系統(tǒng)中立方式運行的代碼都被破壞了。 例如,您不能假設(shè)一年中有12個月,而開發(fā)人員卻假設(shè)他們已經(jīng)添加了整整一年就添加12個月。 您不能假設(shè)所有月份的長度大致相同-科普特日歷系統(tǒng)有12個月的30天和1個月的5或6天。 您也不能假設(shè)下一年的數(shù)字比當(dāng)前年份大,因為日本皇帝改變時,日歷會像日本重新啟動年份一樣編號,通常是年中(您甚至不能假設(shè)同一天有兩天月份是同一年!)。
在日歷系統(tǒng)中立的方式下跨大型應(yīng)用程序編寫代碼的唯一方法是進行繁瑣的代碼審查,其中要仔細(xì)檢查每一行日期和時間代碼,以免對ISO日歷系統(tǒng)產(chǎn)生偏見。 因此, java.time的推薦用法是在整個應(yīng)用程序中使用LocalDate ,包括所有存儲,操縱和解釋業(yè)務(wù)規(guī)則。 唯一應(yīng)使用ChronoLocalDate的時間是在本地化輸入或輸出時,通常是通過將用戶首選的日歷系統(tǒng)存儲在用戶配置文件中來實現(xiàn)的,即使那樣,大多數(shù)應(yīng)用程序?qū)嶋H上并不需要該本地化級別。
有關(guān)此領(lǐng)域的完整原理,請參閱ChronoLocalDate的Javadoc 。
一天中的時間
超越日期,下一個要考慮的概念是Local-of-day,以LocalTime表示。 典型的例子可能是代表便利店的營業(yè)時間,例如從07:00到23:00(從早上7點到晚上11點)。 這樣的商店可能會在美國全境的那幾個小時營業(yè),但當(dāng)?shù)貢r間忽略了時區(qū)。
LocalTime是一種沒有關(guān)聯(lián)的日期或時區(qū)的值類型。 當(dāng)增加或減少時間量時,它將在午夜左右結(jié)束。 因此,20:00加6小時得出02:00。
使用LocalTime類似于使用LocalDate :
LocalTime time = LocalTime.of(20, 30); int hour = date.getHour(); // 20 int minute = date.getMinute(); // 30 time = time.withSecond(6); // 20:30:06 time = time.plusMinutes(3); // 20:33:06調(diào)整器機制也可以與LocalTime一起使用,但是調(diào)用它的時間的復(fù)雜處理較少。
合并日期和時間
下一個要考慮的類是LocalDateTime 。 此值類型是LocalDate和LocalTime的簡單組合。 它代表沒有時區(qū)的日期和時間。
LocalDateTime可以直接創(chuàng)建,也可以通過組合日期和時間來創(chuàng)建:
LocalDateTime dt1 = LocalDateTime.of(2014, Month.JUNE, 10, 20, 30); LocalDateTime dt2 = LocalDateTime.of(date, time); LocalDateTime dt3 = date.atTime(20, 30); LocalDateTime dt4 = date.atTime(time);第三個和第四個選項使用atTime() ,它提供了一種建立日期時間的流暢方法。 提供的大多數(shù)日期和時間類都具有“ at”方法,可以用這種方法將您擁有的對象與另一個對象結(jié)合起來以形成一個更復(fù)雜的對象。
LocalDateTime上的其他方法類似于LocalDate和LocalTime的方法 。 這種熟悉的方法模式對于幫助學(xué)習(xí)API很有用。 下表總結(jié)了所使用的方法前綴:
字首 | 描述 |
的 | 從組成部分創(chuàng)建實例的靜態(tài)工廠方法。 |
從 | 嘗試從相似對象中提取實例的靜態(tài)工廠方法。 from()方法的類型安全性不如of()方法。 |
現(xiàn)在 | 在當(dāng)前時間獲取實例的靜態(tài)工廠方法。 |
解析 | 靜態(tài)工廠方法,允許將字符串解析為對象的實例。 |
得到 | 獲取日期時間對象的部分狀態(tài)。 |
是 | 檢查日期時間對象是否為真。 |
與 | 返回日期時間對象的副本,其中部分狀態(tài)已更改。 |
加 | 返回添加了一定時間的日期時間對象的副本。 |
減去 | 返回減去時間量的日期時間對象的副本。 |
至 | 將此日期時間對象轉(zhuǎn)換為另一個,可以表示原始對象的部分或全部狀態(tài)。 |
在 | 將此日期時間對象與其他數(shù)據(jù)組合以創(chuàng)建更大或更復(fù)雜的日期時間對象。 |
格式 | 提供格式化該日期時間對象的功能。 |
瞬間
在處理日期和時間時,我們通常以年,月,日,小時,分鐘和秒為單位進行考慮。 但是,這只是時間的一種模式,我稱之為“人類”。 第二個通用模型是“機器”或“連續(xù)”時間。 在此模型中,時間軸上的一個點由單個大數(shù)字表示。 這種方法很容易讓計算機處理,并且從1970年的UNIX秒計數(shù)中可以看出,與Java中的1970年的毫秒計數(shù)相匹配。
java.time API通過Instant值類型提供時間的機器視圖。 它提供了在沒有任何其他上下文信息(例如時區(qū))的情況下表示時間線上的點的功能。 從概念上講,它僅表示自1970年(UTC 1970年1月1日開始的午夜)以來的秒數(shù)。 由于API基于納秒,因此Instant類提供了將精度存儲到納秒的能力。
Instant start = Instant.now(); // perform some calculation Instant end = Instant.now(); assert end.isAfter(start);Instant類通常用于存儲和比較時間戳,您需要在其中記錄事件發(fā)生的時間,而無需記錄有關(guān)事件發(fā)生的時區(qū)的任何信息。
在許多方面,Instant有趣的方面是您無法使用它,而不是您可以做什么。 例如,這些代碼行將引發(fā)異常:
instant.get(ChronoField.MONTH_OF_YEAR); instant.plus(6, ChronoUnit.YEARS);它們會引發(fā)異常,因為Instant僅包含數(shù)秒和十億分之一秒的時間,并且無法處理對人類有意義的單位。 如果需要此功能,則需要提供時區(qū)信息。
時區(qū)
英國引入了時區(qū)的概念, 當(dāng)時鐵路的發(fā)明以及通信方面的其他改進使人們突然可以覆蓋遠(yuǎn)處,因為太陽時間的變化很重要。 直到那時,每個村莊和城鎮(zhèn)都有自己的基于太陽的時間定義,通常以日d為基準(zhǔn)。
最初在英國布里斯托(Bristol)交易所大樓上的時鐘照片顯示了這種混淆的一個例子。 紅手顯示格林威治標(biāo)準(zhǔn)時間,黑手顯示布里斯托爾時間,相差10分鐘:
在技??術(shù)的推動下,標(biāo)準(zhǔn)時區(qū)系統(tǒng)得到了發(fā)展,取代了較早的當(dāng)?shù)靥枙r。 但是關(guān)鍵事實是時區(qū)是政治創(chuàng)造。 它們通常用于展示對該地區(qū)的政治控制,例如克里米亞到莫斯科時期的最新變化。 與任何政治事物一樣,相關(guān)規(guī)則經(jīng)常違反邏輯。 規(guī)則可以而且確實會在很少通知的情況下進行更改。
時區(qū)規(guī)則由發(fā)布IANA時區(qū)數(shù)據(jù)庫的國際組織收集和收集。 這組數(shù)據(jù)包含地球上每個區(qū)域的標(biāo)識符以及該區(qū)域時區(qū)變化的歷史。 標(biāo)識符的格式為“歐洲/倫敦”或“美國/紐約”。
在java.time API之前,您使用TimeZone類來表示時區(qū)。 現(xiàn)在,您使用ZoneId類。 有兩個主要區(qū)別。 首先, ZoneId是不可變的,它提供了將實例存儲在靜態(tài)變量中的功能。 其次,實際規(guī)則本身在ZoneRules舉行,而不是在本身了zoneid,只需撥打了zoneid getRules()獲得的規(guī)則。
時區(qū)的一種常見情況是與UTC /格林威治時間的固定偏移量。 在談?wù)摃r差時,通常會遇到這種情況,例如紐約比倫敦落后5小時。 ZoneOffset類是ZoneId的子類, 它代表倫敦格林威治的零子午線的時間偏移。
作為開發(fā)人員,最好不必處理時區(qū)及其復(fù)雜性。 java.time API允許您盡可能地做到這一點。 盡可能使用LocalDate , LocalTime , LocalDateTime和Instant類。 當(dāng)您無法避免時區(qū)時, ZonedDateTime類將處理該要求。
ZonedDateTime類管理從人類時間線(在桌面日歷和掛鐘上看到)到機器時間線(以秒為單位)的轉(zhuǎn)換。 這樣,您可以從本地類或即時實例創(chuàng)建ZonedDateTime:
ZoneId zone = ZoneId.of("Europe/Paris"); LocalDate date = LocalDate.of(2014, Month.JUNE, 10); ZonedDateTime zdt1 = date.atStartOfDay(zone); Instant instant = Instant.now(); ZonedDateTime zdt2 = instant.atZone(zone);時區(qū)最煩人的部分之一是夏令時(DST)。 使用DST,與格林威治的偏移量每年更改兩次(或多次),通常在Spring前進,在秋季/秋季前進。 進行這些調(diào)整后,我們所有人都必須更改房屋周圍點綴的掛鐘。 這些更改由java.time稱為偏移量轉(zhuǎn)換 。 在Spring,本地時間軸上存在一個“間隙”,其中一些本地時間不會發(fā)生。 相比之下,在秋天/秋天,當(dāng)某些本地時間出現(xiàn)兩次時會出現(xiàn)“重疊”。
ZonedDateTime類使用其工廠方法和操作方法進行處理。 例如,添加一天將添加一個邏輯日,如果超過了DST邊界,則可能會大于或小于24小時。 同樣,方法atStartOfDay()之所以如此命名,是因為您不能假設(shè)結(jié)果時間將是午夜-從午夜到凌晨1點可能存在DST間隙。
關(guān)于DST的最后一個技巧。 如果您想證明您已經(jīng)考慮過DST重疊(同一本地時間發(fā)生兩次)應(yīng)該發(fā)生什么,則可以使用專用于處理重疊的兩種特殊方法之一:
zdt = zdt.withEarlierOffsetAtOverlap(); zdt = zdt.withLaterOffsetAtOverlap();如果對象與DST重疊,則使用這兩種方法之一將選擇較早或較晚的時間。 在所有其他情況下,這些方法將無效。
時間量
到目前為止討論的日期和時間類別以各種方式代表了時間軸上的點。 為時間量提供了兩個附加值類型。
Duration類表示以秒和納秒為單位的時間量。 例如,“ 23.6秒”。
Period類代表以年,月和日為單位的時間量。 例如,“ 3年2個月零6天”。
這些可以添加到主要日期和時間類別中,也可以從其中刪除:
Period sixMonths = Period.ofMonths(6); LocalDate date = LocalDate.now(); LocalDate future = date.plus(sixMonths);格式化和解析
整個軟件包專用于格式化和打印日期和時間-java.time.format 。 該軟件包圍繞DateTimeFormatter及其關(guān)聯(lián)的生成器DateTimeFormatterBuilder展開 。
創(chuàng)建格式化程序的最常見方法是DateTimeFormatter上的靜態(tài)方法和常量。 這些包括:
- 通用 ISO格式的常量,例如ISO_LOCAL_DATE 。
- 模式字母,例如ofPattern(“ dd / MM / uuuu” )。
- 本地化的樣式,例如ofLocalizedDate(FormatStyle.MEDIUM )。
擁有格式化程序后,通常可以通過將其傳遞給主要日期和時間類的相關(guān)方法來使用它:
DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/uuuu"); LocalDate date = LocalDate.parse("24/06/2014", f); String str = date.format(f);這樣,您就可以與格式化程序本身上的format和parse方法隔離。
如果需要控制格式化的語言環(huán)境,請在格式化程序上使用withLocale(Locale)方法。 類似的方法可以控制日歷系統(tǒng),時區(qū),十進制編號系統(tǒng)和解析的分辨率。
如果需要更多控制,請參見DateTimeFormatterBuilder類,該類允許逐步建立復(fù)雜的格式。 它還具有格式化程序不區(qū)分大小寫的解析,寬松的解析,填充和可選部分的功能。
摘要
java.time API是Java SE 8中用于日期和時間的新的全面模型。它將在Joda-Time中開始的想法和實現(xiàn)提高到一個新的水平,并最終允許開發(fā)人員將java.util.Date和Calendar留在后面。 絕對可以再次享受日期和時間編程的時間!
- Oracle 官方教程
- 非官方項目主頁
- ThreeTen-Extra項目 ,帶有補充核心Java SE的其他類
翻譯自: https://www.infoq.com/articles/java.time?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
團隊和做的直觀圖
總結(jié)
以上是生活随笔為你收集整理的团队和做的直观图_直观,可靠的日期和时间处理,终于出现在Java中的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用派生类实现统一接口解决三种基础排序问
- 下一篇: Java小白入门200例54之打印水仙花