Golang神奇的2006-01-02 15:04:05

Golang 日期格式化

熱身

在講這個(gè)問(wèn)題之前撵儿,先來(lái)看一道代碼題:

package main

import (
    "fmt"
    "time"
)

func main() {
    timeString := time.Now().Format("2006-01-02 15:04:05")
    fmt.Println(timeString)
    fmt.Println(time.Now().Format("2017-09-07 18:05:32"))
}

這段代碼的輸出是什么(假定運(yùn)行時(shí)刻的時(shí)間是2017-09-07 18:05:32)?

什么壮池?你已經(jīng)知道答案了薯酝?那你是大神叛买,可以跳過(guò)這篇文章了。

一惜互、神奇的日期

剛接觸Golang時(shí)布讹,閱讀代碼的時(shí)候總會(huì)在代碼中發(fā)現(xiàn)這么一個(gè)日期,

2006-01-02 15:04:05

剛看到這段代碼的時(shí)候训堆,我當(dāng)時(shí)想:這個(gè)人好隨便啊描验,隨便寫(xiě)一個(gè)日期在這里,但是又感覺(jué)還挺方便的坑鱼,格式清晰一目了然膘流。也沒(méi)有更多的在意了。
之后一次做需求的時(shí)候輪到自己要格式化時(shí)間了姑躲,仿照它的樣子睡扬,寫(xiě)了一個(gè)日期格式來(lái)格式化,差不多就是上面代碼題上寫(xiě)的那樣黍析。殊不知卖怜,運(yùn)行完畢后,結(jié)果令人驚呆阐枣。马靠。奄抽。

運(yùn)行結(jié)果如下:

2017-09-07 18:06:43
7097-09+08 98:43:67

頓時(shí)就犯糊涂了:怎么就變成這個(gè)鳥(niǎo)樣子了?format不認(rèn)識(shí)我的日期甩鳄?這么標(biāo)準(zhǔn)的日期都不認(rèn)識(shí)逞度?

二、開(kāi)始探究

查閱了資料妙啃,發(fā)現(xiàn)原來(lái)這個(gè)日期就是寫(xiě)死的一個(gè)日期档泽,不是這個(gè)日期就不認(rèn)識(shí),就不能正確的格式化揖赴。記住就好了馆匿。

但是,還是覺(jué)得有點(diǎn)納悶燥滑。為什么輸出日期是這個(gè)亂的渐北?仔細(xì)觀察這個(gè)日期,06年铭拧,1月2日下午3點(diǎn)4分5秒赃蛛,查閱相關(guān)資料還有 -7時(shí)區(qū),Monday搀菩,數(shù)字1~7都有了呕臂,而且都不重復(fù)。難道有什么深刻含義秕磷?還是單純的為了方便記憶诵闭?

晚上睡覺(jué)前一直在心里想。突然想到:這些數(shù)字全都不重復(fù)澎嚣,那豈不就是說(shuō),每個(gè)數(shù)字就能代表你需要格式化的屬性了瘟芝?比如易桃,解析格式化字符串的時(shí)候,遇到了1锌俱,就說(shuō)明這個(gè)地方要填的是月份晤郑,遇到了4,說(shuō)明這個(gè)位置是分鐘贸宏?

不禁覺(jué)得造寝,發(fā)明這串時(shí)間數(shù)字的人還是很聰明的。2006-01-02 15:04:05這個(gè)日期吭练,不但挺好記的诫龙,而且用起來(lái)也比較方便。這個(gè)比其他編程語(yǔ)言的yyyy-MM-dd HH:mm:ss這種東西好記多了鲫咽。(樓主就曾經(jīng)把yyyy大小寫(xiě)弄錯(cuò)了签赃,弄出一個(gè)大bug谷异,寫(xiě)成YYYY,結(jié)果锦聊,當(dāng)時(shí)沒(méi)測(cè)出來(lái)歹嘹,到了十二月左右的時(shí)候,年份多了一年孔庭。尺上。。)

三圆到、深入探究

