亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java新舊時(shí)間日期API的使用和避坑指南

 更新時(shí)間:2025年05月24日 10:19:46   作者:litbluehandy  
這篇文章主要介紹了Java新舊時(shí)間日期API的使用和避坑指南,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

在 Java 8 之前,我們處理日期時(shí)間需求時(shí),使用 Date、Calender 和 SimpleDateFormat,來(lái)聲明時(shí)間戳、使用日歷處理日期和格式化解析日期時(shí)間。但是,這些類(lèi)的 API 的缺點(diǎn)比較明顯,比如可讀性差、易用性差、使用起來(lái)冗余繁瑣,還有線(xiàn)程安全問(wèn)題。因此,Java 8 推出了新的日期時(shí)間類(lèi)。

每一個(gè)類(lèi)功能明確清晰、類(lèi)之間協(xié)作簡(jiǎn)單、API 定義清晰不踩坑,API 功能強(qiáng)大無(wú)需借助外部工具類(lèi)即可完成操作,并且線(xiàn)程安全。

Java 8引入了三個(gè)新的日期時(shí)間類(lèi),分別是LocalDate、LocalTimeLocalDateTime,分別處理日期、時(shí)間和日期時(shí)間。

一、新的時(shí)間和日期API

1.1 獲取當(dāng)前時(shí)間

LocalDateTime localDateTime = LocalDateTime.now();

System.out.println("當(dāng)前時(shí)刻:" + localDateTime );
System.out.println("當(dāng)前年:" + localDateTime.getYear() +
                   "\n當(dāng)前月:" + localDateTime.getMonth() +
                   "\n當(dāng)前日:" + localDateTime.getDayOfMonth());

System.out.println("當(dāng)前時(shí)/分/秒:" +
                   localDateTime.getHour() +" / " +
                   localDateTime.getMinute() + "/" +
                   localDateTime.getSecond());


/* 
 * 打印結(jié)果
 * 
 * 當(dāng)前時(shí)刻:2020-09-04T22:11:27.505361600
 * 當(dāng)前年:2020
 * 當(dāng)前月:SEPTEMBER
 * 當(dāng)前日:4
 * 當(dāng)前時(shí)/分/秒:  22/13/48
 */

1.2 構(gòu)造一個(gè)指定年月日的時(shí)間

比如構(gòu)造:2019年8月30日18時(shí)26分30秒,大約是我對(duì)小方表白的時(shí)刻。

LocalDateTime specifiedTime = LocalDateTime.of(2019, Month.AUGUST, 30, 18, 26, 30);
System.out.println("構(gòu)造時(shí)間:" + specifiedTime );

/**
 * 打印結(jié)果
 * 
 * 構(gòu)造時(shí)間:2019-08-30T18:26:30
 */

1.3 修改日期

LocalDateTime updateTime = LocalDateTime.now();
// 增加1個(gè)月
updateTime.plusMonths(1);
// 減少2天
updateTime.minusDays(2);
// 直接修改到2028年
updateTime.withYear(2028);
// 直接修改到本月的第28天
updateTime.withDayOfMonth(28);
// 組合條件修改
updateTime.withDayOfMonth(12).withYear(2060).minusDays(1);

1.4 格式化日期

LocalDateTime formatTime = LocalDateTime.now();
String type1 = formatTime.format(DateTimeFormatter.BASIC_ISO_DATE);
String type2 = formatTime.format(DateTimeFormatter.ISO_DATE);
String type3 = formatTime.format(DateTimeFormatter.ofPattern("yyyy-/-MM-/-dd"));

System.out.println("formatTime1:" + type1 +
                   "\nformatTime2: " + type2 +
                   "\nformatTime3: " + type3);

/**
 * 輸出:
 * formatTime1:20200904
 * formatTime2: 2020-09-04
 * formatTime3: 2020-/-09-/-04
 */

1.5 計(jì)算時(shí)間差

Java 8 中有一個(gè)專(zhuān)門(mén)的類(lèi) Period 定義了日期間隔,通過(guò)Period.between 得到了兩個(gè)LocalDate 的差,返回的是兩個(gè)日期差幾年零幾月零幾天。如果希望得知兩個(gè)日期之間差幾天,直接調(diào)用 PeriodgetDays(). 方法得到的只是最后的“零幾天”,而不是算總的間隔天數(shù)。

LocalDate today = LocalDate.of(2020, 9, 5);
LocalDate specifyDate = LocalDate.of(2019, 8, 30);
System.out.println(Period.between(specifyDate, today).getDays());
System.out.println(Period.between(specifyDate, today));
System.out.println(ChronoUnit.DAYS.between(specifyDate, today));

