国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

JAVA-LocalDateTime時間格式化,轉(zhuǎn)換時間戳和源碼分析(萬字長文詳解)

這篇具有很好參考價值的文章主要介紹了JAVA-LocalDateTime時間格式化,轉(zhuǎn)換時間戳和源碼分析(萬字長文詳解)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

JAVA-LocalDateTime時間格式化,轉(zhuǎn)換時間戳和源碼分析

LocalDateTime

LocalDateTime作為java8新加的時間類型,也是后面開發(fā)中常用的時間類型。

作為時間類型,最關(guān)注的點無非是這幾個

  • 獲取當(dāng)前時間
  • 獲取指定時間
  • 時間格式化
  • 時間轉(zhuǎn)時間戳
  • 時間戳轉(zhuǎn)時間
  • 時間比較
  • 時間加減

這些點通過LocalDateTime來操作,都會比較簡單

獲取當(dāng)前時間

只需要now一下就可以獲取到當(dāng)前的時間,還是很方便的。

LocalDateTime time = LocalDateTime.now();

再看一下之前的Date

Date date = new Date();

獲取指定時間

這個有比較多的方式

  • 通過原來的datedateTime類型來生成
  • 通過傳年月日時分秒生成
LocalDateTime time = LocalDateTime.of(2022,11,30,6,6,6);

原來Date類的方式。比較奇怪,他的年份會+1900,所以2022年就得是122,月份也會+1,所以11月就是10.但是這個方法呢后面會被刪除,已經(jīng)被標(biāo)記為棄用了,使用Calendar代替。

Date date = new Date(122,10,30,6,6,6);

看一下Calendar的使用。這個年份就正常了,是2022,但是月份還是會+1.

Calendar calendar = Calendar.getInstance();
calendar.set(2022,10,30,6,6,6);

時間格式化

時間格式化都是通過format函數(shù),需要傳一個DateTimeFormatter對象進去,我們可以通過DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")來生成自己想要的格式。

DateTimeFormatter類里面也有一些定義好的格式可以直接用,除了下面列出的還有一些其他的,感興趣可以看一下,不過我覺得都沒啥用。

  • ISO_DATE_TIME 2011-12-03T10:15:30
  • ISO_OFFSET_DATE_TIME 2011-12-03T10:15:30+01:00
  • ISO_LOCAL_DATE_TIME 2011-12-03T10:15:30
time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

看一下Date的格式化。這個需要借用SimpleDateFormat類來完成格式化。

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
format.format(date);

時間轉(zhuǎn)時間戳

時間轉(zhuǎn)時間戳分為兩種,一種是當(dāng)你已經(jīng)有一個LocalDateTime類型的時間了,需要轉(zhuǎn)換成秒或者毫秒的時間戳。

時間轉(zhuǎn)換秒級時間戳

只需要直接用toEpochSecond方法就可以了。

LocalDateTime time = LocalDateTime.now();
time.toEpochSecond(ZoneOffset.ofHours(8));

Date類型沒有辦法直接獲取秒級時間戳,只能獲取毫秒級再轉(zhuǎn)秒。

時間轉(zhuǎn)換毫秒級時間戳

轉(zhuǎn)換毫秒需要先轉(zhuǎn)換成instant對象,然后才能轉(zhuǎn)換成毫秒級時間戳。

LocalDateTime time = LocalDateTime.now();
time.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();

Date獲取毫秒就很簡單了。

Date date = new Date();
date.getTime();

字符串轉(zhuǎn)換成時間戳

時間轉(zhuǎn)時間戳分為兩種,除了上面的,還有一種是有一個格式化好的字符串,比如2022-12-18 10:00:00這種,但是這個是字符串并不是時間類型。所以要先轉(zhuǎn)換成LocalDateTime類型,然后就可以轉(zhuǎn)換成時間戳了。

其實就是上面格式化的一種反向操作。使用parse方法就可以了。

LocalDateTime.parse("2022-12-18 10:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
LocalDateTime.parse("2022-12-18", DateTimeFormatter.ofPattern("yyyy-MM-dd"));

Date類型的字符串轉(zhuǎn)時間戳也是通過SimpleDateFormat類來完成。

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = format.parse("2022-12-18 10:00:00")

時間戳轉(zhuǎn)時間

那如果我們現(xiàn)在轉(zhuǎn)換成時間戳以后又想轉(zhuǎn)換成時間呢?也可以通過instant對象來做到。

毫秒時間戳轉(zhuǎn)時間

Instant.ofEpochSecond(1671365543834)是將一個毫秒時間戳轉(zhuǎn)換成一個instant對象。在通過ofInstant方法就可以將instant對象轉(zhuǎn)換成LocalDateTime對象了。

LocalDateTime.ofInstant(Instant.ofEpochSecond(1671365543834), ZoneOffset.ofHours(8));

Date

Date date = new Date(1669759566000L);
秒時間戳轉(zhuǎn)時間

Instant.ofEpochMilli(1671365543)是將一個秒時間戳轉(zhuǎn)換成instant對象。和上面的區(qū)別就是使用的是ofEpochMilli方法。

LocalDateTime.ofInstant(Instant.ofEpochMilli(1671365543), ZoneOffset.ofHours(8));

Date類不支持秒,只能把秒轉(zhuǎn)成毫秒在轉(zhuǎn)時間戳。

時間比較

通過compareTo方法可以進行時間的一個比較大小。如果大于會返回1,小于返回-1.

LocalDateTime time = LocalDateTime.now();
time.compareTo(LocalDateTime.now());

Date也是通過compareTo方法進行比較

Date date = new Date(1669759566000L);
date.compareTo(new Date());

時間加減

如果加上幾天,就是plusDays。加幾個小時就是plusHours。當(dāng)然也可以使用plus

LocalDateTime time = LocalDateTime.now();
time.plusDays(1);
time.plusHours(1);
time.plus(Period.ofDays(1));

如果減去幾天就是minusDays.減去幾個小時就是minusHours。當(dāng)然也可以使用minus。

LocalDateTime time = LocalDateTime.now();
time.minusDays(1);
time.minusHours(1);
time.minus(Period.ofDays(1));

Date類不支持時間加減,只能通過Calendar類實現(xiàn)。

Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, 1);
//減去
calendar.add(Calendar.DAY_OF_MONTH, -1);