為了一窺這個(gè)時(shí)間格式化的究竟尖昏,我們還是得閱讀go的time包源代碼。在$GOROOT/src/time/format.go文件中构资,我們可以找到如下代碼:

const (
    _                        = iota
    stdLongMonth             = iota + stdNeedDate  // "January"
    stdMonth                                       // "Jan"
    stdNumMonth                                    // "1"
    stdZeroMonth                                   // "01"
    stdLongWeekDay                                 // "Monday"
    stdWeekDay                                     // "Mon"
    stdDay                                         // "2"
    stdUnderDay                                    // "_2"
    stdZeroDay                                     // "02"
    stdHour                  = iota + stdNeedClock // "15"
    stdHour12                                      // "3"
    stdZeroHour12                                  // "03"
    stdMinute                                      // "4"
    stdZeroMinute                                  // "04"
    stdSecond                                      // "5"
    stdZeroSecond                                  // "05"
    stdLongYear              = iota + stdNeedDate  // "2006"
    stdYear                                        // "06"
    stdPM                    = iota + stdNeedClock // "PM"
    stdpm                                          // "pm"
    stdTZ                    = iota                // "MST"
    stdISO8601TZ                                   // "Z0700"  // prints Z for UTC
    stdISO8601SecondsTZ                            // "Z070000"
    stdISO8601ShortTZ                              // "Z07"
    stdISO8601ColonTZ                              // "Z07:00" // prints Z for UTC
    stdISO8601ColonSecondsTZ                       // "Z07:00:00"
    stdNumTZ                                       // "-0700"  // always numeric
    stdNumSecondsTz                                // "-070000"
    stdNumShortTZ                                  // "-07"    // always numeric
    stdNumColonTZ                                  // "-07:00" // always numeric
    stdNumColonSecondsTZ                           // "-07:00:00"
    stdFracSecond0                                 // ".0", ".00", ... , trailing zeros included
    stdFracSecond9                                 // ".9", ".99", ..., trailing zeros omitted

上面就是所能見(jiàn)到的所有關(guān)于日期時(shí)間的片段抽诉。基本能夠涵蓋所有的關(guān)于日期格式化的請(qǐng)求吐绵。

可以總結(jié)如下:

格式 含義
01迹淌、 1、Jan己单、January
02唉窃、 2、_2 日纹笼,這個(gè)_2表示如果日期是只有一個(gè)數(shù)字纹份,則表示出來(lái)的日期前面用個(gè)空格占位。
03廷痘、 3蔓涧、15 時(shí)
04、4
05笋额、5
2006元暴、06、6
-070000兄猩、 -07:00:00茉盏、 -0700、 -07:00枢冤、 -07
Z070000鸠姨、Z07:00:00、 Z0700淹真、 Z07:00
時(shí)區(qū)
PM讶迁、pm 上下午
Mon、Monday 星期
MST 美國(guó)時(shí)間趟咆,如果機(jī)器設(shè)置的是中國(guó)時(shí)間則表示為UTC

看完了這些添瓷,心里對(duì)日期格式問(wèn)題已經(jīng)有數(shù)了梅屉。
所以,我們回頭看一下開(kāi)頭的問(wèn)題鳞贷,我用

2017-09-07 18:05:32

這串?dāng)?shù)字來(lái)格式化這個(gè)日期

2017-09-07 18:05:32

得到的結(jié)果就是

7097-09+08 98:43:67

看了這個(gè)我就在想坯汤,如果是我,我會(huì)怎么解析這個(gè)格式呢搀愧?不禁想起來(lái)了學(xué)習(xí)《編譯原理》時(shí)候的詞法分析器惰聂,這個(gè)肯定需要構(gòu)造一個(gè)語(yǔ)法樹(shù)。至于文法什么的咱筛,暫時(shí)我也還弄不清搓幌。既然這樣,那不如我們直接看GO源代碼一窺究竟迅箩,看看golang語(yǔ)言團(tuán)隊(duì)的人是怎么解析的:

