全力で怠けたい

怠けるために全力を尽くしたいブログ。

dt に計算結果の日付が無効になったときの調整オプションを追加したメモ。

3/31の1ヶ月後が4/30になったり5/1になったりする 動きをオプションで指定できるようにしたメモ。

dt は5/1になる動きだったのを、4/30になるようにオプションで指定できるようにした。

$ dt "2018/01/31" +1M
2018/03/03


$ dt --adjust-day "2018/01/31" +1M
2018/02/28

こんな感じ。

ただ、複数月を1回で足すときと複数回に分けて足すときとでは最終的な結果が変わることには注意が必要かもしれない。

$ dt --adjust-day "2018/01/31" +2M
2018/03/31

$ dt --adjust-day "2018/01/31" +1M +1M
2018/03/28

github.com

3/31の1ヶ月後が4/30になったり5/1になったりするメモ。

日付計算で月を加算 (減算) して、加算結果の日付が無効になったときの動きのメモ。

書くこと

  • 加算結果が無効な日付になる例
  • 各言語での例
  • 仕様
  • まとめ

加算結果が無効な日付になる例

日付計算で月を加算 (減算) したとき、加算結果の日付が無効になることがある *1
たとえば、3/31に1ヶ月を足すと4/31という無効な日付になるが、プログラミング言語により無効な日付の扱いが変わってくる。
いくつかの言語での例を書いてみる。

各言語での例

# Java
$ jshell
jshell> import java.time.LocalDate;
jshell> LocalDate.of(2018, 3, 31).plusMonths(1);
$2 ==> 2018-04-30

# Ruby
$ irb
irb(main):001:0> require "date"
=> true
irb(main):002:0> Date.new(2018, 3, 31).next_month
=> #<Date: 2018-04-30 ((2458239j,0s,0n),+0s,2299161j)>

# Golang
$ gore -autoimport
gore version 0.3.0  :help for help
gore> time.Date(2018, 3, 31, 0, 0, 0, 0, time.Local).AddDate(0, 1, 0)
2018-05-01 00:00:00 Local

JavaRuby では4月の最後の日である4/30が返されるが、Go では翌月の5/1が返されている。

仕様

各言語の仕様を見てみる。

Java

LocalDate

This method adds the specified amount to the months field in three steps:
1. Add the input months to the month-of-year field
2. Check if the resulting date would be invalid
3. Adjust the day-of-month to the last valid day if necessary
For example, 2007-03-31 plus one month would result in the invalid date 2007-04-31. Instead of returning an invalid result, the last valid day of the month, 2007-04-30, is selected instead.

加算結果の日付が無効になるときは、月の日を有効な最後の日に調整するようだ。

Ruby

instance method Date#

対応する月に同じ日が存在しない時は、代わりにその月の末日が使われます。

Go

Time.AddDate

AddDate normalizes its result in the same way that Date does, so, for example, adding one month to October 31 yields December 1, the normalized form for November 31.

AddDateは、Dateと同じ方法で結果を正規化します。たとえば、10月31日に1か月を追加すると、11月31日の正規化された形式である12月1日が得られます。

まとめ

個人的には、Go のように3/31の1ヶ月後が5/1になるというのは変な感じがするが、4/31という無効な日付は4/30の1日後であるから、4/30の有効な1日後 = 5/1が返されるというのは納得できる。
むしろ、言語やライブラリの場合は仕様にはっきりと書かれているので使う側が気をつければいいが、お客さんや同僚と話しているときにこのあたりの "自然な加算結果" が違っていると、少し苦労しそう。

*1:うるう年が絡むと年の加算などでも同じことが起こる

Go での開発環境に Makefile を導入したメモ。

dtMakefile を導入しました。

雑に始めた Go での開発環境のメモ。 - 全力で怠けたい で↓みたいなことを書いたのですが、

いくつかコマンドを Github で公開しているのですが、Github の Release に登録するためのバイナリの作成やアップロードが面倒です。
そこで、コンパイルには gox を、Github にアップロードするために ghr を利用しています。
実際のところは、↓こんな感じに雑なシェルスクリプトを書いて、パッケージディレクトリで叩いています。

やっぱり面倒だったので Makefile を導入しました。
今まで JVM 系の開発が多かったので Makefile を使ったことはないのですが、Go での開発ではよく使われているみたいですね。