時間格式在入?yún)⒊鰠⒅械氖褂?/h4>

入?yún)⒌臅r候需要通過JsonFormat注解來指定需要的是字符串類型和對應(yīng)的時間格式。

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
private LocalDate date;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime time;

出參的時候就很簡單了,因為只是返回了一個字符串。

private String time;

格式化時間源碼分析

格式化的時候這兩個年是不一樣的,具體的可以看一下源碼。我們來找一下。

首先點進去是LocalDateTime這個類里面

@Override  // override for Javadoc and performance
public String format(DateTimeFormatter formatter) {
    //判斷參數(shù)是否空
    Objects.requireNonNull(formatter, "formatter");
    //真正的執(zhí)行格式化
    return formatter.format(this);
}

接下來點進去,看一下怎么執(zhí)行的,可以看到又調(diào)用了formatTo這個函數(shù),說明主要的格式化代碼都在這里面。

public String format(TemporalAccessor temporal) {
       //創(chuàng)建了一個32長度的字符串構(gòu)建器
        StringBuilder buf = new StringBuilder(32);
        //格式化主要代碼
        formatTo(temporal, buf);
        //轉(zhuǎn)成字符串
        return buf.toString();
    }

看一下formatTo函數(shù),可以發(fā)現(xiàn)主要是調(diào)用printerParser這個對象的format方法,那我們這個對象是哪來的呢,是在一開始指定格式化類型的時候來的。不同的格式化類型對應(yīng)不同的解析器,也就會執(zhí)行不同的format方法。

public void formatTo(TemporalAccessor temporal, Appendable appendable) {
        //判斷參數(shù),這里不用管
        Objects.requireNonNull(temporal, "temporal");
        Objects.requireNonNull(appendable, "appendable");
        try {
            //創(chuàng)建一個DateTimePrintContext對象
            DateTimePrintContext context = new DateTimePrintContext(temporal, this);
            //判斷,顯然我們之前傳過來的就是一個StringBuilder
            if (appendable instanceof StringBuilder) {
                //主要看這個怎么處理  這里有個 printerParser 對象,這個對象是怎么來的呢,其實是上面DateTimeFormatter.ofPattern的時候給創(chuàng)建的。
                printerParser.format(context, (StringBuilder) appendable);
            } else {
                //這里其實就是如果傳的不是個StringBuilder,就在創(chuàng)建一個然后執(zhí)行
                // buffer output to avoid writing to appendable in case of error
                StringBuilder buf = new StringBuilder(32);
                printerParser.format(context, buf);
                appendable.append(buf);
            }
        } catch (IOException ex) {
            throw new DateTimeException(ex.getMessage(), ex);
        }
    }

接下來我們看一下ofPattern這個方法里面是怎樣的吧。這里是創(chuàng)建了一個 時間格式化的建造者,然后把我們這個字符串添加進去了。

//這里的字符串就是我們傳的 yyyy-MM-dd HH:mm:ss
public static DateTimeFormatter ofPattern(String pattern) {
    return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
}

看一下appendPattern里面是怎么把字符串加進去的。

public DateTimeFormatterBuilder appendPattern(String pattern) {
    //忽略
    Objects.requireNonNull(pattern, "pattern");
    //主要的解析邏輯
    parsePattern(pattern);
    return this;
}

繼續(xù)追蹤到parsePattern方法里面。這個方法代碼比較多,這里只關(guān)注我們想知道的。其余的有興趣的可以看一下。

