IE9以降のブラウザとNede.jsで動きます。 AMDでも動くはずです。 試していません。動かなかったら教えてください。
どんなライブラリか?
こんな感じ
dateAdder(new Date(2014,10,27), {days: 1}) //Thu Nov 28 2014 00:00:00 GMT+0900 (JST) dateAdder(new Date(2014,10,27), {hours: 1}) //Thu Nov 27 2014 01:00:00 GMT+0900 (JST) dateAdder(new Date(2014,10,27), {days: 1, hours: 1}) //Thu Nov 28 2014 01:00:00 GMT+0900 (JST)
JavaScriptのDateオブジェクトに足し算したり引き算したりする関数です。
なぜ作ったか?
ledsun/generate-google-calendar-url · GitHubを使ってGoogleカレンダー用のURLを作る時に使おうと思いました。 予定に開始時刻だけあって終了時刻が無かったら、終了時刻を開始時刻の1時間後に設定します。
どうすればJavaScriptで一時間後の時刻が得られるでしょうか?
標準的なJavaScript
一般にJavaScriptで時間を足す処理を書くとこうです。
var newEnd = new Date(date.getTime()) newEnd.setHours(date.getHours() + value)
二行必要なのが、ちょっと嫌いです。
StackOverflowで検索しても似た感じです。
var today = new Date(); var tomorrow = new Date(today); tomorrow.setDate(today.getDate()+1);
npmパッケージ
111247パッケージを誇るnpmを検索してみます。
parseメソッドやformatメソッドはちょっと余計です。 addメソッドの実装 は参考になりそうです。
1つのメソッドでget/setを両方やるjQueryぽいインターフェースは頂けません。 他のインターフェースと混ぜると辛いです。 実装やテストもコストが高いです。
Math Functionsに加算があります。新しいオブジェクトを返す、非破壊的なメソッドなのも良いです。 ただ、
- 加算と減産を分ける価値が分かりません
- 比較メソッドの価値も分かりません。getTimeすれば良くないです?
- min/maxはreduceで十分な気もします
- サポートしている単位が多いのも気になります。decadeって使います?10年足せば良くないですか?
名前がかっこいいです。 でも欲しいのはDateオブジェクトの代替えではありません。
そんな訳で、手頃なものが無いので作りました。
作っていた時の四方山話
Dateの標準API
主にゲッターセッター。 妙に統一感がありません。
特に年月日
- getFullYear
- getDate
- getMonth
時以下は単位複数形で統一されて居るのに
英語的には正しいのかもしれませんね・・・。
日付のテストコードの話
https://twitter.com/ledsun/status/538131617382817792
変更した日付の値が期待通りかassertで比べたいです。 ところがassert.equalもstrictEqualも時刻の比較はしてくれない。 オブジェクトが同一かどうかを比較します。
結論からいうと頭に+を付けて数値に変換して比較しました。
オレオレassertはダメ
こんなの
var assertDate = function(expct, actual, message){ assert.equal(expect.getTime(), actual.getTime(), message) }
原則的にオレオレassertは悪手です。
- オレオレassertの動作を理解しなくてはいけません
- 関連のあるテストコード以外のコードも読まなくてはいけません
テストコードは定石がすくないので、 他人が書いたテストコードはメンテナンスしづらいです。 テストコードを書くコストが減っても、読むコストが増えると状況は悪化します。
getTime
最少単位で比較すれば良いのでミリ秒に変換して比較します。
assert.equal(expect.getTime(), actual.getTime())
毎回getTimeを書くのは面倒です。
Date系ライブラリのテストコードを見てみましょう。
valueOf派
https://github.com/JerrySievert/date-utils/blob/master/test/date-new-test.js#L11
assert.equal(expect.valueOf(), actual.valueOf())
プリミティブ値の比較は仕様上好ましそうです。 getTimeとメソッド名の長さも得られる値も同じです。
数値にキャスト派
https://github.com/jquense/date-math/blob/master/test.js#L27 https://github.com/timruffles/immutable-date/blob/master/test.js#L11
assert.equal(+expect, +actual)
JavaScriptの標準から外れずに、最も短く書けるのでこの記法にしました。
開発環境を自慢
saucelabsでクロスブラウザテスト
ソースコードはES5対応です。 実際に各ブラウザで動くかはsaucelabsで確認しました。 mochaで書いたテストコードをzuulでブラウザで動かしました。
最初はもっとたくさんのブラウザでテストしました。 時間掛かかりました。 各ブラウザの動作可能な最も古いバージョンだけテストすることにしました。 1セット約2分掛かかります。
最初はtestling使う予定でした。 ローカルでテストして、travis.ci設定して、githubにpushしても反応がありません。 調べてみたら ci.testling.com Service Timeout · Issue #88 · substack/testling · GitHub でした。
UMD.js
generate-google-calendar-url もブラウザとNode.jsで動きます。 EventEmitter2を真似て手で書きました。
UMD.jsという標準を知り、 従うことにしました。 ソフトウェア開発のコツは「解決したい問題以外は他人に押し付ける」です。
手で書いたら、ソースコードが読みにくくなったのでgulp-umdを使いました。
元が1ファイルだったので楽でした。 複数ファイルに分けるとどうなるんでしょうね?
Jxck_さんの教え
"script" を "npm run" で実行する場合は、パスが自動で通る
必要なコマンドはすべてnpmから実行出来るようにしました。
- npm run build
- npm test
- npm run browser
- npm run saucelabs
npm run saucelabs
はtravis.ci用なので人間はあまり実行しないはずです。
t-wadaさんの教え
細かすぎて伝わらない package.json 小ネタ三選 - t-wadaのブログ
一通りやりました。
もちろんpower-assert使いました。 途中でテストコマンド間違えてエラー詳細で無くなったらパニックに陥りました。 知らず知らずのうちに頼り切りです。
まとめ
npmモジュールの開発は簡単ですね<3
ソースコードは40行しかないのに、こんなに書くことがある!