https://github.com/ebc-2in2crc/dt/blob/master/Makefile

GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
NAME := dt
CURRENT := $(shell pwd)
BUILDDIR=./build
BINDIR=$(BUILDDIR)/bin
PKGDIR=$(BUILDDIR)/pkg
DISTDIR=$(BUILDDIR)/dist

VERSION := $(shell git describe --tags --abbrev=0)
LDFLAGS := -X 'main.version=$(VERSION)'
GOXOS := "darwin windows linux"
GOXARCH := "386 amd64"
GOXOUTPUT := "$(PKGDIR)/$(NAME)_{{.OS}}_{{.Arch}}/{{.Dir}}"

.PHONY: deps
## Install dependencies
deps:
	$(GOGET) golang.org/x/tools/cmd/goimports
	$(GOGET) github.com/golang/lint/golint
	$(GOGET) github.com/codegangsta/cli
	$(GOGET) github.com/mitchellh/go-homedir
	$(GOGET) github.com/Songmu/make2help/cmd/make2help
	$(GOGET) github.com/tcnksm/ghr

.PHONY: build
## Build binaries
build: deps
	go build -ldflags "$(LDFLAGS)" -o $(BINDIR)/$(NAME)

.PHONY: cross-build
## Cross build binaries
cross-build:
	rm -rf $(PKGDIR)
	gox -os=$(GOXOS) -arch=$(GOXARCH) -output=$(GOXOUTPUT)

.PHONY: package
## Make package
package: cross-build
	rm -rf $(DISTDIR)
	mkdir $(DISTDIR)
	pushd $(PKGDIR) > /dev/null && \
		for P in `ls | xargs basename`; do zip -r $(CURRENT)/$(DISTDIR)/$$P.zip $$P; done && \
		popd > /dev/null

.PHONY: release
## Release package to Github
release: package
	ghr $(VERSION) $(DISTDIR)

.PHONY: test
## Run tests
test: deps
	$(GOTEST) -v ./...

.PHONY: lint
## Lint
lint: deps
	go vet ./...
	golint ./...

.PHONY: fmt
## Format source codes
fmt: deps
	find . -name "*.go" -not -path "./vendor/*" | xargs goimports -w

.PHONY: clean
clean:
	$(GOCLEAN)
	rm -rf $(BUILDDIR)

.PHONY: help
## Show help
help:
	@make2help $(MAKEFILE_LIST)

ハマったこと

雑に始めた Go での開発環境のメモ。

少し前からちょっとしたことをするときに Go を使い始めたので、その開発環境のメモ。

書くこと

  • エディター
  • ビルド
  • 公開
  • 公開しているコマンドたち
  • まとめ

エディター

だいたい Goland で書いています。
コード補完、クイックフィックス、リファクタリングなどの機能がとても優秀で、初学者でも詰まることなくコードを書けるので助かっています。

ビルドと公開

ビルド

Goland 上で Run するか シェルから go run / go build するのがほとんどです。
コード量やファイルも少ないので、今のところはこれで事足りています。

公開

いくつかコマンドを Github で公開しているのですが、Github の Release に登録するためのバイナリの作成やアップロードが面倒です。
そこで、コンパイルには gox を、Github にアップロードするために ghr を利用しています。
実際のところは、↓こんな感じに雑なシェルスクリプトを書いて、パッケージディレクトリで叩いています。

#!/bin/sh

CURRENT=`pwd`
PGNAME=`basename ${CURRENT}`
BUILD_DIR=build
PKG_DIR=${BUILD_DIR}/pkg
DIST_DIR=${BUILD_DIR}/dist

if [ -z "$1" ]; then
	echo 'compile.sh <tag>'
	exit 1
fi

TAG=$1
PACKAGE=.

if [ -d ${PKG_DIR} ]; then
	rm -r ${PKG_DIR}
fi
mkdir -p ${PKG_DIR}

gox \
    -os="darwin windows linux" \
    -arch="386 amd64" \
    -output "${PKG_DIR}/${PGNAME}_{{.OS}}_{{.Arch}}/{{.Dir}}" \
    $PACKAGE

if [ -d ${DIST_DIR} ]; then
	rm -r ${DIST_DIR}
fi
mkdir ${DIST_DIR}