private void parsePattern(String pattern) {
    //這里給字符串做循環(huán),注意 pattern = yyyy-MM-dd HH:mm:ss 這個字符串。
    for (int pos = 0; pos < pattern.length(); pos++) {
        //取出字符 比如第一個就是 y 對應(yīng)的ASCII碼就是121
        char cur = pattern.charAt(pos);
        //這里就是判斷是否是大小寫字母了,也就是A-Z或者a-z
        if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
            //初始化變量 start = 0 pos = 1
            int start = pos++;
            //這里做一個循環(huán),目的其實就是找出相同的字符有幾個,比如y有4個,pos就會變成4
            for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++);  // short loop
            //這里就是算出具體的數(shù)量 4 - 0 = 4
            int count = pos - start;
            // padding  這里忽略 我們這里面沒有這個字符
            if (cur == 'p') {
                int pad = 0;
                if (pos < pattern.length()) {
                    cur = pattern.charAt(pos);
                    if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
                        pad = count;
                        start = pos++;
                        for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++);  // short loop
                        count = pos - start;
                    }
                }
                if (pad == 0) {
                    throw new IllegalArgumentException(
                            "Pad letter 'p' must be followed by valid pad pattern: " + pattern);
                }
                padNext(pad); // pad and continue parsing
            }
            //接下來是主要邏輯。
            // main rules
            //從hashMap里面取出對應(yīng)的值,這個map放在下面了。y取出來就是 YEAR_OF_ERA
            TemporalField field = FIELD_MAP.get(cur);
            //判斷map里面取出來的是否為空,如果不為空就直接解析,如果為空就接著往下走,看是不是 zvZOXxWwY 這幾個,如果都不是就會報錯了
            if (field != null) {
                //我們y是能取出來的,直接解析這里 cur = y, count = 4, field = YEAR_OF_ERA
                parseField(cur, count, field);
            } else if (cur == 'z') {
                if (count > 4) {
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
                } else if (count == 4) {
                    appendZoneText(TextStyle.FULL);
                } else {
                    appendZoneText(TextStyle.SHORT);
                }
            } else if (cur == 'V') {
                if (count != 2) {
                    throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
                }
                appendZoneId();
            } else if (cur == 'Z') {
                if (count < 4) {
                    appendOffset("+HHMM", "+0000");
                } else if (count == 4) {
                    appendLocalizedOffset(TextStyle.FULL);
                } else if (count == 5) {
                    appendOffset("+HH:MM:ss","Z");
                } else {
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
                }
            } else if (cur == 'O') {
                if (count == 1) {
                    appendLocalizedOffset(TextStyle.SHORT);
                } else if (count == 4) {
                    appendLocalizedOffset(TextStyle.FULL);
                } else {
                    throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur);
                }
            } else if (cur == 'X') {
                if (count > 5) {
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
                }
                appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
            } else if (cur == 'x') {
                if (count > 5) {
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
                }
                String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
                appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
            } else if (cur == 'W') {
                // Fields defined by Locale
                if (count > 1) {
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
                }
                appendInternal(new WeekBasedFieldPrinterParser(cur, count));
            } else if (cur == 'w') {
                // Fields defined by Locale
                if (count > 2) {
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
                }
                appendInternal(new WeekBasedFieldPrinterParser(cur, count));
            } else if (cur == 'Y') {
                // Fields defined by Locale
                appendInternal(new WeekBasedFieldPrinterParser(cur, count));
            } else {
                throw new IllegalArgumentException("Unknown pattern letter: " + cur);
            }
            pos--;

        } else if (cur == '\'') {
            // parse literals
            int start = pos++;
            for ( ; pos < pattern.length(); pos++) {
                if (pattern.charAt(pos) == '\'') {
                    if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') {
                        pos++;
                    } else {
                        break;  // end of literal
                    }
                }
            }
            if (pos >= pattern.length()) {
                throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern);
            }
            String str = pattern.substring(start + 1, pos);
            if (str.length() == 0) {
                appendLiteral('\'');
            } else {
                appendLiteral(str.replace("''", "'"));
            }

        } else if (cur == '[') {
            optionalStart();

        } else if (cur == ']') {
            if (active.parent == null) {
                throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
            }
            optionalEnd();

        } else if (cur == '{' || cur == '}' || cur == '#') {
            throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
        } else {
            // - : 這兩個符號就會走到這里了
            appendLiteral(cur);
        }
    }
}

看一下通過不同的key取值的map

//時代
FIELD_MAP.put('G', ChronoField.ERA);                       // SDF, LDML (different to both for 1/2 chars)
//這個時代的年份,也就是我們常用的年份yyyy
FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);               // SDF, LDML
//單純的年份
FIELD_MAP.put('u', ChronoField.YEAR);                      // LDML (different in SDF)
FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR);             // LDML (removed quarter from 310)
FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR);             // LDML (stand-alone)
//一年里面的月份,也是我們常用的月份 MM
FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);             // SDF, LDML
FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR);             // SDF, LDML (stand-alone)
//一年里面的天,我們基本不用這個作為日子
FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);               // SDF, LDML
//一個月里面的天,我們常用這個獲取多少號
FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);              // SDF, LDML
FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);  // SDF, LDML
FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);               // SDF, LDML (different to both for 1/2 chars)
FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK);               // LDML (stand-alone)
FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK);               // LDML (needs localized week number)
FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);               // SDF, LDML
//一天里面的小時,常用的小時 HH
FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);               // SDF, LDML
FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);         // SDF, LDML
FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);              // SDF, LDML
FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);        // SDF, LDML
//一個小時里面的分鐘,常用的分鐘 mm
FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);            // SDF, LDML
//一分鐘里面的秒數(shù),常用的秒數(shù) ss
FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);          // SDF, LDML
//這個大S基本不用,這是秒里面的納秒
FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);            // LDML (SDF uses milli-of-second number)
FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY);              // LDML
FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND);            // 310 (proposed for LDML)
FIELD_MAP.put('N', ChronoField.NANO_OF_DAY);               // 310 (proposed for LDML)

