YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

Goで良い感じに日時をパースするライブラリdatemakiの話とGo 1.6

はじめに

こんにちは、Go界の京成舎人ライナーです。このエントリは Go Advent Calendar 2015 の22日目の記事です。

今回のアドベントカレンダーに向けて、タイトルとは別のことをいろいろとやってみてたんですが、OS X 10.11 (El Capitan) でのみ発生する謎事象の原因をいろいろ調べてたらどうもGoと全然関係ないことのようで、まったくGo Advent Calendarに関係ない記事になりそうなのでやめました。

というわけで、今回はGoでの日時のパースについてです。

Goにおける日付のパース処理

いうまでもないおさらいなのですが、念の為に。Goでは日付の処理は timeパッケージ で行っています。他のプログラミング言語とは異なるフォーマットなので、初めて使うときは少々戸惑いますが、慣れてくると「2006年1月2日3時4分5秒」という決まった時刻のどの数字がどこに来るかを書くだけなので案外覚えやすいものです。

http://play.golang.org/p/wq8jrZKLjq

package main

import (
    "fmt"
    "time"
)

func main() {
    s := "2015/12/22 10:00:30"
    layout := "2006/01/02 15:04:05"
    t, err := time.Parse(layout, s)
    if err != nil {
        panic(err)
    }

    layout2 := "06年01月02日 15時04分05秒"
    fmt.Println(t.Format(layout2))
}

実行するとこのようになります。

15年12月22日 10時00分30秒

相対時間と絶対時間

timeパッケージのパース関数やフォーマット関数は上記のような数字だけの絶対時間を扱うのはとても楽なのですが、文字が入った日時などを扱おうとすると面倒です。たとえば "2015 December 21st" とかです。

また相対時間を扱うときも結構面倒だったりします。たとえば "yesterday" とか "3 days ago" とかそういうやつです。

いろいろなパターンについて @shibukawa がgistにまとめてくれています。

これを眺めるだけでも「うぁあああああ」という気持ちになるのですが、「うぁあああああ」とばかりも言ってられないので、とりあえずなんとか力技でもいいから扱えるようにしとくかー、と思ってライブラリを数カ月前に作ったのを忘れてたんですが、今日切羽詰まって思い出しました。

datemaki

なんとなく「dateが入った名前がいいなあ」と思って、伊達巻にしました。いまの季節にぴったりですね!

github.com

datemakiを使うと、日時をパースするのがどう楽になるのか、サンプルコードでご覧ください。

package main

import (
    "fmt"

    "github.com/ymotongpoo/datemaki"
)

func main() {
    exps := []string{
        "3 days ago",
        "2015 Dec 22nd 23:00:00",
        "yesterday 14:00",
    }

    for _, e := range exps {
        t, err := datemaki.Parse(e)
        if err != nil {
            fmt.Println(err)
            continue
        }
        fmt.Println(t)
    }
}

これを実行するとこのような結果になります。

# 実行時の日時は 2015/12/21 23:16:49
% go run main.go
2015-12-18 23:16:49.782843129 +0900 JST
2015-12-22 23:00:00 +0900 JST
2015-12-20 14:00:00 +0900 JST

Parse() という雑な関数に投げただけでちゃんと返してくれました!

datemakiの今後

最初の実装はかたっぱしから正規表現かけたり、序数のサフィックスを持ってるかを適当に判定したりして、漏れがかなりありそうです。テストケースも結構雑なので多分バグります。またいまは実行時間からの相対時間などしかとれないですが、ログを解析するなどの場合はログの取得時からの相対時間もとれないといけないので、ちゃんと状態管理用の構造体を持たせないといけないなあと実装しながら思いました。

Pull Requestなどは随時募集してますが、気が向いた時にしか反応しないと思います。ごめんなさい。(先に謝っていくスタイル)

Go 1.6でのtimeパッケージの変更

ところで、みなさんGo 1.6 beta1はすでにチェケラしてますかぁ~!?Go 1.6のリリースノートをチラ見すると超下の方にちっさくtimeパッケージについての変更予定が書いてあります。(まだリリースしてないので変更しないかもしれない可能性もある。たぶんそんなことないけど。)

  • Go 1.6 Release Notes DRAFT - The Go Programming Language

    The time package's Parse function has always rejected any day of month larger than 31, such as January 32. In Go 1.6, Parse now also rejects February 29 in non-leap years, February 30, February 31, April 31, June 31, September 31, and November 31.

書いてあるままを読むと「Go 1.6ではこれまでエラーにしてなかったうるう年でない2月29日等々もエラーにしますよ」って書いてあります。試しに次のコードをGo 1.5.2とGo 1.6 beta1で実行してみます。

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    fmt.Println("Go version", runtime.Version())
    s1 := "2015/04/31 10:00:00"
    s2 := "2015/04/31 25:00:00"

    layout := "2006/01/02 15:04:05"

    t1, err := time.Parse(layout, s1)
    if err != nil {
        fmt.Println("[s1]", err)
    }
    fmt.Println("[s1]", s1, "->", t1)

    t2, err := time.Parse(layout, s2)
    if err != nil {
        fmt.Println("[s2]", err)
    }
    fmt.Println("[s2]", s2, "->", t2)
}
  • Go 1.5.2
% PATH=/opt/go/go1.5.2/bin:$PATH GOROOT=/opt/go/go1.5.2 go run main.go
Go version go1.5.2
[s1] 2015/04/31 10:00:00 -> 2015-05-01 10:00:00 +0000 UTC
[s2] parsing time "2015/04/31 25:00:00": hour out of range
[s2] 2015/04/31 25:00:00 -> 0001-01-01 00:00:00 +0000 UTC
  • Go 1.6 beta1