pushd ${PKG_DIR} > /dev/null
for P in `ls | xargs basename`
do
	zip -r ${CURRENT}/${DIST_DIR}/$P.zip $P
done
popd > /dev/null

ghr ${TAG} ${DIST_DIR}

公開しているコマンドたち

日付時刻を簡単に計算 / フォーマット変換できる dt というコマンドと、西暦と和暦を相互変換する
wareki というコマンドを公開しています。

まとめ

こんな感じの開発環境でやっていますが、gore や ghq や peco などといったとても便利なツールがあるみたいなので、これからもどんどん変わりそうです。

日付を計算したり、日付の書式を変換する dt コマンドを作りました。

日付を計算したり、日付の書式を変換するコマンドを作りました。

github.com

書くこと

  • なにができる?
  • 使い方
  • インストール
  • 動機
  • まとめ

なにができる?

  • 日付の加算と減算
  • 日付の書式を変換

これだけをしてくれるコマンドです。

使い方

システム時刻の1年3ヶ月20秒後を求める

$ date "+%Y/%m/%d %H:%M:%S"
2018/05/12 17:30:00

$ dt now +1Y +3M +20s
2019/08/12 17:30:20

計算元の日付を指定

$ dt "2018/05/12 17:30:00" +1Y +3M +20s
2019/08/12 17:30:20

日付の加算

$ dt "2018/05/12 17:30:00" +1Y
2019/05/12 17:30:00

$ dt "2018/05/12 17:30:00" +1M
2018/06/12 17:30:00

$ dt "2018/05/12 17:30:00" +1D
2018/05/13 17:30:00

$ dt "2018/05/12 17:30:00" +1h
2018/05/12 18:30:00

$ dt "2018/05/12 17:30:00" +1m
2018/05/12 17:31:00

$ dt "2018/05/12 17:30:00" +1s
2018/05/12 17:30:01

# '+' は省略可能
$ dt "2018/05/12 17:30:00" 1Y 3M 20s
2019/08/12 17:30:20

日付の減算

$ dt "2018/05/12 17:30:00" -1Y -3M -20s
2017/02/12 17:29:40

入力フォーマット

# コマンド標準
$ dt "2018/05/12 17:30:00" +1Y +3M +20s
2019/08/12 17:30:20

# RFC822
$ dt -o def "12 May 18 17:30 MST" +1Y +3M +20s
2019/08/12 17:30:20

$ dt -o def 1526113800 +1Y +3M +20s
2019/08/12 17:30:20# unix 秒

自動判断されるフォーマット

自動判断されるフォーマット

入力フォーマットを指定

# unix ミリ秒
$ dt -i unixm -o def 1526113800000 +1Y +3M +20s
2019/08/12 17:30:20

# カスタムフォーマット
$ dt -i "02-Jan-06 15:04:05" -o def "12-May-18 17:30:00" +1Y +3M +20s
2019/08/12 17:30:20

出力フォーマット

デフォルトは入力フォーマットと同じ。

$ dt "2018-05-12 17:30:00" +1Y +3M +20s
2019-08-12 17:30:20

$ dt 1526113800 +1Y +3M +20s
15300622820

出力フォーマットを指定

$ dt -o def 1526113800 +1Y +3M +20s
2019/08/12 17:30:20

$ dt -o "02-Jan-06 15:04:05" 1526113800 +1Y +3M +20s
12-Aug-19 17:30:20

ヘルプ

$ dt --help
# ...

インストール

Homebrew

$ brew tap ebc-2in2crc/dt
$ brew install dt

Developer

$ go get -u github.com/ebc-2in2crc/dt/...

手動

https://github.com/ebc-2in2crc/dt/releases からダウンロードした zip ファイルを展開した中にあるファイルを、パスの通ったディレクトリに入れる。

動機

DB に格納されている日付が unix 秒だったり、それに対する加減算をしたり、入力と出力で日付のフォーマットが異なっていたりするのが面倒くさくて作りました。date コマンドでも同じことはできなくもありませんが……

実装言語が Go なのは、以前チュートリアルをやったきり触っておらず文法そのほかすべてをきれいに忘れてしまったので、再入門するつもりで選びました。

まとめ

バグとかあると思いますし、「おそいよ!」とか「こーしたら使いやすくなる」とかあったら、イシュー作ったり声かけてもらえると嬉しいです。