繼續(xù)深入,直接解析y的方法parseField。可以看到這個是根據(jù)我們格式化的字母執(zhí)行不同的代碼,比如u,y都執(zhí)行到一個代碼塊。4個y走到了appendValue方法里面。

private void parseField(char cur, int count, TemporalField field) {
    boolean standalone = false;
    switch (cur) {
        case 'u':
        case 'y':
            //判斷數(shù)量
            if (count == 2) {
                //yy走這里
                appendValueReduced(field, 2, 2, ReducedPrinterParser.BASE_DATE);
            } else if (count < 4) {
                //y or yyy走這里
                appendValue(field, count, 19, SignStyle.NORMAL);
            } else {
                // yyyy走這里 field = YEAR_OF_ERA count = 4
                appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
            }
            break;
        case 'c':
            if (count == 2) {
                throw new IllegalArgumentException("Invalid pattern \"cc\"");
            }
            /*fallthrough*/
        case 'L':
        case 'q':
            standalone = true;
            /*fallthrough*/
        case 'M':
        case 'Q':
        case 'E':
        case 'e':
            switch (count) {
                case 1:
                case 2:
                    //兩個MM輸出月份走到這里
                    if (cur == 'c' || cur == 'e') {
                        appendInternal(new WeekBasedFieldPrinterParser(cur, count));
                    } else if (cur == 'E') {
                        appendText(field, TextStyle.SHORT);
                    } else {
                        if (count == 1) {
                            appendValue(field);
                        } else {
                            //經(jīng)過判斷走到這里
                            appendValue(field, 2);
                        }
                    }
                    break;
                case 3:
                    appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT);
                    break;
                case 4:
                    appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL);
                    break;
                case 5:
                    appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW);
                    break;
                default:
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
            }
            break;
        case 'a':
            if (count == 1) {
                appendText(field, TextStyle.SHORT);
            } else {
                throw new IllegalArgumentException("Too many pattern letters: " + cur);
            }
            break;
        case 'G':
            switch (count) {
                case 1:
                case 2:
                case 3:
                    appendText(field, TextStyle.SHORT);
                    break;
                case 4:
                    appendText(field, TextStyle.FULL);
                    break;
                case 5:
                    appendText(field, TextStyle.NARROW);
                    break;
                default:
                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
            }
            break;
        case 'S':
            appendFraction(NANO_OF_SECOND, count, count, false);
            break;
        case 'F':
            if (count == 1) {
                appendValue(field);
            } else {
                throw new IllegalArgumentException("Too many pattern letters: " + cur);
            }
            break;
        case 'd':
        case 'h':
        case 'H':
        case 'k':
        case 'K':
        case 'm':
        case 's':
            if (count == 1) {
                appendValue(field);
            } else if (count == 2) {
                //可以看到dd HH mm ss也是走到這里,最終也是通過NumberPrinterParser這個對象來格式化
                appendValue(field, count);
            } else {
                throw new IllegalArgumentException("Too many pattern letters: " + cur);
            }
            break;
        case 'D':
            if (count == 1) {
                appendValue(field);
            } else if (count <= 3) {
                appendValue(field, count);
            } else {
                throw new IllegalArgumentException("Too many pattern letters: " + cur);
            }
            break;
        default:
            if (count == 1) {
                appendValue(field);
            } else {
                appendValue(field, count);
            }
            break;
    }
}

看一下appendValue方法。field = YEAR_OF_ERA,minWidth = 4, maxWidth = 19, signStyle = SignStyle.EXCEEDS_PAD。前面是一些判斷,重點是創(chuàng)建了一個NumberPrinterParser的對象。最后轉(zhuǎn)換的時候其實就是通過這個對象來轉(zhuǎn)換的。

public DateTimeFormatterBuilder appendValue(
    TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
    //這里不執(zhí)行 忽略
    if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
        return appendValue(field, maxWidth);
    }
    //參數(shù)校驗
    Objects.requireNonNull(field, "field");
    Objects.requireNonNull(signStyle, "signStyle");
    //一些校驗規(guī)則
    if (minWidth < 1 || minWidth > 19) {
        throw new IllegalArgumentException("The minimum width must be from 1 to 19 inclusive but was " + minWidth);
    }
    if (maxWidth < 1 || maxWidth > 19) {
        throw new IllegalArgumentException("The maximum width must be from 1 to 19 inclusive but was " + maxWidth);
    }
    if (maxWidth < minWidth) {
        throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " +
                maxWidth + " < " + minWidth);
    }
    //重點是這里,創(chuàng)建了一個 NumberPrinterParser的對象,把參數(shù)傳進去了。
    NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle);
    appendValue(pp);
    return this;
}