func nextStdChunk(layout string) (prefix string, std int, suffix string) {
    for i := 0; i < len(layout); i++ {
        switch c := int(layout[i]); c {
        case 'J': // January, Jan
            if len(layout) >= i+3 && layout[i:i+3] == "Jan" {
                if len(layout) >= i+7 && layout[i:i+7] == "January" {
                    return layout[0:i], stdLongMonth, layout[i+7:]
                }
                if !startsWithLowerCase(layout[i+3:]) {
                    return layout[0:i], stdMonth, layout[i+3:]
                }
            }

        case 'M': // Monday, Mon, MST
            if len(layout) >= i+3 {
                if layout[i:i+3] == "Mon" {
                    if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
                        return layout[0:i], stdLongWeekDay, layout[i+6:]
                    }
                    if !startsWithLowerCase(layout[i+3:]) {
                        return layout[0:i], stdWeekDay, layout[i+3:]
                    }
                }
                if layout[i:i+3] == "MST" {
                    return layout[0:i], stdTZ, layout[i+3:]
                }
            }

        case '0': // 01, 02, 03, 04, 05, 06
            if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' {
                return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:]
            }

        case '1': // 15, 1
            if len(layout) >= i+2 && layout[i+1] == '5' {
                return layout[0:i], stdHour, layout[i+2:]
            }
            return layout[0:i], stdNumMonth, layout[i+1:]

        case '2': // 2006, 2
            if len(layout) >= i+4 && layout[i:i+4] == "2006" {
                return layout[0:i], stdLongYear, layout[i+4:]
            }
            return layout[0:i], stdDay, layout[i+1:]

        case '_': // _2, _2006
            if len(layout) >= i+2 && layout[i+1] == '2' {
                //_2006 is really a literal _, followed by stdLongYear
                if len(layout) >= i+5 && layout[i+1:i+5] == "2006" {
                    return layout[0 : i+1], stdLongYear, layout[i+5:]
                }
                return layout[0:i], stdUnderDay, layout[i+2:]
            }

        case '3':
            return layout[0:i], stdHour12, layout[i+1:]

        case '4':
            return layout[0:i], stdMinute, layout[i+1:]

        case '5':
            return layout[0:i], stdSecond, layout[i+1:]

        case 'P': // PM
            if len(layout) >= i+2 && layout[i+1] == 'M' {
                return layout[0:i], stdPM, layout[i+2:]
            }

        case 'p': // pm
            if len(layout) >= i+2 && layout[i+1] == 'm' {
                return layout[0:i], stdpm, layout[i+2:]
            }

        case '-': // -070000, -07:00:00, -0700, -07:00, -07
            if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
                return layout[0:i], stdNumSecondsTz, layout[i+7:]
            }
            if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
                return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
            }
            if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
                return layout[0:i], stdNumTZ, layout[i+5:]
            }
            if len(layout) >= i+6 && layout[i:i+6] == "-07:00" {
                return layout[0:i], stdNumColonTZ, layout[i+6:]
            }
            if len(layout) >= i+3 && layout[i:i+3] == "-07" {
                return layout[0:i], stdNumShortTZ, layout[i+3:]
            }

        case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
            if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
                return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
            }
            if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
                return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
            }
            if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
                return layout[0:i], stdISO8601TZ, layout[i+5:]
            }
            if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
                return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
            }
            if len(layout) >= i+3 && layout[i:i+3] == "Z07" {
                return layout[0:i], stdISO8601ShortTZ, layout[i+3:]
            }

        case '.': // .000 or .999 - repeated digits for fractional seconds.
            if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
                ch := layout[i+1]
                j := i + 1
                for j < len(layout) && layout[j] == ch {
                    j++
                }
                // String of digits must end here - only fractional second is all digits.
                if !isDigit(layout, j) {
                    std := stdFracSecond0
                    if layout[i+1] == '9' {
                        std = stdFracSecond9
                    }
                    std |= (j - (i + 1)) << stdArgShift
                    return layout[0:i], std, layout[j:]
                }
            }
        }
    }
    return layout, 0, ""
}