PATH=/opt/go/go1.6.b1/bin:$PATH GOROOT=/opt/go/go1.6.b1 go run main.go
Go version go1.6beta1
[s1] parsing time "2015/04/31 10:00:00": day of month out of range
[s1] 2015/04/31 10:00:00 -> 0001-01-01 00:00:00 +0000 UTC
[s2] parsing time "2015/04/31 25:00:00": hour out of range
[s2] 2015/04/31 25:00:00 -> 0001-01-01 00:00:00 +0000 UTC

そのようになってます。しかし、俺が一番ほしいのは時刻の部分の繰上げです。たとえばテレビ欄とかに出てくる「25:30」みたいなのをパースする機能なので逆方向に行っちゃってますね。

というわけでこの辺ちょっと本家にパッチ送るモチベーション湧いてきました。とりあえずdatemakiを良い感じにしつつ、ぼちぼちやってきます。みなさん、お正月に伊達巻見たらdatemaki思い出してください。

明日のラインナップは次の皆さんです!!

(訂正)2016.01.05 完全に1.6のリリースノートを逆に読み違えてました。1.6では日付のチェックがより厳しくなりました、というのが正です。

QNAPのTurboNASが吹っ飛んでtestdiskに助けられた話

はじめに

こんにちは。この記事は pyspa Advent Calendar 2015 の22日目の記事です。Go Advent Calendarとまったく同じ日に登録したことにあとで気づいて、すごく後悔しています。そんなわけで、今日は自宅のTurboNASが吹っ飛んでtestdiskに助けられた話をします。最後提灯記事っぽいけど、1ユーザとして便利なので書きました。

TL;DR

  • TurboNASは便利だけどデータが吹っ飛ぶ危険はある
  • testdisk超助かる
  • Google Apps for unlimited storage and vaultで安心を買った

QNAPのNASは素晴らしいです

2012年2月に買ってから自宅のNASとして活躍していたQNAPのTurboNAS(TS-659+ Pro、いまは売ってないっぽい)なんですが、こいつがなかなかすぐれもので、RAID 0/1/5/6/10なんかを簡単に組めて(ソフトウェアRAID)、イーサネットポートも2口、iSCSIでも使えて、CIFS/AFP/NFSでもマウント可能、他にもメディアトランスコーダーや、DLNAサーバー等々、各種サービスもWebのGUIでちょいちょいっと簡単に設定できる。Mac使ってる人にはTimeMachineにもなってくれる便利なやつです。

f:id:ymotongpoo:20151222171138p:plain

で、写真などのメディアや必要書類をスキャンした電子ファイルの保存など、結構重要な役割を果たしていました。そんな中、今年の10月末にHDDの警告が出ていたので、新規HDDに換装しようとしたところ、久しぶりだったため手順を誤ってしまいました。

ホットスワップができるというのが製品の売りなので、とりあえず前のHDDにもどして、再構成が終わるのを待ってたんですが、なんかいやな予感がするわけです。で、再構成が終わっても、RAID5の仮想ディスク領域をマウントできない。個々のディスクは認識してるので、まさかと思って確認してみるとRAID5のパーティションテーブルがぶっ壊れてました。

すでにその時ディスクには6TB-7TBくらいのデータがあったのでかなり冷や汗モノでした。

復旧作業

最初mdadmでRAIDから外れてるディスクを追加しようとしたものの、そいつ自体のパーティションテーブルが壊れてるということで弾かれ、しょうがないのでmke2fsでスーパーブロックのアドレス見てから、e2fsckで修復試みてみたものの全然状況が変わらないんで、これ以上手をいれるのはまずいということで、RAID自体の復旧は断念しました。

しかしディスク自体はまだ生きてるようなので、上書きされてしまう前にデータのサルベージを行うことにしました。諸々探してみた中で最も信頼性が高かったのが testdisk でした。

www.cgsecurity.org

testdiskはパーティションの復旧などをTUIでサポートするというのが主な機能ですが、復旧可能パーティションに対して、拡張機能(Advanced)の中にある List メニューを選択すると、読み取れるファイルがすべて表示されます。幸い中を確認したかぎりではファイル自体は生きてそうなので、急いで3TBの外付けHDDを購入し、TurboNASに接続、testdiskで2週間かけてほぼすべてのファイルをサルベージし、事なきを得ました。

Google Apps with unlimited storage and Vault

ところで、Google Appsには「Google Apps with unlimited storage and Vault」というプランがあります。これは5名以上いれば、月々1200円で容量無制限のGoogle Driveが利用できるというものです。(もちろん他のGoogle Appsの機能も使えます)

apps.google.com

閲覧用の写真のJPEGデータはいまはGoogle Photosで管理しているので、容量を心配することないんですが、オリジナルサイズやRAWデータはやはりデータが大きく、かと言って今回のようなことがあるとせっかくの思い出が全部消えてしまうのでなかなか扱いに困ってしまいます。これまではNASですべて管理してたのですが、今回の事故を期に有志を募ってこちらのプランを契約しました。先ほどの3TBの外付けHDDが14000円弱だったので、こちらのプランのほぼ1年分の価格です。その価格で安心を買えると思ったら安いものだなと思います。

また副次的に、ウェブでもスマホでもいろいろなクライアントがあるので、どこでも中身を簡単に確認できたりするのがとても楽です。スキャンしたPDFなんかを外で楽に閲覧できるのが地味に助かっています。

おわりに

やっぱりでかいファイルが多くなるとふっとんだ時のリスクがでかいので、大人しくストレージサービス借りたほうが結果的に安いですね。