看一下NumberPrinterParser類。還記得最開始格式化的時候那一段代碼printerParser.format(context, (StringBuilder) appendable);嗎,實際調(diào)用的就是這里。?


//構(gòu)造方法賦值
NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
    // validated by caller
    this.field = field;
    this.minWidth = minWidth;
    this.maxWidth = maxWidth;
    this.signStyle = signStyle;
    this.subsequentWidth = 0;
}

//格式化方法
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
    //從context上下文中獲取字段 field = YEAR_OF_ERA  context實際包含了真正的時間 2022-12-01T00:00:00
    Long valueLong = context.getValue(field);
    if (valueLong == null) {
        return false;
    }
    //獲取到以后 value = 2022
    long value = getValue(context, valueLong);
    DecimalStyle decimalStyle = context.getDecimalStyle();
    String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
    if (str.length() > maxWidth) {
        throw new DateTimeException("Field " + field +
            " cannot be printed as the value " + value +
            " exceeds the maximum print width of " + maxWidth);
    }
    //轉(zhuǎn)換一個格式類型
    str = decimalStyle.convertNumberToI18N(str);

    //這些條件都不滿足,忽略
    if (value >= 0) {
        switch (signStyle) {
            case EXCEEDS_PAD:
                if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) {
                    buf.append(decimalStyle.getPositiveSign());
                }
                break;
            case ALWAYS:
                buf.append(decimalStyle.getPositiveSign());
                break;
        }
    } else {
        switch (signStyle) {
            case NORMAL:
            case EXCEEDS_PAD:
            case ALWAYS:
                buf.append(decimalStyle.getNegativeSign());
                break;
            case NOT_NEGATIVE:
                throw new DateTimeException("Field " + field +
                    " cannot be printed as the value " + value +
                    " cannot be negative according to the SignStyle");
        }
    }
    //填充0 也就是yyyy minWidth = 4就會填充0 MM minWidth = 2如果 1月就會填充01,一個M就不會走到循環(huán)填充0
    for (int i = 0; i < minWidth - str.length(); i++) {
        buf.append(decimalStyle.getZeroDigit());
    }
    //輸出到buf中
    buf.append(str);
    return true;
}

可以看到上面的代碼,但是NumberPrinterParser其實只是解析了yMdHms這些格式的。也可以再看一下M的確認一下。

首先是appendValue這個方法。大差不差,除了傳到解析器的參數(shù)不一樣,沒啥區(qū)別,其實dd這些也都一樣。

public DateTimeFormatterBuilder appendValue(TemporalField field, int width) {
    //參數(shù)校驗
    Objects.requireNonNull(field, "field");
    if (width < 1 || width > 19) {
        throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width);
    }
    //可以發(fā)現(xiàn)MM也是用的yyyy這個解析器格式化的,但是后面三個參數(shù)不一樣
    NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE);
    appendValue(pp);
    return this;
}

那我們-,:這些格式化符號的輸出呢?是通過另外一個解析器,它先是取到char類型的一個字符來判斷的時候會走到else里面然后走appendLiteral(cur);這個方法??匆幌逻@個方法里面。這里可以看到主要使用的是 CharLiteralPrinterParser 這個解析器。

public DateTimeFormatterBuilder appendLiteral(char literal) {
    //這里可以看到主要使用的是 CharLiteralPrinterParser 這個解析器
    appendInternal(new CharLiteralPrinterParser(literal));
    return this;
}

看一下 CharLiteralPrinterParser 這個解析器

//構(gòu)造方法
CharLiteralPrinterParser(char literal) {
    this.literal = literal;
}

@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
    //簡單粗暴 直接把 - : 這種符號添加到字符串里面
    buf.append(literal);
    return true;
}

接下來看一下為啥我們剛才上面說的,y代表 YEAR_OF_ERA,為啥就能從2022-12-01里面取到2022呢?這個可以看到我們NumberPrinterParser這個解析器里面主要調(diào)用了一個context.getValue(field)方法。

主要是temporal.getLong(field)方法,其實temporal就是我們的日期時間,在我們一開始創(chuàng)建上下文的時候過來的?;貞浺幌律厦娴膭?chuàng)建。這里的temporal可以再往上一層傳過來的,傳的其實就是LocalDateTime的對象。

new DateTimePrintContext(temporal, this)

Long getValue(TemporalField field) {
    try {
        //主要是這里,其實temporal就是我們的日期時間,在我們一開始創(chuàng)建上下文的時候過來的。
        return temporal.getLong(field);
    } catch (DateTimeException ex) {
        if (optional > 0) {
            return null;
        }
        throw ex;
    }
}

所以我們再看一下getLong方法??梢钥吹接幸粋€類型判斷,yMdHms這幾個類型就會走到if里面,如果是時間的 Hms這幾個調(diào)用time.getLong方法,yMd日期的調(diào)用日期的getLong方法。Y的話就會走到 getFrom 這個方法。而且是通過field調(diào)用。