這段代碼有點(diǎn)長(zhǎng)溉愁,不過(guò)邏輯還是很清楚的,我們吧上面表格中的那些常用項(xiàng)的先進(jìn)行排序饲趋,然后根據(jù)排序結(jié)果拐揭,對(duì)首個(gè)字符進(jìn)行分類(lèi),相同首字符的項(xiàng)放在一個(gè)case里面判斷處理奕塑√梦郏看起來(lái)這里是簡(jiǎn)單的進(jìn)行判斷處理,其實(shí)這就是編譯里面詞法分析的一個(gè)步驟(分詞)龄砰。

縱觀整個(gè)format.go文件盟猖,其實(shí)這個(gè)日期處理還是挺復(fù)雜的,包括日期計(jì)算换棚,格式解析式镐,對(duì)日期進(jìn)行格式化等。

本來(lái)想引申開(kāi)來(lái)講一下編譯原理的詞法分析的圃泡。無(wú)奈發(fā)現(xiàn)自己現(xiàn)在也有點(diǎn)記不清楚了碟案。一個(gè)很簡(jiǎn)單的問(wèn)題,還是花了不少時(shí)間來(lái)寫(xiě)颇蜡。真是紙上得來(lái)終覺(jué)淺,絕知此事要躬行傲究鳌风秤!

如果你喜歡這篇文章,請(qǐng)打賞支持我扮叨!如果文中有什么錯(cuò)誤還望指出缤弦!

收款碼.JPG
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彻磁,隨后出現(xiàn)的幾起案子碍沐,更是在濱河造成了極大的恐慌狸捅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累提,死亡現(xiàn)場(chǎng)離奇詭異尘喝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斋陪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)朽褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人无虚,你說(shuō)我怎么就攤上這事缔赠。” “怎么了友题?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嗤堰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我度宦,道長(zhǎng)踢匣,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任斗埂,我火速辦了婚禮符糊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呛凶。我一直安慰自己男娄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布漾稀。 她就那樣靜靜地躺著模闲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崭捍。 梳的紋絲不亂的頭發(fā)上尸折,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音殷蛇,去河邊找鬼实夹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛粒梦,可吹牛的內(nèi)容都是我干的亮航。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼匀们,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缴淋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤重抖,失蹤者是張志新(化名)和其女友劉穎露氮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钟沛,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畔规,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讹剔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片油讯。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖延欠,靈堂內(nèi)的尸體忽然破棺而出陌兑,到底是詐尸還是另有隱情,我是刑警寧澤由捎,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布兔综,位于F島的核電站,受9級(jí)特大地震影響狞玛,放射性物質(zhì)發(fā)生泄漏软驰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一心肪、第九天 我趴在偏房一處隱蔽的房頂上張望锭亏。 院中可真熱鬧,春花似錦硬鞍、人聲如沸慧瘤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锅减。三九已至,卻和暖如春伐坏,著一層夾襖步出監(jiān)牢的瞬間怔匣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工桦沉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留每瞒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓纯露,卻偏偏與公主長(zhǎng)得像独泞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苔埋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,071評(píng)論 25 707
  • Go最新資料匯總鏈接 Golang資料集 《Platform-native GUI library for Go》...
    Albert陳凱閱讀 5,835評(píng)論 0 148
  • 作者:gabriel theodoropoulos,原文鏈接蜒犯,原文日期:2015-10-18譯者:ray16897...
    梁杰_numbbbbb閱讀 3,832評(píng)論 0 11
  • 該資源的github地址:Qix 《Platform-native GUI library for Go》 介紹:...
    ty4z2008閱讀 4,861評(píng)論 5 121
  • 在生活這場(chǎng)默劇里组橄,每個(gè)人都有保持沉默的機(jī)會(huì)荞膘,但是,肆意飛揚(yáng)的年代玉工,我們?nèi)钥梢杂梦覀儤O力張大的嘴型告訴世界我們需要什...
    冰依潔穎閱讀 224評(píng)論 0 0