時間戳轉換器命令行版

Perl 6 By Example: Datetime Conversion for the Command Line

我偶爾會在數(shù)據(jù)庫中存儲 UNIX 時間戳, 即從 1970-01-01 開始的秒數(shù)橘忱。我在按照日期查詢數(shù)據(jù)庫中的數(shù)據(jù)時, 需要將 UNIX 時間戳轉換為人類可讀的時間, 所以我寫了個很小的工具來幫助我在 UNIX 時間戳和日期/時間之間來回轉換:

$ autotime 2015-12-24
1450915200
$ autotime 2015-12-24 11:23:00
1450956180
$ autotime 1450915200
2015-12-24
$ autotime 1450956180
2015-12-24 11:23:00

使用庫

Perl 6 的 DateTimeDate 模塊會做實際的轉換。
DateTime.new 構造函數(shù)有一個接收單個整數(shù)作為 UNIX 時間戳的變體:

$ perl6 -e "say DateTime.new(1480915200)"
2016-12-05T05:20:00Z

看起來我們已經(jīng)完成了一個方向的轉換,對嗎?

#!/usr/bin/env perl6
sub MAIN (Int $timestamp) {
    say DateTime.new($timestamp)
}

我們來運行它:

$ autotime 1450915200
Invalid DateTime string '1450915200'; use an ISO 8601 timestamp (yyyy-mm-ddThh:mm:ssZ or yyyy-mm-ddThh:mm:ss+01:00) instead
  in sub MAIN at autotime line 2
  in block <unit> at autotime line 2

發(fā)生了什么募强?看起來 DateTime 構造函數(shù)把參數(shù)當作了字符串, 盡管 sub MAIN 的參數(shù)被聲明為 Int。怎么會變成那樣呢? 我們添加一些調(diào)試輸出:

#!/usr/bin/env perl6
sub MAIN(Int $timestamp) {
    say $timestamp.^name;
    say DateTime.new($timestamp)
}

打印出:

IntStr

$thing.^name 是 $thing 所屬類的名字。 IntStrIntStr 類的子類, 這就是為什么 DateTime 構造函數(shù)正常地認為 $timestamp 是一個 Str 的原因挟阻。

長話短說, 我們可以在參數(shù)前添加一個 + 前綴使參數(shù)強制為 "真" 整數(shù), 這也是將字符串轉為數(shù)值的通用機制:

#!/usr/bin/env perl6
sub MAIN(Int $timestamp) {
    say DateTime.new(+$timestamp)
}

這一次它真的工作了:

$ ./autotime-01.p6 1450915200
2015-12-24T00:00:00Z

輸出是 ISO 8601 樣式的時間戳格式, 對眼睛不太友好伐债。對于小時,分鐘和秒數(shù)都為 0 的日期, 我們真正想要的只有日期:

#!/usr/bin/env perl6
sub MAIN(Int $timestamp) {
    my $dt = DateTime.new(+$timestamp);
    if $dt.hour == 0 && $dt.minute == 0 && $dt.second == 0 {
        say $dt.Date;
    }
    else {
        say $dt;
    }
}

這樣看起來更好一點:

$ ./autotime 1450915200
2015-12-24

但是上面那種三個比較都為 0 的寫法實在太丑了, 如果是 4 個, 5 個, 6 個... 那就是又丑又長趣兄。Perl 6 有一個 all Junction:

if all($dt.hour, $dt.minute, $dt.second) == 0 {
    say $dt.Date;
}

all(...) 創(chuàng)建了一個 Junction, 它是幾個其他值的組合值, 它也存儲了一個邏輯模式驻民。當你比較一個 junction 和其他值的時候, 那個比較會自動地應用到該 junction 中的所有值上腺怯。if 語句在布爾上下文中對該 junction 進行求值, 在這個例子中, 當所有的比較為 True 時, if 也返回 True

其他類型的 junction 還有 any, all, none川无。考慮到在布爾上下文中, 0 是唯一一個求值為 false 的整數(shù), 我們甚至可以把上面的例子寫為:

if none($dt.hour, $dt.minute, $dt.second) {
    say $dt.Date;
}

