1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        萬(wàn)字博文教你搞懂java源碼的日期和時(shí)間相關(guān)用法

        共 26732字,需瀏覽 54分鐘

         ·

        2021-10-29 12:35

        介紹

        本篇文章主要介紹java源碼中提供了哪些日期和時(shí)間的類

        日期和時(shí)間的兩套API

        java提供了兩套處理日期和時(shí)間的API

        1、舊的API,放在java.util 這個(gè)包下的:比較常用的有Date和Calendar等

        2、新的API是java 8新引入的,放在java.time 這個(gè)包下的:LocalDateTime,ZonedDateTime,DateTimeFormatter和Instant等

        為什么會(huì)有兩套日期時(shí)間API,這個(gè)是有歷史原因的,舊的API是jdk剛開(kāi)始就提供的,隨著版本的升級(jí),逐漸發(fā)現(xiàn)原先的api不滿足需要,暴露了一些問(wèn)題,所以在java 8 這個(gè)版本中,重新引入新API。

        這兩套API都要了解,為什么呢?

        因?yàn)閖ava 8 發(fā)布時(shí)間是2014年,很多之前的系統(tǒng)還是沿用舊的API,所以這兩套API都要了解,同時(shí)還要掌握兩套API相互轉(zhuǎn)化的技術(shù)。

        一:Date

        支持版本及以上

        JDK1.0

        介紹

        Date類說(shuō)明

        Date類負(fù)責(zé)時(shí)間的表示,在計(jì)算機(jī)中,時(shí)間的表示是一個(gè)較大的概念,現(xiàn)有的系統(tǒng)基本都是利用從1970.1.1 00:00:00 到當(dāng)前時(shí)間的毫秒數(shù)進(jìn)行計(jì)時(shí),這個(gè)時(shí)間稱為epoch(時(shí)間戳)

        package java.util;

        public class Date
        implements java.io.Serializable, Cloneable, Comparable<Date>
        {
        ...

        private transient long fastTime;
        ....
        }
        復(fù)制代碼

        java.util.Date是java提供表示日期和時(shí)間的類,類里有個(gè)long 類型的變量fastTime,它是用來(lái)存儲(chǔ)以毫秒表示的時(shí)間戳。

        date常用的用法

        import java.util.Date;
        -----------------------------------------
        //獲取當(dāng)前時(shí)間
        Date date = new Date();
        System.out.println("獲取當(dāng)前時(shí)間:"+date);
        //獲取時(shí)間戳
        System.out.println("獲取時(shí)間戳:"+date.getTime());

        // date時(shí)間是否大于afterDate 等于也為false
        Date afterDate = new Date(date.getTime()-3600*24*1000);
        System.out.println("after:"+date.after(afterDate));
        System.out.println("after:"+date.after(date));

        // date時(shí)間是否小于afterDate 等于也為false
        Date beforeDate = new Date(date.getTime()+3600*24*1000);
        System.out.println("before:"+date.before(beforeDate));
        System.out.println("before:"+date.before(date));

        //兩個(gè)日期比較
        System.out.println("compareTo:"+date.compareTo(date));
        System.out.println("compareTo:"+date.compareTo(afterDate));
        System.out.println("compareTo:"+date.compareTo(beforeDate));

        //轉(zhuǎn)為字符串
        System.out.println("轉(zhuǎn)為字符串:"+date.toString());
        //轉(zhuǎn)為GMT時(shí)區(qū) toGMTString() java8 中已廢棄
        System.out.println("轉(zhuǎn)為GMT時(shí)區(qū):"+date.toGMTString());
        //轉(zhuǎn)為本地時(shí)區(qū) toLocaleString() java8 已廢棄
        System.out.println("轉(zhuǎn)為本地時(shí)區(qū):"+date.toLocaleString());
        復(fù)制代碼

        自定義時(shí)間格式-SimpleDateFormat

        date的toString方法轉(zhuǎn)成字符串,不是我們想要的時(shí)間格式,如果要自定義時(shí)間格式,就要使用SimpleDateFormat

        		//獲取當(dāng)前時(shí)間
        Date date = new Date();
        System.out.println("獲取當(dāng)前時(shí)間:"+date);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.format(date));
        SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy年MM月dd日 HH時(shí)mm分ss秒");
        System.out.println(simpleDateFormat1.format(date));
        復(fù)制代碼

        SimpleDateFormat也可以方便的將字符串轉(zhuǎn)成Date

        //獲取當(dāng)前時(shí)間
        String str = "2021-07-13 23:48:23";
        try {
        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
        System.out.println(date);
        } catch (ParseException e) {
        e.printStackTrace();
        }
        復(fù)制代碼

        日期和時(shí)間格式化參數(shù)說(shuō)明

        yyyy:年
        MM:月
        dd:日
        hh:1~12小時(shí)制(1-12)
        HH:24小時(shí)制(0-23)
        mm:分
        ss:秒
        S:毫秒
        E:星期幾
        D:一年中的第幾天
        F:一月中的第幾個(gè)星期(會(huì)把這個(gè)月總共過(guò)的天數(shù)除以7)
        w:一年中的第幾個(gè)星期
        W:一月中的第幾星期(會(huì)根據(jù)實(shí)際情況來(lái)算)
        a:上下午標(biāo)識(shí)
        k:和HH差不多,表示一天24小時(shí)制(1-24)。
        K:和hh差不多,表示一天12小時(shí)制(0-11)。
        z:表示時(shí)區(qū)

        復(fù)制代碼

        SimpleDateFormat線程不安全原因及解決方案


        SimpleDateFormat線程為什么是線程不安全的呢?

        來(lái)看看SimpleDateFormat的源碼,先看format方法:

        // 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);
        ...
        }
        復(fù)制代碼

        問(wèn)題就出在成員變量calendar,如果在使用SimpleDateFormat時(shí),用static定義,那SimpleDateFormat變成了共享變量。那SimpleDateFormat中的calendar就可以被多個(gè)線程訪問(wèn)到。

        SimpleDateFormat的parse方法也是線程不安全的:

         public Date parse(String text, ParsePosition pos)
        {
        ...
        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();
        }
        }
        }
        // An IllegalArgumentException will be thrown by Calendar.getTime()
        // if any fields are out of range, e.g., MONTH == 17.
        catch (IllegalArgumentException e) {
        pos.errorIndex = start;
        pos.index = oldStart;
        return null;
        }

        return parsedDate;
        }
        復(fù)制代碼

        由源碼可知,最后是調(diào)用**parsedDate = calb.establish(calendar).getTime();**獲取返回值。方法的參數(shù)是calendar,calendar可以被多個(gè)線程訪問(wèn)到,存在線程不安全問(wèn)題。

        我們?cè)賮?lái)看看**calb.establish(calendar)**的源碼

        calb.establish(calendar)方法先后調(diào)用了cal.clear()cal.set(),先清理值,再設(shè)值。但是這兩個(gè)操作并不是原子性的,也沒(méi)有線程安全機(jī)制來(lái)保證,導(dǎo)致多線程并發(fā)時(shí),可能會(huì)引起cal的值出現(xiàn)問(wèn)題了。

        驗(yàn)證SimpleDateFormat線程不安全

        public class SimpleDateFormatDemoTest {

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

        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }


        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        String dateString = simpleDateFormat.format(new Date());
        try {
        Date parseDate = simpleDateFormat.parse(dateString);
        String dateString2 = simpleDateFormat.format(parseDate);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        } catch (Exception e) {
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }
        }
        }
        }
        復(fù)制代碼

        出現(xiàn)了兩次false,說(shuō)明線程是不安全的。而且還拋異常,這個(gè)就嚴(yán)重了。

        解決方案

        這個(gè)是阿里巴巴 java開(kāi)發(fā)手冊(cè)中的規(guī)定:

        1、不要定義為static變量,使用局部變量

        2、加鎖:synchronized鎖和Lock鎖

        3、使用ThreadLocal方式

        4、使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是線程安全的,java 8+支持)

        5、使用FastDateFormat 替換SimpleDateFormat(FastDateFormat 是線程安全的,Apache Commons Lang包支持,不受限于java版本)

        解決方案1:不要定義為static變量,使用局部變量

        就是要使用SimpleDateFormat對(duì)象進(jìn)行format或parse時(shí),再定義為局部變量。就能保證線程安全。

        public class SimpleDateFormatDemoTest1 {

        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }


        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = simpleDateFormat.format(new Date());
        try {
        Date parseDate = simpleDateFormat.parse(dateString);
        String dateString2 = simpleDateFormat.format(parseDate);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        } catch (Exception e) {
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }
        }
        }
        }
        復(fù)制代碼

        由圖可知,已經(jīng)保證了線程安全,但這種方案不建議在高并發(fā)場(chǎng)景下使用,因?yàn)闀?huì)創(chuàng)建大量的SimpleDateFormat對(duì)象,影響性能。

        解決方案2:加鎖:synchronized鎖和Lock鎖

        加synchronized鎖:SimpleDateFormat對(duì)象還是定義為全局變量,然后需要調(diào)用SimpleDateFormat進(jìn)行格式化時(shí)間時(shí),再用synchronized保證線程安全。

        public class SimpleDateFormatDemoTest2 {

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

        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }

        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        try {
        synchronized (simpleDateFormat){
        String dateString = simpleDateFormat.format(new Date());
        Date parseDate = simpleDateFormat.parse(dateString);
        String dateString2 = simpleDateFormat.format(parseDate);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        }
        } catch (Exception e) {
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }
        }
        }
        }
        復(fù)制代碼

        如圖所示,線程是安全的。定義了全局變量SimpleDateFormat,減少了創(chuàng)建大量SimpleDateFormat對(duì)象的損耗。但是使用synchronized鎖, 同一時(shí)刻只有一個(gè)線程能執(zhí)行鎖住的代碼塊,在高并發(fā)的情況下會(huì)影響性能。但這種方案不建議在高并發(fā)場(chǎng)景下使用加Lock鎖:加Lock鎖和synchronized鎖原理是一樣的,都是使用鎖機(jī)制保證線程的安全。

        public class SimpleDateFormatDemoTest3 {

        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        private static Lock lock = new ReentrantLock();
        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }

        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        try {
        lock.lock();
        String dateString = simpleDateFormat.format(new Date());
        Date parseDate = simpleDateFormat.parse(dateString);
        String dateString2 = simpleDateFormat.format(parseDate);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        } catch (Exception e) {
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }finally {
        lock.unlock();
        }
        }
        }
        }
        復(fù)制代碼

        由結(jié)果可知,加Lock鎖也能保證線程安全。要注意的是,最后一定要釋放鎖,代碼里在finally里增加了lock.unlock();,保證釋放鎖。在高并發(fā)的情況下會(huì)影響性能。這種方案不建議在高并發(fā)場(chǎng)景下使用

        解決方案3:使用ThreadLocal方式

        使用ThreadLocal保證每一個(gè)線程有SimpleDateFormat對(duì)象副本。這樣就能保證線程的安全。

        public class SimpleDateFormatDemoTest4 {

        private static ThreadLocal threadLocal = new ThreadLocal(){
        @Override
        protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
        };
        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }

        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        try {
        String dateString = threadLocal.get().format(new Date());
        Date parseDate = threadLocal.get().parse(dateString);
        String dateString2 = threadLocal.get().format(parseDate);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        } catch (Exception e) {
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }finally {
        //避免內(nèi)存泄漏,使用完threadLocal后要調(diào)用remove方法清除數(shù)據(jù)
        threadLocal.remove();
        }
        }
        }
        }
        復(fù)制代碼

        使用ThreadLocal能保證線程安全,且效率也是挺高的。適合高并發(fā)場(chǎng)景使用。

        解決方案4:使用DateTimeFormatter代替SimpleDateFormat


        使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是線程安全的,java 8+支持)DateTimeFormatter介紹

        public class DateTimeFormatterDemoTest5 {
        private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }


        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        try {
        String dateString = dateTimeFormatter.format(LocalDateTime.now());
        TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString);
        String dateString2 = dateTimeFormatter.format(temporalAccessor);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        } catch (Exception e) {
        e.printStackTrace();
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }
        }
        }
        }
        復(fù)制代碼

        使用DateTimeFormatter能保證線程安全,且效率也是挺高的。適合高并發(fā)場(chǎng)景使用。

        解決方案5:使用FastDateFormat 替換SimpleDateFormat

        使用FastDateFormat 替換SimpleDateFormat(FastDateFormat 是線程安全的,Apache Commons Lang包支持,不受限于java版本)

        public class FastDateFormatDemo6 {
        private static FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

        public static void main(String[] args) {
        //1、創(chuàng)建線程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //2、為線程池分配任務(wù)
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
        }
        //3、關(guān)閉線程池
        pool.shutdown();
        }


        static class ThreadPoolTest implements Runnable{

        @Override
        public void run() {
        try {
        String dateString = fastDateFormat.format(new Date());
        Date parseDate = fastDateFormat.parse(dateString);
        String dateString2 = fastDateFormat.format(parseDate);
        System.out.println(Thread.currentThread().getName()+" 線程是否安全: "+dateString.equals(dateString2));
        } catch (Exception e) {
        e.printStackTrace();
        System.out.println(Thread.currentThread().getName()+" 格式化失敗 ");
        }
        }
        }
        }
        復(fù)制代碼

        使用FastDateFormat能保證線程安全,且效率也是挺高的。適合高并發(fā)場(chǎng)景使用。

        FastDateFormat源碼分析
         Apache Commons Lang 3.5
        復(fù)制代碼
        //FastDateFormat
        @Override
        public String format(final Date date) {
        return printer.format(date);
        }

        @Override
        public String format(final Date date) {
        final Calendar c = Calendar.getInstance(timeZone, locale);
        c.setTime(date);
        return applyRulesToString(c);
        }
        復(fù)制代碼

        源碼中 Calender 是在 format 方法里創(chuàng)建的,肯定不會(huì)出現(xiàn) setTime 的線程安全問(wèn)題。這樣線程安全疑惑解決了。那還有性能問(wèn)題要考慮?

        我們來(lái)看下FastDateFormat是怎么獲取的

        FastDateFormat.getInstance();
        FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);
        復(fù)制代碼

        看下對(duì)應(yīng)的源碼

        /**
        * 獲得 FastDateFormat實(shí)例,使用默認(rèn)格式和地區(qū)
        *
        * @return FastDateFormat
        */

        public static FastDateFormat getInstance() {
        return CACHE.getInstance();
        }

        /**
        * 獲得 FastDateFormat 實(shí)例,使用默認(rèn)地區(qū)

        * 支持緩存
        *
        * @param pattern 使用{@link java.text.SimpleDateFormat} 相同的日期格式
        * @return FastDateFormat
        * @throws IllegalArgumentException 日期格式問(wèn)題
        */

        public static FastDateFormat getInstance(final String pattern) {
        return CACHE.getInstance(pattern, null, null);
        }
        復(fù)制代碼

        這里有用到一個(gè)CACHE,看來(lái)用了緩存,往下看

        private static final FormatCache CACHE = new FormatCache(){
        @Override
        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
        return new FastDateFormat(pattern, timeZone, locale);
        }
        };

        //
        abstract class FormatCache<F extends Format> {
        ...
        private final ConcurrentMap cInstanceCache = new ConcurrentHashMap<>(7);

        private static final ConcurrentMap C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap<>(7);
        ...
        }
        復(fù)制代碼

        在getInstance 方法中加了ConcurrentMap 做緩存,提高了性能。且我們知道ConcurrentMap 也是線程安全的。

        實(shí)踐
        /**
        * 年月格式 {@link FastDateFormat}:yyyy-MM
        */

        public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);
        復(fù)制代碼

        //FastDateFormat
        public static FastDateFormat getInstance(final String pattern) {
        return CACHE.getInstance(pattern, null, null);
        }
        復(fù)制代碼

        如圖可證,是使用了ConcurrentMap 做緩存。且key值是格式,時(shí)區(qū)和locale(語(yǔ)境)三者都相同為相同的key。

        問(wèn)題

        1、tostring()輸出時(shí),總以系統(tǒng)的默認(rèn)時(shí)區(qū)格式輸出,不友好。

        2、時(shí)區(qū)不能轉(zhuǎn)換

        3、日期和時(shí)間的計(jì)算不簡(jiǎn)便,例如計(jì)算加減,比較兩個(gè)日期差幾天等。

        4、格式化日期和時(shí)間的SimpleDateFormat對(duì)象是線程不安全的

        5、Date對(duì)象本身也是線程不安全的

        public class Date
        implements java.io.Serializable, Cloneable, Comparable<Date>
        {
        ...
        }
        復(fù)制代碼

        二:Calendar

        支持版本及以上

        JDK1.1

        介紹

        Calendar類說(shuō)明

        Calendar類提供了獲取或設(shè)置各種日歷字段的各種方法,比Date類多了一個(gè)可以計(jì)算日期和時(shí)間的功能。

        Calendar常用的用法

        		// 獲取當(dāng)前時(shí)間:
        Calendar c = Calendar.getInstance();
        int y = c.get(Calendar.YEAR);
        int m = 1 + c.get(Calendar.MONTH);
        int d = c.get(Calendar.DAY_OF_MONTH);
        int w = c.get(Calendar.DAY_OF_WEEK);
        int hh = c.get(Calendar.HOUR_OF_DAY);
        int mm = c.get(Calendar.MINUTE);
        int ss = c.get(Calendar.SECOND);
        int ms = c.get(Calendar.MILLISECOND);
        System.out.println("返回的星期:"+w);
        System.out.println(y + "-" + m + "-" + d + " " + " " + hh + ":" + mm + ":" + ss + "." + ms);
        復(fù)制代碼

        如上圖所示,月份計(jì)算時(shí),要+1;返回的星期是從周日開(kāi)始計(jì)算,周日為1,1~7表示星期;

        Calendar的跨年問(wèn)題和解決方案

        問(wèn)題

        背景:在使用Calendar 的api getWeekYear()讀取年份,在跨年那周的時(shí)候,程序獲取的年份可能不是我們想要的,例如在2019年30號(hào)時(shí),要返回2019,結(jié)果是返回2020,是不是有毒

        // 獲取當(dāng)前時(shí)間:
        Calendar c = Calendar.getInstance();
        c.clear();
        String str = "2019-12-30";
        try {
        c.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(str));
        int y = c.getWeekYear();
        System.out.println(y);
        } catch (ParseException e) {
        e.printStackTrace();
        }
        復(fù)制代碼

        分析原因

        老規(guī)矩,從源碼入手

        Calendar類
        -------------------------
        //@since 1.7
        public int getWeekYear() {
        throw new UnsupportedOperationException();
        }
        復(fù)制代碼

        這個(gè)源碼有點(diǎn)奇怪,getWeekYear()方法是java 7引入的。它的實(shí)現(xiàn)怎么是拋出異常,但是執(zhí)行時(shí),又有結(jié)果返回。

        斷點(diǎn)跟進(jìn),通過(guò)Calendar.getInstance()獲取的Calendar實(shí)例是GregorianCalendar

        GregorianCalendar
        --------------------------------------
        public int getWeekYear() {
        int year = get(YEAR); // implicitly calls complete()
        if (internalGetEra() == BCE) {
        year = 1 - year;
        }

        // Fast path for the Gregorian calendar years that are never
        // affected by the Julian-Gregorian transition
        if (year > gregorianCutoverYear + 1) {
        int weekOfYear = internalGet(WEEK_OF_YEAR);
        if (internalGet(MONTH) == JANUARY) {
        if (weekOfYear >= 52) {
        --year;
        }
        } else {
        if (weekOfYear == 1) {
        ++year;
        }
        }
        return year;
        }
        ...
        }
        復(fù)制代碼

        方法內(nèi)獲取的年份剛開(kāi)始是正常的

        在JDK中會(huì)把前一年末尾的幾天判定為下一年的第一周,因此上面程序的結(jié)果是1

        解決方案

        使用Calendar類 get(Calendar.YEAR)獲取年份

        問(wèn)題

        1、讀取月份時(shí),要+1

        2、返回的星期是從周日開(kāi)始計(jì)算,周日為1,1~7表示星期

        3、Calendar的跨年問(wèn)題,獲取年份要用c.get(Calendar.YEAR),不要用c.getWeekYear();

        4、獲取指定時(shí)間是一年中的第幾周時(shí),調(diào)用cl.get(Calendar.WEEK_OF_YEAR),要注意跨年問(wèn)題,跨年的那一周,獲取的值為1。離跨年最近的那周為52。

        三:LocalDateTime

        支持版本及以上

        jdk8

        介紹

        LocalDateTime類說(shuō)明

        表示當(dāng)前日期時(shí)間,相當(dāng)于:yyyy-MM-ddTHH:mm:ss

        LocalDateTime常用的用法

        獲取當(dāng)前日期和時(shí)間

        		LocalDate d = LocalDate.now(); // 當(dāng)前日期
        LocalTime t = LocalTime.now(); // 當(dāng)前時(shí)間
        LocalDateTime dt = LocalDateTime.now(); // 當(dāng)前日期和時(shí)間
        System.out.println(d); // 嚴(yán)格按照ISO 8601格式打印
        System.out.println(t); // 嚴(yán)格按照ISO 8601格式打印
        System.out.println(dt); // 嚴(yán)格按照ISO 8601格式打印
        復(fù)制代碼

        由運(yùn)行結(jié)果可行,本地日期時(shí)間通過(guò)now()獲取到的總是以當(dāng)前默認(rèn)時(shí)區(qū)返回的

        獲取指定日期和時(shí)間

        		LocalDate d2 = LocalDate.of(2021, 07, 14); // 2021-07-14, 注意07=07月
        LocalTime t2 = LocalTime.of(13, 14, 20); // 13:14:20
        LocalDateTime dt2 = LocalDateTime.of(2021, 07, 14, 13, 14, 20);
        LocalDateTime dt3 = LocalDateTime.of(d2, t2);
        System.out.println("指定日期時(shí)間:"+dt2);
        System.out.println("指定日期時(shí)間:"+dt3);
        復(fù)制代碼

        日期時(shí)間的加減法及修改

        		LocalDateTime currentTime = LocalDateTime.now(); // 當(dāng)前日期和時(shí)間
        System.out.println("------------------時(shí)間的加減法及修改-----------------------");
        //3.LocalDateTime的加減法包含了LocalDate和LocalTime的所有加減,上面說(shuō)過(guò),這里就只做簡(jiǎn)單介紹
        System.out.println("3.當(dāng)前時(shí)間:" + currentTime);
        System.out.println("3.當(dāng)前時(shí)間加5年:" + currentTime.plusYears(5));
        System.out.println("3.當(dāng)前時(shí)間加2個(gè)月:" + currentTime.plusMonths(2));
        System.out.println("3.當(dāng)前時(shí)間減2天:" + currentTime.minusDays(2));
        System.out.println("3.當(dāng)前時(shí)間減5個(gè)小時(shí):" + currentTime.minusHours(5));
        System.out.println("3.當(dāng)前時(shí)間加5分鐘:" + currentTime.plusMinutes(5));
        System.out.println("3.當(dāng)前時(shí)間加20秒:" + currentTime.plusSeconds(20));
        //還可以靈活運(yùn)用比如:向后加一年,向前減一天,向后加2個(gè)小時(shí),向前減5分鐘,可以進(jìn)行連寫(xiě)
        System.out.println("3.同時(shí)修改(向后加一年,向前減一天,向后加2個(gè)小時(shí),向前減5分鐘):" + currentTime.plusYears(1).minusDays(1).plusHours(2).minusMinutes(5));
        System.out.println("3.修改年為2025年:" + currentTime.withYear(2025));
        System.out.println("3.修改月為12月:" + currentTime.withMonth(12));
        System.out.println("3.修改日為27日:" + currentTime.withDayOfMonth(27));
        System.out.println("3.修改小時(shí)為12:" + currentTime.withHour(12));
        System.out.println("3.修改分鐘為12:" + currentTime.withMinute(12));
        System.out.println("3.修改秒為12:" + currentTime.withSecond(12));
        復(fù)制代碼

        LocalDateTime和Date相互轉(zhuǎn)化

        Date轉(zhuǎn)LocalDateTime

        		System.out.println("------------------方法一:分步寫(xiě)-----------------------");
        //實(shí)例化一個(gè)時(shí)間對(duì)象
        Date date = new Date();
        //返回表示時(shí)間軸上同一點(diǎn)的瞬間作為日期對(duì)象
        Instant instant = date.toInstant();
        //獲取系統(tǒng)默認(rèn)時(shí)區(qū)
        ZoneId zoneId = ZoneId.systemDefault();
        //根據(jù)時(shí)區(qū)獲取帶時(shí)區(qū)的日期和時(shí)間
        ZonedDateTime zonedDateTime = instant.atZone(zoneId);
        //轉(zhuǎn)化為L(zhǎng)ocalDateTime
        LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
        System.out.println("方法一:原Date = " + date);
        System.out.println("方法一:轉(zhuǎn)化后的LocalDateTime = " + localDateTime);

        System.out.println("------------------方法二:一步到位(推薦使用)-----------------------");
        //實(shí)例化一個(gè)時(shí)間對(duì)象
        Date todayDate = new Date();
        //Instant.ofEpochMilli(long l)使用1970-01-01T00:00:00Z的紀(jì)元中的毫秒來(lái)獲取Instant的實(shí)例
        LocalDateTime ldt = Instant.ofEpochMilli(todayDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
        System.out.println("方法二:原Date = " + todayDate);
        System.out.println("方法二:轉(zhuǎn)化后的LocalDateTime = " + ldt);
        復(fù)制代碼

        LocalDateTime轉(zhuǎn)Date

        		System.out.println("------------------方法一:分步寫(xiě)-----------------------");
        //獲取LocalDateTime對(duì)象,當(dāng)前時(shí)間
        LocalDateTime localDateTime = LocalDateTime.now();
        //獲取系統(tǒng)默認(rèn)時(shí)區(qū)
        ZoneId zoneId = ZoneId.systemDefault();
        //根據(jù)時(shí)區(qū)獲取帶時(shí)區(qū)的日期和時(shí)間
        ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
        //返回表示時(shí)間軸上同一點(diǎn)的瞬間作為日期對(duì)象
        Instant instant = zonedDateTime.toInstant();
        //轉(zhuǎn)化為Date
        Date date = Date.from(instant);
        System.out.println("方法一:原LocalDateTime = " + localDateTime);
        System.out.println("方法一:轉(zhuǎn)化后的Date = " + date);

        System.out.println("------------------方法二:一步到位(推薦使用)-----------------------");
        //實(shí)例化一個(gè)LocalDateTime對(duì)象
        LocalDateTime now = LocalDateTime.now();
        //轉(zhuǎn)化為date
        Date dateResult = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
        System.out.println("方法二:原LocalDateTime = " + now);
        System.out.println("方法二:轉(zhuǎn)化后的Date = " + dateResult);
        復(fù)制代碼

        線程安全

        網(wǎng)上大家都在說(shuō)JAVA 8提供的LocalDateTime是線程安全的,但是它是如何實(shí)現(xiàn)的呢

        今天讓我們來(lái)挖一挖

        public final class LocalDateTime
        implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable
        {
        ...
        }
        復(fù)制代碼

        由上面的源碼可知,LocalDateTime是不可變類。我們都知道一個(gè)Java并發(fā)編程規(guī)則:不可變對(duì)象永遠(yuǎn)是線程安全的。

        對(duì)比下Date的源碼 ,Date是可變類,所以是線程不安全的。

        public class Date
        implements java.io.Serializable, Cloneable, Comparable<Date>
        {
        ...
        }
        復(fù)制代碼

        四:ZonedDateTime

        支持版本及以上

        jdk8

        介紹

        ZonedDateTime類說(shuō)明

        表示一個(gè)帶時(shí)區(qū)的日期和時(shí)間,ZonedDateTime可以理解為L(zhǎng)ocalDateTime+ZoneId

        從源碼可以看出來(lái),ZonedDateTime類中定義了LocalDateTime和ZoneId兩個(gè)變量。

        且ZonedDateTime類也是不可變類且是線程安全的。

        public final class ZonedDateTime
        implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable
        {

        /**
        * Serialization version.
        */

        private static final long serialVersionUID = -6260982410461394882L;

        /**
        * The local date-time.
        */

        private final LocalDateTime dateTime;
        /**
        * The time-zone.
        */

        private final ZoneId zone;

        ...
        }
        復(fù)制代碼

        ZonedDateTime常用的用法

        獲取當(dāng)前時(shí)間+帶時(shí)區(qū)+時(shí)區(qū)轉(zhuǎn)換

        		// 默認(rèn)時(shí)區(qū)獲取當(dāng)前時(shí)間
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        // 用指定時(shí)區(qū)獲取當(dāng)前時(shí)間,Asia/Shanghai為上海時(shí)區(qū)
        ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
        //withZoneSameInstant為轉(zhuǎn)換時(shí)區(qū),參數(shù)為ZoneId
        ZonedDateTime zonedDateTime2 = zonedDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));
        System.out.println(zonedDateTime);
        System.out.println(zonedDateTime1);
        System.out.println(zonedDateTime2);
        復(fù)制代碼

        LocalDateTime+ZoneId變ZonedDateTime

        		LocalDateTime localDateTime = LocalDateTime.now();
        ZonedDateTime zonedDateTime1 = localDateTime.atZone(ZoneId.systemDefault());
        ZonedDateTime zonedDateTime2 = localDateTime.atZone(ZoneId.of("America/New_York"));
        System.out.println(zonedDateTime1);
        System.out.println(zonedDateTime2);
        復(fù)制代碼

        上面的例子說(shuō)明了,LocalDateTime是可以轉(zhuǎn)成ZonedDateTime的。

        DateTimeFormatter常用的用法

        		ZonedDateTime zonedDateTime = ZonedDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
        System.out.println(formatter.format(zonedDateTime));

        DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
        System.out.println(usFormatter.format(zonedDateTime));

        DateTimeFormatter chinaFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
        System.out.println(chinaFormatter.format(zonedDateTime));
        復(fù)制代碼

        DateTimeFormatter的坑

        1、在正常配置按照標(biāo)準(zhǔn)格式的字符串日期,是能夠正常轉(zhuǎn)換的。如果月,日,時(shí),分,秒在不足兩位的情況需要補(bǔ)0,否則的話會(huì)轉(zhuǎn)換失敗,拋出異常。

        		DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
        LocalDateTime dt1 = LocalDateTime.parse("2021-7-20 23:46:43.946", DATE_TIME_FORMATTER);
        System.out.println(dt1);
        復(fù)制代碼

        會(huì)報(bào)錯(cuò):

        java.time.format.DateTimeParseException: Text '2021-7-20 23:46:43.946' could not be parsed at index 5
        復(fù)制代碼

        分析原因:是格式字符串與實(shí)際的時(shí)間不匹配

        "yyyy-MM-dd HH:mm:ss.SSS"

        "2021-7-20 23:46:43.946"

        中間的月份格式是MM,實(shí)際時(shí)間是7

        解決方案:保持格式字符串與實(shí)際的時(shí)間匹配

        	DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
        LocalDateTime dt1 = LocalDateTime.parse("2021-07-20 23:46:43.946", DATE_TIME_FORMATTER);
        System.out.println(dt1);
        復(fù)制代碼

        2、YYYY和DD謹(jǐn)慎使用

        		LocalDate date = LocalDate.of(2020,12,31);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYYMM");
        // 結(jié)果是 202112
        System.out.println( formatter.format(date));
        復(fù)制代碼

        Java’s DateTimeFormatter pattern “YYYY” gives you the week-based-year, (by default, ISO-8601 standard) the year of the Thursday of that week.
        復(fù)制代碼

        YYYY是取的當(dāng)前周所在的年份,week-based year 是 ISO 8601 規(guī)定的。2020年12月31號(hào),周算年份,就是2021年

         private static void tryit(int Y, int M, int D, String pat) {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pat);
        LocalDate dat = LocalDate.of(Y,M,D);
        String str = fmt.format(dat);
        System.out.printf("Y=%04d M=%02d D=%02d " +
        "formatted with " +
        "\"%s\" -> %s\n",Y,M,D,pat,str);
        }
        public static void main(String[] args){
        tryit(2020,01,20,"MM/DD/YYYY");
        tryit(2020,01,21,"DD/MM/YYYY");
        tryit(2020,01,22,"YYYY-MM-DD");
        tryit(2020,03,17,"MM/DD/YYYY");
        tryit(2020,03,18,"DD/MM/YYYY");
        tryit(2020,03,19,"YYYY-MM-DD");
        }
        復(fù)制代碼
        Y=2020 M=01 D=20 formatted with "MM/DD/YYYY" -> 01/20/2020
        Y=2020 M=01 D=21 formatted with "DD/MM/YYYY" -> 21/01/2020
        Y=2020 M=01 D=22 formatted with "YYYY-MM-DD" -> 2020-01-22
        Y=2020 M=03 D=17 formatted with "MM/DD/YYYY" -> 03/77/2020
        Y=2020 M=03 D=18 formatted with "DD/MM/YYYY" -> 78/03/2020
        Y=2020 M=03 D=19 formatted with "YYYY-MM-DD" -> 2020-03-79
        復(fù)制代碼

        最后三個(gè)日期是有問(wèn)題的,因?yàn)榇髮?xiě)的DD代表的是處于這一年中那一天,不是處于這個(gè)月的那一天,但是dd就沒(méi)有問(wèn)題。

        例子參考于:www.cnblogs.com/tonyY/p/121…

        所以建議使用yyyy和dd。

        六:Instant

        支持版本及以上

        jdk8

        介紹

        Instant類說(shuō)明

        public final class Instant
        implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable
        {
        ...
        }
        復(fù)制代碼

        Instant也是不可變類且是線程安全的。其實(shí)Java.time 這個(gè)包是線程安全的。

        Instant是java 8新增的特性,里面有兩個(gè)核心的字段

        	...	
        private final long seconds;

        private final int nanos;
        ...
        復(fù)制代碼

        一個(gè)是單位為秒的時(shí)間戳,另一個(gè)是單位為納秒的時(shí)間戳。

        是不是跟**System.currentTimeMillis()**返回的long時(shí)間戳很像,System.currentTimeMillis()返回的是毫秒級(jí),Instant多了更精確的納秒級(jí)時(shí)間戳。

        Instant常用的用法

         		Instant now = Instant.now();
        System.out.println("now:"+now);
        System.out.println(now.getEpochSecond()); // 秒
        System.out.println(now.toEpochMilli()); // 毫秒
        復(fù)制代碼

        Instant是沒(méi)有時(shí)區(qū)的,但是Instant加上時(shí)區(qū)后,可以轉(zhuǎn)化為ZonedDateTime

        		Instant ins = Instant.now();
        ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());
        System.out.println(zdt);
        復(fù)制代碼

        long型時(shí)間戳轉(zhuǎn)Instant

        要注意long型時(shí)間戳的時(shí)間單位選擇Instant對(duì)應(yīng)的方法轉(zhuǎn)化

        //1626796436 為秒級(jí)時(shí)間戳
        Instant ins = Instant.ofEpochSecond(1626796436);
        ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());
        System.out.println("秒級(jí)時(shí)間戳轉(zhuǎn)化:"+zdt);
        //1626796436111l 為秒級(jí)時(shí)間戳
        Instant ins1 = Instant.ofEpochMilli(1626796436111l);
        ZonedDateTime zdt1 = ins1.atZone(ZoneId.systemDefault());
        System.out.println("毫秒級(jí)時(shí)間戳轉(zhuǎn)化:"+zdt1);
        復(fù)制代碼

        Instant的坑

        Instant.now()獲取的時(shí)間與北京時(shí)間相差8個(gè)時(shí)區(qū),這是一個(gè)細(xì)節(jié),要避坑。

        看源碼,用的是UTC時(shí)間。

        public static Instant now() {
        return Clock.systemUTC().instant();
        }
        復(fù)制代碼

        解決方案:

        Instant now = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));
        System.out.println("now:"+now);
        復(fù)制代碼


        作者:小虛竹and掘金
        鏈接:https://juejin.cn/post/7023961450859233293
        來(lái)源:稀土掘金
        著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。



        瀏覽 64
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            特黄特色大片免费看 | 久久婷婷成人综合色怡春院 | 美女被爽到哭 | 女人被舔全过程免费视频 | 丰满少妇喷水大秀高清在线 | 欧美日韩中文字幕无码 | 日韩成人一区二区视频 | 综合色区 | 女女互慰视频 | 欧美综合网|