/**
 * 輸出:
 * 6
 * P1Y6D
 * 372
 */

1.6 時(shí)間反解析

LocalDate inverseAnalysisTime = LocalDate.parse("2020-/-09-/-04" ,
                DateTimeFormatter.ofPattern("yyyy-/-MM-/-dd"));
System.out.println("反解析后時(shí)間為:" + inverseAnalysisTime);

/**
 * 輸出:
 * 反解析后時(shí)間為:2020-09-04
 */

LocalDateTime inverseAnalysisTime = LocalDateTime.parse("2020-/-09-/-04 22:42" ,
                DateTimeFormatter.ofPattern("yyyy-/-MM-/-dd HH:mm"));
System.out.println("反解析后時(shí)間為:" + inverseAnalysisTime);
/**
 * 輸出:
 * 反解析后時(shí)間為:2020-09-04T22:42
 */

注意:

  • 這里的LocalDate、LocalTimeLocalDateTime的使用要區(qū)別好,不然解析過(guò)程會(huì)出現(xiàn)錯(cuò)誤。

1.7 Instant類(lèi)

Instant對(duì)象和時(shí)間戳是一一對(duì)應(yīng)的,它是精確到納秒的(而不是象舊版本的Date精確到毫秒)。

Instant instant = Instant.now();
System.out.println(instant);

// 輸出, ISO-8601 標(biāo)準(zhǔn)
// 2020-09-04T15:13:50.152933300Z

Instant 類(lèi)返回的值計(jì)算從 1970 年 1 月 1 日(1970-01-01T00:00:00Z)第一秒開(kāi)始的時(shí)間, 也稱(chēng)為 EPOCH。 發(fā)生在時(shí)期之前的瞬間具有負(fù)值,并且發(fā)生在時(shí)期后的瞬間具有正值 (1970-01-01T00:00:00Z 中的 Z 其實(shí)就是偏移量為 0)。Instant 類(lèi)提供的其他常量是 MIN, 表示最小可能(遠(yuǎn)遠(yuǎn))的瞬間,MAX表示最大(遠(yuǎn)期)瞬間。

  • 該類(lèi)還提供了多種方法操作 Instant。加和減的增加或減少時(shí)間的方法。以下代碼將 1 小時(shí)添加到當(dāng)前時(shí)間:
Instant oneHourLater = Instant.now().plusHours(1);
  • 比較時(shí)間的方法
long secondsFromEpoch = Instant.ofEpochSecond(0L).until(Instant.now(),ChronoUnit.SECONDS);
// 1599233977

LocalDateTime start = LocalDateTime.of(2020, 9, 4, 0, 0, 0);
LocalDateTime end = LocalDateTime.of(2020, 9, 8, 0, 0, 0);
// 兩個(gè)時(shí)間之間相差了4天
System.out.println(start.until(end, ChronoUnit.DAYS));

// 4
  • Instant 不包含年,月,日等單位。但是可以轉(zhuǎn)換成 LocalDateTime 或 ZonedDateTime, 如下 把一個(gè) Instant + 默認(rèn)時(shí)區(qū)轉(zhuǎn)換成一個(gè) LocalDateTime。
LocalDateTime ldt = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());

System.out.printf("%s %d %d at %d:%d%n", ldt.getMonth(), ldt.getDayOfMonth(),
                                                        ldt.getYear(), ldt.getHour(), ldt.getMinute());
// SEPTEMBER 4 2020 at 23:40

無(wú)論是 ZonedDateTime 或 OffsetTimeZone 對(duì)象可被轉(zhuǎn)換為 Instant 對(duì)象,因?yàn)槎加成涞綍r(shí)間軸上的確切時(shí)刻。 但是,相反情況并非如此。要將 Instant 對(duì)象轉(zhuǎn)換為 ZonedDateTime 或 OffsetDateTime 對(duì)象,需要提供時(shí)區(qū)或時(shí)區(qū)偏移信息。

二、線(xiàn)程安全性問(wèn)題

放兩張圖就一目了然:

三、數(shù)據(jù)庫(kù)中時(shí)間存儲(chǔ)

3.1 區(qū)別

int

  • 占用4個(gè)字節(jié)
  • 建立索引之后,查詢(xún)速度快
  • 條件范圍搜索可以使用使用between
  • 不能使用mysql提供的時(shí)間函數(shù)