@Override
public long getLong(TemporalField field) {
    //類型判斷 yMdHms這幾個走的這里面
    if (field instanceof ChronoField) {
        ChronoField f = (ChronoField) field;
        //如果是時間的 Hms這幾個調(diào)用time.getLong方法,yMd日期的調(diào)用日期的getLong方法
        return (f.isTimeBased() ? time.getLong(field) : date.getLong(field));
    }
    //Y走這個方法
    return field.getFrom(this);
}

看一下getFrom方法。

@Override
public long getFrom(TemporalAccessor temporal) {
    if (rangeUnit == WEEKS) {  // day-of-week
        return localizedDayOfWeek(temporal);
    } else if (rangeUnit == MONTHS) {  // week-of-month
        return localizedWeekOfMonth(temporal);
    } else if (rangeUnit == YEARS) {  // week-of-year
        return localizedWeekOfYear(temporal);
    } else if (rangeUnit == WEEK_BASED_YEARS) {
        return localizedWeekOfWeekBasedYear(temporal);
    } else if (rangeUnit == FOREVER) {
        // YYYY 大寫的Y走的是這里
        return localizedWeekBasedYear(temporal);
    } else {
        throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this);
    }
}

如果大寫的Y格式化就會走下面的函數(shù),主要就是取出年份以后計算周數(shù),如果周數(shù)=0就認為是上一年的,年份-1,如果周數(shù)大于等于下一年的周數(shù)就年份+1

private int localizedWeekBasedYear(TemporalAccessor temporal) {
    //獲取到這周的第幾天 第5天
    int dow = localizedDayOfWeek(temporal);
    //獲取日期中的年份 2021
    int year = temporal.get(YEAR);
    //獲取今年的第幾天 2021-12-30 是 364天
    int doy = temporal.get(DAY_OF_YEAR);
    //這周開始的偏移量 5
    int offset = startOfWeekOffset(doy, dow);
    //今年的第幾周 53周
    int week = computeWeek(offset, doy);
    //如果這周是0周,就是上一年的,年份就-1
    if (week == 0) {
        // Day is in end of week of previous year; return the previous year
        return year - 1;
    } else {
        //如果接近年底,使用更高精度的邏輯
        //檢查 如果年份的日期包含在下一年的部分的星期里面了
        // If getting close to end of year, use higher precision logic
        // Check if date of year is in partial week associated with next year
        //獲取一年里面的天數(shù) 對象里面包含 最小1天 - 最大365天
        ValueRange dayRange = temporal.range(DAY_OF_YEAR);
        //獲取到年份的長度,也就是365
        int yearLen = (int)dayRange.getMaximum();
        //下一年的周數(shù) 根據(jù)下面的計算公式得出 (7 + 5 + 366 - 1) / 7 = 53
        //這里為啥是366呢,因為yearLen是今年的天數(shù)也就是365 + 1,其實也就是到下一年去了。為的是計算下一年的第一周
        int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
        //比較如果今年的這周大于等于下一年的周 就年份 +1 所以這里格式化就會出錯了。
        if (week >= newYearWeek) {
            return year + 1;
        }
    }
    return year;
}

那么是怎么計算今年的第幾周的呢,看一下computeWeek方法。其實就是一個計算公式。

//offset = 5 , day = 今年的第幾天 364 天
private int computeWeek(int offset, int day) {
    //計算公式 ( 7 + 5 + (364 - 1)) / 7
    return ((7 + offset + (day - 1)) / 7);
}

還有一個問題,就是我們用到了一個周的偏移量,這個偏移量怎么計算的呢,看一下這個方法startOfWeekOffset。以2021-12-30為例,day = 364,dow = 5

private int startOfWeekOffset(int day, int dow) {
    // offset of first day corresponding to the day of week in first 7 days (zero origin)
    //算出上一周 (364 - 5) % 7 = 2
    int weekStart = Math.floorMod(day - dow, 7);
    // offset = -2
    int offset = -weekStart;
    //這里 2 + 1  > 1會走進去
    if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) {
        // The previous week has the minimum days in the current month to be a 'week'
        //這里 7 - 2 = 5 返回的就是5
        offset = 7 - weekStart;
    }
    return offset;
}

上面看完了大寫的Y,再來看一下小寫的y。走的getLong方法。

日期的getLong方法。經(jīng)過判斷后主要看get0這個方法??梢钥吹竭@個命名就很隨意了。。

@Override
public long getLong(TemporalField field) {
    //這個判斷也會走進來
    if (field instanceof ChronoField) {
        //這兩個判斷忽略
        if (field == EPOCH_DAY) {
            return toEpochDay();
        }
        if (field == PROLEPTIC_MONTH) {
            return getProlepticMonth();
        }
        //走到這里
        return get0(field);
    }
    return field.getFrom(this);
}

看一下日期的get0方法??梢园l(fā)現(xiàn)了,這里主要處理了這幾種類型。我們常用的

  • y也就是YEAR_OF_ERA 處理很簡單,判斷了一下year >= 1就返回 year。
  • M也就是MONTH_OF_YEAR 處理很簡單,返回日期的month.
  • d也就是DAY_OF_MONTH 返回日期的day.

從這里也可以看出我們格式化成YEARERA作為年其實也是可以的。