ということで dt コマンド の紹介でした。

西暦と和暦を変換する wareki コマンドを作りました

西暦と和暦を変換するコマンドを作りました => Docker に対応しました

github.com

書くこと

  • なにができる?
  • 使い方
  • インストール
  • 動機
  • まとめ

なにができる?

  • 西暦を和暦に変換
  • 和暦を西暦に変換

これだけをしてくれるコマンドです。

使い方

西暦を和暦に変換

$ wareki 2018
H30

西暦を省略すると今年の和暦に変換

$ wareki
H30

$ date "+%Y/%m/%d"
2018/05/10

和暦を漢字で出力

$ wareki --kanji
平成30

年月日を指定すると厳密に変換

$ wareki 1989/01/08
H1

$ wareki 1989/01/07
S64

和暦から西暦に変換

$ wareki --heisei 30
2018

$ wareki --showa 64
1989

ヘルプ

$ wareki --help
NAME:
  wareki - 西暦を和暦に変換する

USAGE:
  wareki [options] [AC]

DESCRIPTION:
  AC に指定した西暦を和暦に変換します.
  和暦の元号は西暦に応じて自動的に決まります. たとえば, 1989/01/08 を指
  定すると H1 (平成1) に, 1989/01/07 を指定すると S64 (昭和64) に
  なります.
  AC は省略でき, デフォルト値はシステム日付になります.
  デフォルトでは, 元号は英大文字1文字で出力しますが (e.g. H) --kanji オ
  プションを指定することにより漢字で出力することもできます (e.g. 平成)
  また, --meiji, --taisho, --showa, --heisei オプションにより, 和暦か
  ら西暦に変換することもできます.

OPTIONS:
  --meiji value, -M value   明治から西暦に変換します (default: 0)
  --taisho value, -T value  大正から西暦に変換します (default: 0)
  --showa value, -S value   昭和から西暦に変換します (default: 0)
  --heisei value, -H value  平成から西暦に変換します (default: 0)
  --kanji, -K               元号を漢字で出力します
  --help, -h                このヘルプを表示します
  --version, -v             バージョンを表示します

インストール

Homebrew

$ brew tap ebc-2in2crc/wareki
$ brew install wareki

Developer

$ go get -u github.com/ebc-2in2crc/wareki/...

手動

https://github.com/ebc-2in2crc/wareki/releases からダウンロードした zip ファイルを展開した中にあるファイルを、パスの通ったディレクトリに入れる。

動機

「今年は平成何年だったかな?」が覚えられなくて作りました。ブラウザで「平成」をググればすぐに出てくるのですが、それも面倒だったので。

実装言語が Go なのは、以前チュートリアルをやったきり触っておらず文法そのほかすべてをきれいに忘れてしまったので、再入門するつもりで選びました。

まとめ

バグとかあると思いますし、「おそいよ!」とか「こーしたら使いやすくなる」とかあったら、イシュー作ったり声かけてもらえると嬉しいです。

ということで wareki コマンド の紹介でした。

外から得られた学びを自分たちの現場に適用するには? まずは、現場の状況や制約に照らし合わせることから始めよう - カイゼン・ジャーニーを読んで

私たちも数多くの挫折を味わってきました。やはり、現場を変えるなんて一人では進めることができないのでしょうか。
 そんなことはありません、ということが言いたくて私たちはこの本を書きました。一人から行動を起こすことはできます。そして、その行動が次の進展をつくります。この本では、私たち自身がこれまで経験し、実践してきたことを下敷きにして、どのようにして始めて、周りを巻き込み、前進していくのかを具体的に示しました。
 私たちは、この本が現場を変えていく人たちに寄り添う存在になって欲しいと願っています。(Foreword より)

早いものでもう4月。1年の4分の1が過ぎ去った事実をなかなか受け入れられない時期ですが、そんな時期に今年最高の1冊になるだろう素晴らしい本に巡り会えたので、感想を記しておきたいと思います。

読んだのは、Twitter のタイムラインで話題をさらっているカイゼン・ジャーニーです。

www.shoeisha.co.jp

ストーリー仕立てでとても読みやすく、そして全編にわたって学びがあるのですが、その中でも「最初の一歩はこれ!」と強烈に思わされたことが、まさに第1部の第01話に書かれていました。