但是也可能沒有必要搞得那么復雜, 如果 $dt 這個 Datetime 對象轉換為 Date 然后再轉換為 DateTime 而不丟失信息, 那么它肯定是一個 Date:

if $dt.Date.DateTime == $dt {
    say $dt.Date;
}
else {
    say $dt;
}

DateTime 格式化

如果時間戳沒有被解析為整天, 那么當前我們的腳本的輸出就會像這樣:

2015-12-24T00:00:01Z

其中的 "Z" 表示 UTC 或 "Zulu" 時區(qū)虑乖。

DateTime 類支持自定義格式化, 所以我們來寫一個:

sub MAIN(Int $timestamp) {
    my $dt = DateTime.new(+$timestamp, formatter => sub ($o) {
            sprintf '%04d-%02d-%02d %02d:%02d:%02d',
                    $o.year, $o.month,  $o.day,
                    $o.hour, $o.minute, $o.second,
    });
    if $dt.Date.DateTime == $dt {
        say $dt.Date;
    }
    else {
        say $dt.Str;
    }
}

現(xiàn)在輸出看起來更好看了:

./autotime 1450915201
2015-12-24 00:00:01

語法 formatter => ... 在參數(shù)上下文中表示具名參數(shù)懦趋。
這樣的代碼我不喜歡, 因為在 DateTime.new 調(diào)用中它是內(nèi)聯(lián)的, 這并不清晰。

我們來單獨寫一個例程:

#!/usr/bin/env perl6
sub MAIN(Int $timestamp) {
    sub formatter($o) {
        sprintf '%04d-%02d-%02d %02d:%02d:%02d',
                $o.year, $o.month,  $o.day,
                $o.hour, $o.minute, $o.second,
    }
    my $dt = DateTime.new(+$timestamp, formatter => &formatter);
    if $dt.Date.DateTime == $dt {
        say $dt.Date;
    }
    else {
        say $dt.Str;
    }
}

是的, 你可以把一個子例程聲明放在另一個子例程聲明的正文中; 子例程只是一個普通的詞法符號,就像一個用 my 聲明的變量疹味。

在行 my $dt = DateTime.new(+$timestamp,formatter => &formatter); 中, 語法 &formatter 引用子例程作為一個對象,而不調(diào)用它仅叫。

這是 Perl 6, formatter => &formatter 有一個簡寫: &formatter
作為一般規(guī)則,如果要填充一個名稱為變量名稱并且其值為變量值的命名參數(shù), 可以通過寫入 :$variable 創(chuàng)建它糙捺。 作為擴展, :thingthing => True 的縮寫诫咱。

尋找其他途徑

現(xiàn)在, 從時間戳到日期和時間的轉換工作的很好, 讓我們看另一種途徑。
我們的小工具需要解析輸入, 并決定輸入的是時間戳還是日期和可選的時間洪灯。

一種無聊的方式是使用條件:

sub MAIN($input) {
    if $input ~~ / ^ \d+ $ / {
        # convert from timestamp to date/datetime
    }
    else {
        # convert from date to timestamp

    }
}

但我討厭無聊, 所以我想看看一個更令人興奮的(端可擴展)方法坎缭。

Perl 6 支持多重分派。這意味著您可以有多個具有相同名稱但不同簽名的子例程签钩。
Perl 6 自動決定要調(diào)用哪一個掏呼。 您必須通過編寫 multi sub 而不是 sub 來顯式地啟用此功能, 以便 Perl 6 可以捕獲意外的重新聲明。

讓我們看看它在實際中的運用:

#!/usr/bin/env perl6

multi sub MAIN(Int $timestamp) {
    sub formatter($o) {
        sprintf '%04d-%02d-%02d %02d:%02d:%02d',
                $o.year, $o.month,  $o.day,
                $o.hour, $o.minute, $o.second,
    }
    my $dt = DateTime.new(+$timestamp, :&formatter);
    if $dt.Date.DateTime == $dt {
        say $dt.Date;
    }
    else {
        say $dt.Str;
    }
}


multi sub MAIN(Str $date) {
    say Date.new($date).DateTime.posix
}