datetime

  • 占用8個(gè)字節(jié),允許為空值,可以自定義值
  • 系統(tǒng)不會(huì)自動(dòng)修改其值
  • 與時(shí)區(qū)無(wú)關(guān),存什么拿到的就是什么。
  • 可以在指定datetime字段的值的時(shí)候使用now()變量來(lái)自動(dòng)插入系統(tǒng)的當(dāng)前時(shí)間。

timestamp

  • 類(lèi)型在默認(rèn)情況下,insert、update 數(shù)據(jù)時(shí),timestamp列會(huì)自動(dòng)以當(dāng)前時(shí)間(CURRENT_TIMESTAMP)填充/更新。
  • 受時(shí)區(qū)timezone的影響以及MYSQL版本和服務(wù)器的SQL MODE的影響 ,存儲(chǔ)時(shí)對(duì)當(dāng)前的時(shí)區(qū)進(jìn)行轉(zhuǎn)換,檢索時(shí)再轉(zhuǎn)換回當(dāng)前的時(shí)區(qū)。

3.2 使用建議

  • int適合需要進(jìn)行大量時(shí)間范圍查詢(xún)的數(shù)據(jù)表。
  • datetime適合用來(lái)記錄數(shù)據(jù)的原始的創(chuàng)建時(shí)間,因?yàn)闊o(wú)論你怎么更改記錄中其他字段的值,datetime字段的值都不會(huì)改變,除非你手動(dòng)更改它。
  • timestamp適合用來(lái)記錄數(shù)據(jù)的最后修改時(shí)間,因?yàn)橹灰愀牧擞涗浿衅渌侄蔚闹担?code>timestamp字段的值都會(huì)被自動(dòng)更新。(如果需要可以設(shè)置timestamp不自動(dòng)更新)。

四、“老三樣”的坑

老三樣指:Date、CalenderSimpleDateFormat

4.1 初始化日期時(shí)間

如果要初始化一個(gè) 2020 年 9 月 5 日 11 點(diǎn) 12 分 13 秒這樣的時(shí)間:

 Date date = new Date(2020, 9, 5, 11, 12, 13);

// 輸出:
// Tue Oct 05 11:12:13 CST 3920

這里就要注意:年應(yīng)該是和 1900 的差值,月應(yīng)該是從 0 到 11 而不是從 1 到 12。

我們也可以直接使用Calander

Calendar calendar = Calendar.getInstance();
// 月份依舊是 0-11
calendar.set(2020,8,5,11,16,25);

System.out.println(calendar.getTime());

// 輸出:
// Sat Sep 05 11:16:25 CST 2020

4.2 時(shí)區(qū)問(wèn)題

關(guān)于 Date 類(lèi),我們要有兩點(diǎn)認(rèn)識(shí):

  • Date 并無(wú)時(shí)區(qū)問(wèn)題,世界上任何一臺(tái)計(jì)算機(jī)使用 new Date() 初始化得到的時(shí)間都一樣。因?yàn)椋珼ate 中保存的是 UTC 時(shí)間,UTC 是以原子鐘為基礎(chǔ)的統(tǒng)一時(shí)間,不以太陽(yáng)參照計(jì)時(shí),并無(wú)時(shí)區(qū)劃分。
  • Date 中保存的是一個(gè)時(shí)間戳,代表的是從 1970 年 1 月 1 日 0 點(diǎn)(Epoch 時(shí)間)到現(xiàn)在的毫秒數(shù)。嘗試輸出 Date(0):
System.out.println(new Date(0));
System.out.println(TimeZone.getDefault().getID() + ":" + TimeZone.getDefault().getRawOffset()/3600000);

// 輸出:
// Thu Jan 01 08:00:00 CST 1970
// 因?yàn)槲覚C(jī)器當(dāng)前的時(shí)區(qū)是中國(guó)上海,相比 UTC 時(shí)差 +8 小時(shí)。