外に出て知見を得ていくにあたって、覚えておかなければならない大事なことがある。外から得られた学びを、そのまま自分たちの現場や仕事に適用しようとしても、たいていうまくいかない。自分たちの「状況」に照らし合わせてみることが必要だ。(013ページより)

ところで、これはどういうことなのでしょうか? すぐあとに「うまくいかない」具体例が書かれています。

これは、キーワードだけで技術やプロセス、プラクティスを引っ張ってきて、現場に手段のみを持ち込もうとしたときも同じことが言える。キラキラした言葉に心を持っていかれてはならない。(013ページより)

たとえば、自分の Twitter のタイムラインやはてなブログでは、

  • 実業務ではアジャイルなど使い物にならない!と言われた
  • Git を使えないどころか部内には知っている人が誰一人としていなかった
  • テストコードを書くと工数がかかるからダメだと言われた
  • プロジェクトに関係のない活動は一切禁止と言われた

こういったツイートやエントリーを目にしない日はない、IT 業界あるあるネタの1つです。

社内の状況や文化に不満を感じる日々、発奮して社外の勉強会に参加してみた→素晴らしいスピーチ! 実際に成されたカイゼンとその原動力となったプラクティス!→自分も社内でやってみよう!!→よくて無反応、ともすれば猛烈な反発にあう→社内の文化も組織も人もまるでだめ、向上心も技術者としてのプライドもない人たちばかり……ことの大小はあれ、Twitterはてなブログでもよく目にする話しですし、多くの人が経験していることだと思います。

では、どうすればいいのでしょうか?

実際に現場をカイゼンする原動力となった技術やプロセス、プラクティス……そういったものを使わずして、自分たちの現場をカイゼンすることが出来るのでしょうか?

当然浮かび上がってくる疑問ですが、その疑問に対する答えも用意されています。

私たちは、他者の実践の背景にどんな状況、制約があったのかを理解し、自分たちの状況、制約の下ではどのように実践するべきなのか捉え直さないといけない。(013ページより)

自分は SI 業界の経験が長いので、たとえとして ERP パッケージを会社に導入することが浮かび上がりました。

たとえば、高価な ERP パッケージを買ってもそのままではおそらく業務は成り立ちません。会社の業務に合うようにカスタマイズするのか、逆にパッケージに合うように業務を変えてしまうのか、あるいは費用と効果がバランスする妥協点を見つけ出すのか……やり方の幅も深さもさまざまですが、少なくとも ERP パッケージを買って導入して終わり、ではありません。

つまり、技術やプロセス、プラクティスというものも、他者が成功した実践そのままを自分の現場に持ち込もうとしても、うまくいくはずがないということです。成功した他者が実践している技術やプロセス、プラクティスがどんなに優れていても、カイゼンに成功した他者と現在進行系で苦しんでいる自分の現場とでは、そもそも練度がまったく違いますから、成功した他者が実践していることをそっくりそのまま自分の現場に持ち込むのは非現実的です。

実際に現場をカイゼンする原動力となった技術やプロセス、プラクティスは当然持ち込むし使うとしても、なにを・いつ・どうやって・どこに・どれくらい持ち込むのか、これを考えて持ち込まなければなりません。

自分の現場はなにが足りていないのか? 組織の文化はどうなっているのか? その組織を構成する一人ひとりはどう考えているのか? 日々忙殺されている業務のなかで、何ができるのか・何からなら始めていけるのか? ……それを考えずして技術やプロセス、プラクティスを持ち込んでも労多くして功少なし、でしょうが、それを考えることができれば、それがカイゼンを成功させる第一歩になる。第1部の第01話の解説役である石神氏はそう説いています。

これを読んだとき、「この本は絶対にすごくいい本だ」と感じました。そして、実際に読み終わってもその印象はまったく変わりませんでした。
折に触れて読み返したい、そう思った本です。

最後に、誰もが気になる江島のモデルについて Afterword から引用させてもらって、感想を〆ます。

江島には特定の中身なんてなくて、状況を変えようと奮闘する人たちそれぞれの人にとっての分身なのだと思います。私たちは、この本の主人公が読者のみなさんの分身であって欲しいと思っています。
日本中の現場に、この本のようなお話が生まれることを願っています。今度は、あなたのお話を聞かせて欲しい。主人公はあなただ。(Afterword より)