我們看一下效果:

$ ./autotime 2015-12-24
1450915200
$ ./autotime 1450915200
Ambiguous call to 'MAIN'; these signatures all match:
:(Int $timestamp)
:(Str $date)
  in block <unit> at ./autotime line 17

不是我所想象的铅檩。問題又是整數(shù)參數(shù)自動被轉換為了 IntStr, Int 和 Str multi(或候選)都接受它作為參數(shù)憎夷。

避免這種錯誤的最簡單的方法是縮小 Str 候選者接受的字符串的種類。
經(jīng)典的方法是用一個正則表達式粗略驗證傳入的參數(shù):

multi sub MAIN(Str $date where /^ \d+ \- \d+ \- \d+ $ /) {
    say Date.new($date).DateTime.posix
}

它確實能工作, 但為什么重復 Date.new 已經(jīng)有用于驗證日期字符串的邏輯昧旨?
如果你傳遞一個看起來不像日期的字符串參數(shù),你會得到這樣的錯誤:

Invalid Date string 'foobar'; use yyyy-mm-dd instead

我們可以使用這種行為約束 MAIN multi 候選者的字符串參數(shù):

multi sub MAIN(Str $date where { try Date.new($_) }) {
    say Date.new($date).DateTime.posix
}

在這里額外的 try 是因為子類型約束后面的 where 不應該拋出異常, 而只是返回一個假值拾给。

現(xiàn)在它的工作得像預期的一樣:

$ ./autotime 2015-12-24;
1450915200
$ ./autotime 1450915200
2015-12-24

處理時間

剩下要實現(xiàn)的功能是把日期和時間轉換為時間戳。換句話說, 我們想這樣調(diào)用 autotime 2015-12-24 11:23:00:

multi sub MAIN(Str $date where { try Date.new($_) }, Str $time?) {
    my $d = Date.new($date);
    if $time {
        my ( $hour, $minute, $second ) = $time.split(':');
        say DateTime.new(date => $d, :$hour, :$minute, :$second).posix;
    }
    else {
        say $d.DateTime.posix;
    }
}

憑借尾部的?, 新的第二個參數(shù)是可選的 兔沃。 如果存在第二個參數(shù), 我們用冒號將時間字符串分割成小時,分鐘和秒蒋得。 我寫的第一個本能是使用較短的變量名稱, my($h, $m, $s) = $time.split(':'), 但然后調(diào)用 DateTime 構造函數(shù)看起來像這樣:

DateTime.new(date => $d, hour => $h, minute => $m, second => $s);

所以構造函數(shù)的命名參數(shù)使我選擇更多的自解釋變量名。

所以, 這個可以工作:

./autotime 2015-12-24 11:23:00
1450956180

而且我們還可以檢測它的原形:

$ ./autotime 1450956180
2015-12-24 11:23:00

系好你的安全帶

Perl 6 的隱式變量或主題變量:

for 1..3 {
    .say
}

產(chǎn)生如下輸出:

[source]

1
2
3

這個例子中沒有顯式的迭代變量, 所以 Perl 隱式地把當前循環(huán)的值綁定給叫做 $_ 的變量粘拾。方法調(diào)用 .say$_.say 的縮寫窄锅。由于我們有一個子例程在同一個變量上調(diào)用了 6 個方法, 所以使用 $_ 會有很好的可視效果:

sub formatter($_) {
    sprintf '%04d-%02d-%02d %02d:%02d:%02d',
            .year, .month,  .day,
            .hour, .minute, .second,
}

如果你不想求助于函數(shù)定義在詞法作用域中設置 $_, 那么你可以使用 given VALUE BLOCK 結構:

given DateTime.new(+$timestamp, :&formatter) {
    if .Date.DateTime == $_ {
        say .Date;
    }
    else {
        .say;
    }
}

Perl 6 還提供了對 $_ 變量的條件語句的快捷方式,可以用作一個通用的switch語句:

given DateTime.new(+$timestamp, :&formatter) {
    when .Date.DateTime == $_ { say .Date }
    default { .say }
}

如果你有一個只讀的變量或參數(shù), 那么你可以不使用 $ 符號, 雖然你可以在聲明時使用反斜線:

multi sub MAIN(Int \timestamp) {
    ...
    given DateTime.new(+timestamp, :&formatter) {
    ...
    }
}

所以現(xiàn)在完整的代碼看起來像這樣:

#!/usr/bin/env perl6

multi sub MAIN(Int \timestamp) {
    sub formatter($_) {
        sprintf '%04d-%02d-%02d %02d:%02d:%02d',
                .year, .month,  .day,
                .hour, .minute, .second,
    }
    given DateTime.new(+timestamp, :&formatter) {
        when .Date.DateTime == $_ { say .Date }
        default { .say }
    }
}

multi sub MAIN(Str $date where { try Date.new($_) }, Str $time?) {
    my $d = Date.new($date);
    if $time {
        my ( $hour, $minute, $second ) = $time.split(':');
        say DateTime.new(date => $d, :$hour, :$minute, :$second).posix;
    }
    else {
        say $d.DateTime.posix;
    }
}

MAIN 魔法

為我們調(diào)用 sub MAIN 的魔法還為我們提供了一個自動化的用法消息, 如果我們用不匹配任何 multi 的參數(shù)調(diào)用 MAIN, 例如調(diào)用時不提供參數(shù):

$ ./autotime
Usage:
  ./autotime <timestamp>
  ./autotime <date> [<time>]

我們可以通過在 MAIN subs 之前添加語義注釋來為這些用法行添加簡短描述:

#!/usr/bin/env perl6

#| Convert timestamp to ISO date
multi sub MAIN(Int \timestamp) {
    ...
}

#| Convert ISO date to timestamp
multi sub MAIN(Str $date where { try Date.new($_) }, Str $time?) {
    ...
}

現(xiàn)在用法信息變?yōu)榱?

$ ./autotime
Usage:
  ./autotime <timestamp> -- Convert timestamp to ISO date
  ./autotime <date> [<time>] -- Convert ISO date to timestamp

總結

我們已經(jīng)看到了一些 Date 和 DateTime 算法, 但令人興奮的部分是 multi dispatch, 命名參數(shù),帶有 where 從句的子類型約束, given/ when 和 隱式 $_ 變量, 以及一些魔法, 當涉及到 MAIN subs 時。

原文請參見 Perl 6 By Example: Datetime Conversion for the Command Line

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市入偷,隨后出現(xiàn)的幾起案子追驴,更是在濱河造成了極大的恐慌,老刑警劉巖疏之,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殿雪,死亡現(xiàn)場離奇詭異,居然都是意外死亡锋爪,警方通過查閱死者的電腦和手機丙曙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來其骄,“玉大人亏镰,你說我怎么就攤上這事≌” “怎么了索抓?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毯炮。 經(jīng)常有香客問我逼肯,道長,這世上最難降的妖魔是什么桃煎? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任篮幢,我火速辦了婚禮,結果婚禮上为迈,老公的妹妹穿的比我還像新娘三椿。我一直安慰自己,他們只是感情好葫辐,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布赋续。 她就那樣靜靜地躺著,像睡著了一般另患。 火紅的嫁衣襯著肌膚如雪纽乱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天昆箕,我揣著相機與錄音鸦列,去河邊找鬼。 笑死鹏倘,一個胖子當著我的面吹牛薯嗤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纤泵,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼骆姐,長吁一口氣:“原來是場噩夢啊……” “哼镜粤!你這毒婦竟也來了?” 一聲冷哼從身側響起玻褪,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤肉渴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后带射,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體同规,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年窟社,在試婚紗的時候發(fā)現(xiàn)自己被綠了券勺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡灿里,死狀恐怖关炼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匣吊,我是刑警寧澤盗扒,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站缀去,受9級特大地震影響,放射性物質發(fā)生泄漏甸祭。R本人自食惡果不足惜缕碎,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望池户。 院中可真熱鬧咏雌,春花似錦、人聲如沸校焦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寨典。三九已至氛雪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耸成,已是汗流浹背报亩。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留井氢,地道東北人弦追。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像花竞,于是被迫代替她去往敵國和親劲件。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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