private int get0(TemporalField field) {
    switch ((ChronoField) field) {
        case DAY_OF_WEEK: return getDayOfWeek().getValue();
        case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1;
        case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1;
        case DAY_OF_MONTH: return day;
        case DAY_OF_YEAR: return getDayOfYear();
        case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead");
        case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1;
        case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1;
        case MONTH_OF_YEAR: return month;
        case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead");
        case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year);
        case YEAR: return year;
        case ERA: return (year >= 1 ? 1 : 0);
    }
    throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
}

看完了日期的處理再看一下時間的吧,其實大同小異了。

時間的getLong方法。同樣的經(jīng)過判斷走到get0里面,注意這是時間的getLongget0。

@Override
public long getLong(TemporalField field) {
    if (field instanceof ChronoField) {
        if (field == NANO_OF_DAY) {
            return toNanoOfDay();
        }
        if (field == MICRO_OF_DAY) {
            return toNanoOfDay() / 1000;
        }
        return get0(field);
    }
    return field.getFrom(this);
}

時間的get0方法。處理的就是這些類型了。主要看我們關(guān)注的幾個

  • H 也就是 HOUR_OF_DAY, 直接返回時間的 hour
  • m 也就是MINUTE_OF_HOUR,直接返回時間的 minute
  • s 也就是 SECOND_OF_MINUTE, 直接返回時間的 second
private int get0(TemporalField field) {
    switch ((ChronoField) field) {
        case NANO_OF_SECOND: return nano;
        case NANO_OF_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'NanoOfDay' for get() method, use getLong() instead");
        case MICRO_OF_SECOND: return nano / 1000;
        case MICRO_OF_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'MicroOfDay' for get() method, use getLong() instead");
        case MILLI_OF_SECOND: return nano / 1000_000;
        case MILLI_OF_DAY: return (int) (toNanoOfDay() / 1000_000);
        case SECOND_OF_MINUTE: return second;
        case SECOND_OF_DAY: return toSecondOfDay();
        case MINUTE_OF_HOUR: return minute;
        case MINUTE_OF_DAY: return hour * 60 + minute;
        case HOUR_OF_AMPM: return hour % 12;
        case CLOCK_HOUR_OF_AMPM: int ham = hour % 12; return (ham % 12 == 0 ? 12 : ham);
        case HOUR_OF_DAY: return hour;
        case CLOCK_HOUR_OF_DAY: return (hour == 0 ? 24 : hour);
        case AMPM_OF_DAY: return hour / 12;
    }
    throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
}

總結(jié)

好了,到這里我們知道了時間格式的各種使用方法和格式化的源碼。

對于不同格式化的區(qū)別。總結(jié)一下。

  • y 處理簡單,只是判斷了year > 1 就返回了year。
  • Y 處理較復(fù)雜,還判斷了周,根據(jù)情況對年份+1和-1。某些年份的某些日期會有坑。一定要注意?。?!
  • Md Hms處理非常簡單,直接返回了日期時間上面對應(yīng)的數(shù)。
  • -: 一些特殊字符,格式化的時候是直接增加到字符串里面的。

下面總結(jié)一下源碼對應(yīng)文件和方法的追蹤鏈。感興趣的可以自己在多翻翻源碼。

ofPattern指定格式的調(diào)用鏈

  • DateTimeFormatter.java -> public static DateTimeFormatter ofPattern(String pattern)
    • DateTimeFormatterBuilder.java -> public DateTimeFormatterBuilder appendPattern(String pattern)
    • DateTimeFormatterBuilder.java -> private void parsePattern(String pattern)
    • DateTimeFormatterBuilder.java -> private void parseField(char cur, int count, TemporalField field)
    • DateTimeFormatterBuilder.java -> public DateTimeFormatterBuilder appendValue(TemporalField field, int width)
    • 在這里創(chuàng)建的解析器
    • DateTimeFormatterBuilder.java -> static class NumberPrinterParser implements DateTimePrinterParser
    • DateTimeFormatterBuilder.java -> static final class CharLiteralPrinterParser implements DateTimePrinterParser

format方法調(diào)用鏈文章來源地址http://www.zghlxwxcb.cn/news/detail-816138.html

  • LocalDateTime.java -> public String format(DateTimeFormatter formatter)
    • DateTimeFormatter.java -> public String format(TemporalAccessor temporal)
    • DateTimeFormatter.java -> public void formatTo(TemporalAccessor temporal, Appendable appendable)
      • 接下來根據(jù)不同的處理解析器進行處理,主要有兩個解析器
      • DateTimeFormatterBuilder.java -> static class NumberPrinterParser implements DateTimePrinterParser
      • DateTimeFormatterBuilder.java -> static final class CharLiteralPrinterParser implements DateTimePrinterParser
        • DateTimePrintContext.java -> Long getValue(TemporalField field)
          • LocalDateTime.java -> public long getLong(TemporalField field)
            • 這里日期調(diào)日期的 LocalDate.java -> public long getLong(TemporalField field)
            • LocalDate.java -> private int get0(TemporalField field)
            • 時間調(diào)時間的 LocalTime.java -> public long getLong(TemporalField field)
            • LocalTime.java -> private int get0(TemporalField field)

