Java 日期類
Java 的 API 提供了很多有用的元件,能幫我們構建複雜的應用。比如日期處理,Java 從 1.0,就提供了 java.util.Date
類用於支援日期和時間的處理,不過由於該 API 設計的缺陷,產生了糟糕的易用性。隨著 1.0 退出舞臺,Date 類中的很多方法都被廢棄了,Java 1.1 使用 java.util.Calendar
類取而代之,很不幸,Calendar 類也有類似的問題和設計缺陷,導致使用這些方法寫出的程式碼非常容易出錯。
所有這些缺陷和不一致導致使用者們轉投第三方的日期和時間庫,比如 Joda-Time
。為了解決這些問題,Oracle 決定在原生的 Java API 中提供高質量的日期和時間支援。你會看到 Java 8 在 java.time
在開始介紹日期類之前,我們先陳述幾個經常出現的名次:
UTC
協調世界時,是最主要的世界時間標準,基於原子鐘。
在 UTC 中,大約每一年或兩年會多出一秒,稱為“閏秒”,新增到一天的最後一秒,並且總是在 12 月 31 日或 6 月 30 日。
GMT
格林尼治標準時間,代表時區,標準時間為 UTC+0,是之前的民間標準,根據地球的自轉和公轉來計算時間,在歐洲國家和非洲國家用做當地標準時間。
GMT 因為是根據地球的轉動來計算時間的,而地球的自轉目前正在緩速變慢,所以 UTC 比 GMT 更加精準。但是一些計算機的標準是根據格林尼治標準時間(GMT)定義的,所以大多數計算機時鐘不夠精確。
在民用領域可以認為兩者相同。
CST
北美中部時區,代表時區,標準時間為 UTC-6。
這種縮寫的時區代表一片區域,但是由於沒有明確的標準,CST 概念來說在中國也代表中國標準時間。具體縮寫的定義可以檢視 ZoneId.SHORT_IDS 定義。
所以 java 提供了明確的時區表示方法,具體可用的時區字串 Id 可以通過 ZoneId.getAvailableZoneIds() 方法檢視。
ISO 8601
代表國際標準的日期和時間表示方法。
日期格式一般為:yyyy-MM-dd;
帶時區的日期一般格式為:yyyy-MM-dd’T’HH:mm:ss.SSSZ。
在 Java 中定義了一些參見的格式化表示方法,在
DateTimeFormatter 有具體的靜態格式化類表示。
UNIX 紀元(Epoch)
代表 1970 年 1 月 1 日 0 時 0 分 0 秒 GMT 午夜
Date
Date 類表示特定的時刻,精度為毫秒,該類反映的是協調世界時(UTC),不反映時區。
該類在使用時有幾點需要注意:
- 該時刻表示的當前時間與紀元的差(以毫秒為單位);
- 年代表與 1900 的差
- 一個月由 0 到 11 之間的整數表示;0 是一月,1 是二月,以此類推;
- 日期(一個月中的一天)由 1 到 31 之間的整數表示;
- 小時用 0 到 23 之間的的整數表示;
- 分鐘用 0 到 59 之間的整數表示;
- 秒用 0 到 61 之間的整數表示;值 60 和 61 僅在閏秒出現;
- 使用 SimpleDateFormat 格式化顯示,該類非執行緒安全。
- 該類是可變類,執行緒不安全。
使用
建立
建立當前時刻:new Date()
,相當於:new Date(System.currentTimeMillis())
,使用紀元時間與當前時間毫秒差建立日期。
使用 Java8 Instant 轉換:Date.from(Instant instant)
,內部還是使用紀元時間差,其它日期物件轉換思想相同。
日期
Date 實現了 Comparable
介面,可以使用 compareTo
直接比較兩個物件;
使用 after
和 before
例項方法;
文字間轉換
使用 DateFormat
抽象類靜態方法轉換簡單的 ISO 格式:
該抽象類非執行緒安全,由於使用了成員變數 calendar 維護內部時間狀態,導致 format 等方法依賴狀態。
Date date = new Date();
DateFormat dateInstance = DateFormat.getDateInstance();
String format = dateInstance.format(date);
Date parse = dateInstance.parse(format);
System.out.println(parse + " DateInstance format:" + format);
// Sun May 31 00:00:00 CST 2020 DateInstance format:2020-5-31
DateFormat dateTimeInstance = DateFormat.getDateTimeInstance();
String format1 = dateTimeInstance.format(date);
Date parse1 = dateInstance.parse(format1);
System.out.println(parse1 + " DateTimeInstance format:" + format1);
// Sun May 31 00:00:00 CST 2020 DateTimeInstance format:2020-5-31 21:04:03
DateFormat instance = DateFormat.getInstance();
String format2 = instance.format(date);
Date parse2 = instance.parse(format2);
System.out.println(parse2 + " Instance format:" + format2);
// Sun May 31 21:04:00 CST 2020 Instance format:20-5-31 下午9:04
DateFormat timeInstance = DateFormat.getTimeInstance();
String format3 = timeInstance.format(date);
Date parse3 = timeInstance.parse(format3);
System.out.println(parse3 + " TimeInstance format:" + format3);
// Thu Jan 01 21:04:03 CST 1970 TimeInstance format:21:04:03
或使用 DateFormat
實現類 SimpleDateFormat
基於 Pattern 形式轉換:
該類非執行緒安全,原因和 DateFormat 相同。
字元 | 日期或時間元素 | Presentation | Examples |
---|---|---|---|
G | Era 年代 | Text | AD |
y | 年 | Year | 1996; 96 |
Y | Week year | Year | 2009; 09 |
M | 一年中的月份 (上下文敏感) | Month | July; Jul; 07 |
L | 一年中的月份 (獨立格式) | Month | July; Jul; 07 |
w | 一年中的周 | Number | 27 |
W | 一月中的周 | Number | 2 |
D | 一年中的天 | Number | 189 |
d | 一月中的天 | Number | 10 |
F | 每月的星期幾 | Number | 2 |
E | Day name in week | Text | Tuesday; Tue |
u | 星期幾 (1 = 星期一, …, 7 = 星期天) | Number | 1 |
a | Am/pm 標記 | Text | PM |
H | 小時 (0-23) | Number | 0 |
k | 小時 (1-24) | Number | 24 |
K | 基於上午/下午表示的時間 am/pm (0-11) | Number | 0 |
h | 基於上午/下午表示的時間 am/pm (1-12) | Number | 12 |
m | 分鐘 | Number | 30 |
s | 秒 | Number | 55 |
S | 毫秒 | Number | 978 |
z | 時區 | 一般時區 | Pacific Standard Time; PST; GMT-08:00 |
Z | 時區 | RFC 822 時區 | -0800 |
X | 時區 | ISO 8601 時區 | -08; -0800; -08:00 |
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String format = simpleDateFormat.format(new Date());
Date parse = simpleDateFormat.parse(format);
Calendar
該類是一個抽象類,和 Date 一樣,代表特定的時刻,該類提供了一些操作日曆欄位的方法,比如獲得下一週的日期。
該類使用時需要注意一下幾點:
- 支援指定時區和區域構造日曆。最常見的實現是 GregorianCalendar,代表公曆;
- 該時刻表示的當前時間與紀元的差(以毫秒為單位);
- 年份不再從 1900 年開始,月份依舊從 0 開始計算;
- 沒有專門的文字格式轉換,通過 Date 作為中轉;
- 該類是可變類,執行緒不安全。
使用
建立
- 使用靜態工廠方法獲取基於系統時區的當前時刻:
Calendar rightNow = Calendar.getInstance();
- 使用靜態內部類構建器構建時間日曆:
new Calendar.Builder()
.setCalendarType("iso8601")
.setWeekDate(2013, 1, MONDAY)
.build();
Date
轉Calendar
final Calendar calendar = new Calendar.Builder().setInstant(d).build();
比較
該類同樣實現了 Comparable
介面,可以使用 compareTo
直接比較兩個物件;
使用 after
和 before
例項方法;
修改欄位
通過 set(int field, int value)
修改當前日曆物件欄位,第一個引數為欄位型別,定義在類的靜態常量中:
ERA
, YEAR
, MONTH
(JANUARY
, FEBRUARY
, MARCH
, APRIL
, MAY
, JUNE
, JULY
, AUGUST
, SEPTEMBER
, OCTOBER
, NOVEMBER
, DECEMBER
, UNDECIMBER
), WEEK_OF_YEAR
, WEEK_OF_MONTH
, DATE
, DAY_OF_MONTH
, DAY_OF_YEAR
, DAY_OF_WEEK
(SUNDAY
, MONDAY
, TUESDAY
, WEDNESDAY
, THURSDAY
, FRIDAY
, SATURDAY
), DAY_OF_WEEK_IN_MONTH
, AM_PM
, HOUR
, HOUR_OF_DAY
, MINUTE
, SECOND
, MILLISECOND
, ZONE_OFFSET
, DST_OFFSET
Calendar rightNow = Calendar.getInstance();
rightNow.set(Calendar.MONTH, Calendar.JANUARY);
或者 set(int year, int month, int date, int hourOfDay, int minute)
和 set(int year, int month, int date)
等。
增加或降低欄位值:add(int field, int amount)
;
滾動增加或減小某個單位值,不影響前一個欄位值:roll(int field, boolean up)
// create a calendar
Calendar cal = Calendar.getInstance();
// displays the current calendar
System.out.println("Month is " + cal.get(Calendar.MONTH));
// Month is 4
// roll month
cal.roll(Calendar.MONTH, true);
System.out.println("Month is " + cal.get(Calendar.MONTH));
// Month is 5
// roll downwards
cal.roll(Calendar.MONTH, false);
System.out.println("Month is " + cal.get(Calendar.MONTH));
// Month is 4
new package java.time
該包中定義的類代表基本的日期時間概念,包括瞬間,持續時間,日期,時間,時區和時間段。它們基於 ISO 日曆系統(公曆)。所有的類都是不可變的並且是執行緒安全的。
每個日期例項都是由方便獲取的欄位 API 組成。對於較低級別的欄位訪問,可以參考 java.time.temporal
包。每個類都支援列印和解析為各種日期和時間,具體參閱 java.time.format
包以獲取自定義選項。
java.time.chrono
包包含日曆中立 API ChronoLocalDate
,ChronoLocalDateTime
,ChronoZonedDateTime
和 Era
,這些類可以用來構建地區化的日曆系統,比如農曆等,系統內建提供了 4 中日曆系統,分別是:
ThaiBuddhistDate
:泰國佛教歷MinguoDate
:中華民國曆JapaneseDate
:日本歷HijrahDate
:伊斯蘭曆
package java.time.temporal
我們可以使用欄位、單位和日期時間調節器來訪問日期和時間。
該程式包在基本程式包上擴充套件,以提供更多功能來滿足更強大的用例。支援包括:
- 日期時間單位,例如年,月,日和小時
- 日期時間欄位,例如一年中的某月,一週中的某日或一天中的某小時
- 日期時間調整功能
- 周的不同定義
欄位和單位
日期和時間以欄位和單位表示。單位用於測量時間量,例如年,天或分鐘。所有單位都實現 TemporalUnit
。眾所周知的單位集在 ChronoUnit
中定義,例如 DAYS。該單位中也定義了一些常用的單位操作 API,例如:
- addTo(R temporal, long amount) - 新增指定時間段
- between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) - 比較兩個時間物件之間的時間
- isSupportedBy(Temporal temporal) - 指定時間物件是否支援此單位
欄位用於表示較大日期時間的一部分,例如年,一年中的月或一分鐘中的秒。所有欄位都實現 TemporalField
。在 ChronoField
中定義了一組眾所周知的欄位,例如 HOUR_OF_DAY。其他欄位由 JulianFields
,WeekFields
和 IsoFields
定義。該欄位也有一些常用的 API,例如:
- getFrom(TemporalAccessor temporal) - 從指定的時間物件獲取此欄位的值
- isSupportedBy(TemporalAccessor temporal) - 檢查時間物件是否支援此欄位
- range() - 獲取此欄位的有效範圍
欄位的一種用法是從時間物件中獲取沒有便利方法的欄位。例如,獲取月中的某天很常見,以至於 LocalDate
上有一個名為 getDayOfMonth()
的方法。但是,對於更特殊的欄位,必須使用該欄位。例如,獲取月中的週數,date.get(ChronoField.ALIGNED_WEEK_OF_MONTH)。
Temporal
作為支援欄位的日期時間型別的抽象。其方法支援獲取欄位的值、建立修改欄位值後的新日期時間以及查詢其他資訊(通常用於提取偏移量或時區)。
調整和查詢
日期時間問題中的關鍵部分是將日期調整為新的相關值,例如“每月的最後一天”或“下一個星期三”。這些被建模為調整基準日期時間的函式。該函式實現 TemporalAdjuster
並在 Temporal 上執行。TemporalAdjusters
中提供了一組常用功能。例如,要查詢給定日期後一週中某天的第一次出現,請使用 TemporalAdjusters.next(DayOfWeek),例如 date.with(next(MONDAY))。
TemporalAdjuster 函式介面有一些常見的靜態實現:
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 建立一個新的日期,它的值為同一個月中每一週的第幾天 |
firstDayOfMonth | 建立一個新的日期,它的值為當月的第一天 |
firstDayOfNextMonth | 建立一個新的日期,它的值為下月的第一天 |
firstDayOfNextYear | 建立一個新的日期,它的值為明年的第一天 |
firstDayOfYear | 建立一個新的日期,它的值為當年的第一天 |
firstInMonth | 建立一個新的日期,它的值為同一個月中,第一個符合星期幾要求的值 |
lastDayOfMonth | 建立一個新的日期,它的值為當月的最後一天 |
lastDayOfNextMonth | 建立一個新的日期,它的值為下月的最後一天 |
lastDayOfNextYear | 建立一個新的日期,它的值為明年的最後一天 |
lastDayOfYear | 建立一個新的日期,它的值為今年的最後一天 |
lastInMonth | 建立一個新的日期,它的值為同一個月中,最後一個符合星期幾要求的值 |
next/previous | 建立一個新的日期,並將其值設定為日期調整後或者調整前,第一個符合指定星期幾要求的日期 |
nextOrSame/previousOrSame | 建立一個新的日期,並將其值設定為日期調整後或者調整前,第一個符合指定星期幾要求的日期,如果該日期已經符合要求,直接返回該物件 |
TemporalAmount
介面模擬相對時間量,比如常用的實現類 Duration
,Period
。
除了調整日期時間外,還提供了一個介面以啟用通過 TemporalQuery
進行查詢。查詢介面的最常見實現是方法引用。可以使用主要時間物件類上的 from(TemporalAccessor)
方法,例如 LocalDate::from
或 Month::from
。在 TemporalQueries
中作為靜態方法提供了進一步的實現。
final ZonedDateTime now = ZonedDateTime.now();
// Now: 2020-06-11T00:00:39.096326+08:00[Asia/Shanghai]
System.out.format("Now: %s%n", now);
final LocalDate localDate = now.query(TemporalQueries.localDate());
final LocalDate toLocalDate = now.toLocalDate();
final LocalDate fromLocalDate = now.query(LocalDate::from);
// true
System.out.println(localDate.isEqual(toLocalDate) && localDate.isEqual(fromLocalDate));
final TemporalUnit unit = now.query(TemporalQueries.precision());
final Chronology chronology = now.query(TemporalQueries.chronology());
final ZoneId zone = now.query(TemporalQueries.zoneId());
final ZoneOffset offset = now.query(TemporalQueries.offset());
// TemporalUnit: Nanos, Chronology: ISO, ZoneId: Asia/Shanghai, ZoneOffset: +08:00
System.out.format("TemporalUnit: %s, Chronology: %s, ZoneId: %s, ZoneOffset: %s %n", unit, chronology, zone, offset);
final Month month = now.query(Month::from);
final MonthDay monthDay = now.query(MonthDay::from);
final YearMonth yearMonth = now.query(YearMonth::from);
final DayOfWeek dayOfWeek = now.query(DayOfWeek::from);
final ZoneId zoneId = now.query(ZoneId::from);
final ZoneOffset zoneOffset = now.query(ZoneOffset::from);
// Month: JUNE, MonthDay: --06-11, YearMonth: 2020-06, DayOfWeek: THURSDAY, zoneId: Asia/Shanghai, zoneOffset: +08:00
System.out.format("Month: %s, MonthDay: %s, YearMonth: %s, DayOfWeek: %s, zoneId: %s, zoneOffset: %s %n", month, monthDay, yearMonth, dayOfWeek, zoneId, zoneOffset);
周
不同的語言環境對星期有不同的定義。例如,在歐洲,一週通常從星期一開始,而在美國,則從星期日開始。 WeekFields
類為這種區別建模。IOS 定義星期一為一週開始,一週至少需要四天。
ISO 日曆系統定義了一個額外的基於周的年劃分。這定義了基於從一年整個星期一到星期一的一年,它是在 IsoFields
中建模的。
日期和時間
日期和時間類的介面都提供了一致的設計,所以再很多 API 上都做了通用的處理,比如一致的方法字首:
of
- 靜態工廠方法parse
- 側重於解析的靜態工廠方法get
- 獲取值或其它is
- 檢查是否為真with
- 等同於 setter 的不可變修改,返回新的副本plus
- 增加一定數量的單位minus
- 減少一定數量的單位to
- 將一個物件轉換為另一個物件at
- 將一個物件結合到該物件中,例如date.atTime(time)
這裡主要以 Insant 展開介紹。
Instant
Instant
實質上是一個數字時間戳。可以從時鐘(Clock
)中獲取當前的瞬時資訊。這對於時間點的記錄和持久化很有用,儲存的結果與 System.currentTimeMillis()
` 相關聯。
建立
從 Date
獲取:new Date().toInstant()
;
使用靜態工廠方法:
- 從系統時鐘(Clock)構建:
Instant.now()
,Instant.now(Clock clock)
- 根據紀元差毫秒構建構建:
Instant.ofEpochMilli(long epochMilli)
- 使用標準 ISO 格式字串構建:
Instant.parse(CharSequence text)
,格式例如:2007-12-03T10:15:30.00Z - 從 TemporalAccessor 實現類構建,該類可以為 Instant 或支援
INSTANT_SECONDS
和NANO_OF_SECOND
欄位的實現類,比如:ZonedDateTime
比較
使用前需明確該物件支援的單位或欄位型別,API 文件中有說明支援的型別;
或者使用:isSupported(TemporalUnit unit) 和 isSupported(TemporalUnit unit) 判斷
-
比較與指定 Temporal 實現類指定單位的差:until(Temporal endExclusive, TemporalUnit unit)
-
使用 isAfter(Instant otherInstant) 或 isBefore(Instant otherInstant) 或 compareTo(Instant otherInstant)
獲取
獲取時間的欄位值前同樣得確認是否支援,這些方法通常以 get*
單位的形式存在,或者直接通過:
- get(TemporalField field)
- getLong(TemporalField field)
- 構建查詢獲取:query(TemporalQuery query)
獲取欄位的有效值範圍:range(TemporalField field)
調整
所有的修改方法都返回新的副本:
- 調整欄位值:with(TemporalField field, long newValue)
- 截斷操作會將小於該單位的欄位都設定為 0:truncatedTo(TemporalUnit unit)
- 增加或減少某個欄位:
plus*
、minus*
- 通過 TemporalAdjuster 函式介面實現調整:with(TemporalAdjuster adjuster)
LocalDate
LocalDate
儲存沒有時間的日期,也不附帶時區資訊。它會儲存 yyyy-MM-dd
格式的日期,可以用來儲存生日。
LocalTime
LocalTime
儲存沒有日期的時間。它將儲存類似於 HH:mm:ss
格式的時間。
LocalDateTime
LocalDateTime
儲存日期和時間。它將儲存類似於 yyyy-MM-dd HH:mm:ss
格式的時間。
ZonedDateTime
ZonedDateTime
儲存帶有時區的日期和時間。如果要考慮到 ZoneId(例如“歐洲/巴黎”)對日期和時間進行準確的計算,這將很有用。如果可能,建議使用沒有時區的簡單類。時區的廣泛使用往往會增加應用程式的複雜性。
該類儲存類似於 yyyy-MM-dd HH:mm:ss.zzz
格式的時間。可通過上面所述時間類通過:atZone(ZoneId zone) 新增時區而來。
下圖對 ZoneDateTime 的組成部分進行了說明。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-z9iwQD2o-1612329693139)(…/…/images/java/zoneDateTime.jpg)]
持續時間和週期
Duration
持續時間是沿時間線以納秒為單位的時間的簡單度量。
此類以秒和納秒為單位對時間量進行建模。也可以使用其他基於持續時間的單位(例如分鐘和小時)來訪問它。
建立
目前為止, 我們看到的所有類都實現了 Temporal
介面, Temporal
介面定義瞭如何讀取和操縱為時間建模的物件的值。
Duration
類的靜態工廠方法 between
就可以為兩個 Temporal 物件建立持續時間。
Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);
也可以使用靜態工廠從其它時間單位建立:
Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period
週期以對人類有意義的單位表示時間量。例如年、月或天。
建立
使用靜態工廠方法建立:
Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
Duration
類和 Period
類共享了很多相似的方法,參見下表所示。
方 法 名 | 是否是靜態方法 | 方法描述 |
---|---|---|
between | 是 | 建立兩個時間點之間的 interval |
from | 是 | 由一個臨時時間點建立 interval |
of | 是 | 由它的組成部分建立 interval 的例項 |
parse | 是 | 由字串建立 interval 的例項 |
addTo | 否 | 建立該 interval 的副本,並將其?加到某個指定的 temporal 物件 |
get | 否 | 讀取該 interval 的狀態 |
isNegative | 否 | 檢查該 interval 是否為負值,不包含零 |
isZero | 否 | 檢查該 interval 的時長是否為零 |
minus | 否 | 通過減去一定的時間建立該 interval 的副本 |
multipliedBy | 否 | 將 interval 的值乘以某個標量建立該 interval 的副本 |
negated | 否 | 以忽略某個時長的方式建立該 interval 的副本 |
plus | 否 | 以增加某個指定的時長的方式建立該 interval 的副本 |
subtractFrom | 否 | 從指定的 temporal 物件中減去該 interval |
列印輸出及解析日期
處理日期和時間物件時,格式化以及解析日期-時間物件是另一個非常重要的功能。新的 java.time.format
包就是特別為這個目的而設計的。
這個包中,最重要的類是 DateTimeFormatter
。建立格式器最簡單的方法是通過它的靜態工廠方法以及常量。
它包含了 IOS 格式定義的常見格式,像 BASIC_ISO_DATE
和 ISO_LOCAL_DATE
這樣的常量是 DateTimeFormatter 類的預定義例項。
所有的 DateTimeFormatter 例項都能用於以一定的格式建立代表特定日期或時間的字串。比如,下面的這個例子中,我們使用了兩個不同的格式器生成了字串:
LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); <-- 20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); <-- 2014-03-18
你也可以通過解析代表日期或時間的字串重新建立該日期物件。所有的日期和時間API 都提供了表示時間點或者時間段的工廠方法,你可以使用工廠方法parse達到重創該日期物件的目的:
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
和老的 java.util.DateFormat
相比較,所有的 DateTimeFormatter
例項都是執行緒安全的。所以,我們能夠以單例模式建立格式器例項,就像 DateTimeFormatter 所定義的那些常量,並能在多個執行緒間共享這些例項。DateTimeFormatter 類還支援一個靜態工廠方法,它可以按照某個特定的模式建立格式器,程式碼清單如下。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);