對(duì)于國(guó)際化的項(xiàng)目,處理好時(shí)間和時(shí)區(qū)問(wèn)題首先就是要正確保存日期時(shí)間。這里有兩種保存方式:

  • 方式一,以 UTC 保存,保存的時(shí)間沒(méi)有時(shí)區(qū)屬性,是不涉及時(shí)區(qū)時(shí)間差問(wèn)題的世界統(tǒng)一時(shí)間。我們通常說(shuō)的時(shí)間戳,或 Java 中的 Date 類(lèi)就是用的這種方式,這也是推薦的方式。
  • 方式二,以字面量保存,比如年 / 月 / 日 時(shí): 分: 秒,一定要同時(shí)保存時(shí)區(qū)信息。只有有了時(shí)區(qū)信息,我們才能知道這個(gè)字面量時(shí)間真正的時(shí)間點(diǎn),否則它只是一個(gè)給人看的時(shí)間表示,只在當(dāng)前時(shí)區(qū)有意義。Calendar 是有時(shí)區(qū)概念的,所以我們通過(guò)不同的時(shí)區(qū)初始化 Calendar,得到了不同的時(shí)間。正確保存日期時(shí)間之后,就是正確展示,即我們要使用正確的時(shí)區(qū),把時(shí)間點(diǎn)展示為符合當(dāng)前時(shí)區(qū)的時(shí)間表示。

4.3 日期時(shí)間格式化和解析

每到年底,就有很多踩時(shí)間格式化的坑,比如“這明明是一個(gè) 2019 年的日期,怎么使用 SimpleDateFormat 格式化后就提前跨年了”。我們來(lái)重現(xiàn)一個(gè)這個(gè)問(wèn)題。

初始化一個(gè) Calendar,設(shè)置日期時(shí)間為 2019 年 12 月 29 日,使用大寫(xiě)的 YYYY 來(lái)初始化 SimpleDateFormat:

Locale.setDefault(Locale.SIMPLIFIED_CHINESE);
System.out.println("defaultLocale:" + Locale.getDefault());
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 29,0,0,0);
SimpleDateFormat YYYY = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("格式化: " + YYYY.format(calendar.getTime()));
System.out.println("weekYear:" + calendar.getWeekYear());
System.out.println("firstDayOfWeek:" + calendar.getFirstDayOfWeek());
System.out.println("minimalDaysInFirstWeek:" + calendar.getMinimalDaysInFirstWeek());

/**
 * 輸出:
 *  
 * defaultLocale:zh_CN
 * 格式化: 2020-12-29
 * weekYear:2020
 * firstDayOfWeek:1
 * minimalDaysInFirstWeek:1
 */

更改時(shí)區(qū)試試:

Locale.setDefault(Locale.FRANCE);

//        格式化: 2019-12-29
//        weekYear:2019
//        firstDayOfWeek:2
//        minimalDaysInFirstWeek:4

那么 week year 就還是 2019 年,因?yàn)橐恢艿牡谝惶鞆闹芤婚_(kāi)始算,2020 年的第一周是 2019 年 12 月 30 日周一開(kāi)始,29 日還是屬于去年。JDK 的文檔中有說(shuō)明:小寫(xiě) y 是年,而大寫(xiě) Y 是 week year,也就是所在的周屬于哪一年,所以沒(méi)有特殊需求,針對(duì)年份的日期格式化,應(yīng)該一律使用 “y” 而非 “Y”。

另一個(gè)是:當(dāng)需要解析的字符串和格式不匹配的時(shí)候,SimpleDateFormat 表現(xiàn)得很寬容,還是能得到結(jié)果

String dateString = "20200905";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
try {
    System.out.println("result:" + dateFormat.parse(dateString));
} catch (ParseException e) {
    e.printStackTrace();
}

// 輸出:
// result:Sun May 01 00:00:00 CST 2095

這里把0905當(dāng)初月份,往后推遲了905個(gè)月,但是并沒(méi)有爆出任何警告或錯(cuò)誤。

我們可以用Java8中的DateTimeFormatter代替:

String dateString = "20200905";
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");
System.out.println("result:" + dateTimeFormatter.parse(dateString));

// 控制臺(tái)報(bào)錯(cuò):
//        Exception in thread "main" java.time.format.DateTimeParseException:Text '20200905' could not be parsed at index 0
//        at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
//        at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1874)
//        at cn.litblue.datedemo.DateDemo.main(DateDemo.java:56)

4.4 線(xiàn)程安全問(wèn)題

我們寫(xiě)一個(gè)案例:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

ExecutorService threadPool = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
    //提交20個(gè)并發(fā)解析時(shí)間的任務(wù)到線(xiàn)程池,模擬并發(fā)環(huán)境
    threadPool.execute(() -> {
        for (int j = 0; j < 10; j++) {
            try {
                System.out.println(simpleDateFormat.parse("2020-09-05 12:10:30"));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    });
}
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.HOURS);

運(yùn)行程序后大量報(bào)錯(cuò),且沒(méi)有報(bào)錯(cuò)的輸出結(jié)果也不正常。

五、總結(jié)

老三樣還是不要用了,新的日期時(shí)間類(lèi)不香么?

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論