到了這里,關(guān)于JAVA-LocalDateTime時間格式化,轉(zhuǎn)換時間戳和源碼分析(萬字長文詳解)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Java格式化日期,時間(三種方法)

    Java格式化日期,時間(三種方法)

    在java中String類格式化的方法,是靜態(tài)format()用于創(chuàng)建格式化的字符串。 format(String format, Object...?args) 新字符串使用本地語言環(huán)境,制定字符串格式和參數(shù)生成格式化的新字符串。 format(Locale locale, String format, Object... args) 使用指定的語言環(huán)境,制定字符串格式和參數(shù)生成格式化

    2024年02月13日
    瀏覽(26)
  • Java格式化日期,時間(三種方法,建議收藏)

    Java格式化日期,時間(三種方法,建議收藏)

    在java中String類格式化的方法,是靜態(tài)format()用于創(chuàng)建格式化的字符串。 format(String format, Object...?args) 新字符串使用本地語言環(huán)境,制定字符串格式和參數(shù)生成格式化的新字符串。 format(Locale locale, String format, Object... args) 使用指定的語言環(huán)境,制定字符串格式和參數(shù)生成格式化

    2024年02月15日
    瀏覽(20)
  • 【Java 基礎(chǔ)篇】Java日期和時間格式化與解析指南:SimpleDateFormat詳解

    【Java 基礎(chǔ)篇】Java日期和時間格式化與解析指南:SimpleDateFormat詳解

    日期和時間在軟件開發(fā)中經(jīng)常被用到,無論是用于記錄事件、計算時間間隔還是格式化日期以供用戶友好的展示。Java 提供了強大的日期和時間處理工具,其中 SimpleDateFormat 類是一個重要的工具,用于格式化日期和時間,同時也支持解析日期和時間。本篇博客將深入探討 Sim

    2024年02月09日
    瀏覽(47)
  • Java中日期時間格式化方法SimpleDateFormat和DateTimeFormatter使用完整示例及區(qū)別說明

    Java中日期時間格式化方法SimpleDateFormat和DateTimeFormatter使用完整示例及區(qū)別說明

    示例代碼: 示例截圖: ?這里完整的用兩種方法分別實現(xiàn)了日期和String的來回轉(zhuǎn)換,鑒于SimpleDateFormat早已過時,且非線程安全,所以推薦大家首選使用DateTimeFormatter,用法基本都是差不多的。變化不大。但是DateTimeFormatter需要Java Level 8(8 - Lambdas, type annotations etc.),需留意。

    2023年04月09日
    瀏覽(26)
  • MySQL 格式化時間

    MySQL是一個非常流行的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),它提供了一種使用SQL語言來管理和操作數(shù)據(jù)庫的方法。在MySQL中,時間格式化是一個常見的需求,但很多人可能并不了解如何正確格式化時間。在本文中,我們將介紹MySQL如何正確格式化時間。 MySQL日期和時間類型 MySQL中有許多日

    2024年02月12日
    瀏覽(17)
  • 【js】時間和時間戳轉(zhuǎn)換、日期格式化

    1、時間戳轉(zhuǎn)換日期方法 (格式:2023-08-17) 2、日期字符串轉(zhuǎn)時間戳 3、時間戳轉(zhuǎn)換日期+時間方法 date:時間戳數(shù)字(格式:2023-08-17 14:11:01) 4、 獲取日期中文格式

    2024年02月12日
    瀏覽(21)
  • Python time時間格式化

    Python time時間格式化

    Python提供了多個內(nèi)置模塊用于操作日期時間,像calendar,time,datetime。time模塊我在之前的文章已經(jīng)有所介紹,它提供 的接口與C標(biāo)準(zhǔn)庫time.h基本一致。相比于time模塊,datetime模塊的接口則更直觀、更容易調(diào)用。今天就來講講datetime模塊。 datetime模塊定義了兩個常量:datetime.MI

    2024年02月12日
    瀏覽(20)
  • 在線時間戳格式化轉(zhuǎn)換工具

    在線時間戳格式化轉(zhuǎn)換工具

    在線時間戳格式化轉(zhuǎn)換工具 本工具支持在時間和時間戳之間相互轉(zhuǎn)換,默認時間參考的是服務(wù)器時間 Unix時間戳(Unix timestamp),或稱Unix時間(Unix time)、POSIX時間(POSIXtime),是一種時間表示方式,定義為從格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起

    2024年02月15日
    瀏覽(21)
  • unity獲取和格式化時間

    在Unity中,可以使用DateTime結(jié)構(gòu)來獲取和格式化時間。例如獲取2023 年 5 月 16 日 13:43:15 000 格式,精確到毫秒。 在上述示例中,DateTime.Now獲取當(dāng)前的日期和時間。然后,使用ToString方法將其格式化為指定的格式。格式字符串\\\"yyyy 年 M 月 d 日 HH:mm:ss.fff\\\"將日期和時間以所需的格式

    2024年02月14日
    瀏覽(16)
  • elasticsearch 將時間類型為時間戳保存格式的時間字段格式化返回

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包