読者です 読者をやめる 読者になる 読者になる

rrule.js簡易メモ

JavaScript iCal

icalのrruleから該当日付を計算してくれるjavascriptライブラリ。
超便利、だが色々問題があったのでメモ。

ビルド環境:node + bower + webpack
※BowerWebpackPluginでbower取得したモジュールはパス書かなくてもいいようにしている。

mainのパスが違います問題

bowerで最新版(2.1.0)取得後、とりあえずライブラリを読み込んで

var RRule = require('rrule').RRule;

ビルド。

Module not found: Error: Cannot resolve 'file' or 'directory' ./rrule.js in ・・・

なぜ・・・

rruleのbower.jsonを確認すると、"main": "rrule.js"となっているが、実際はlib/rrule.jsにある。勘弁して。
とりあえず"main": "lib/rrule.js"に直すと、ビルドエラーは解消。

README.mdが間違ってます問題

12月の土日を指定するrruleを指定してみる。

var rrule = new RRule({
    freq: RRule.WEEKLY,
    byweekday: [RRule.SA, RRule.SU],
    dtstart: new Date(2016, 11, 1),  // 2016/12/01
    until: new Date(2016, 11, 31)    // 2016/12/31
});

実行すると

Uncaught TypeError: Cannot read property 'WEEKLY' of undefined

なぜ・・・スペルミスとかしてないし。
デバッグするとRRuleがundefinedになっている。というわけで、

// var RRule = require('rrule').RRule;
var RRule = require('rrule');

にすると正常に。webpack使っているから? それならREADME.mdが間違っているとは言えない。

と思いきや、調べる過程でソースを読んでいて気づいた。

    module.exports = {
        RRule: RRule
        // rruleset: rruleset
    }

rrulesetが公開されてない?
gitのrrule.jsと比較すると、ソースが全然違う。。。
どうやらgitに表示されているソースはREADME.mdは2.2.0-devで、正式リリースの2.1.0ではない様子。
できればRRuleSetも使いたかったが、管理が怪しげな2.2.0-devを使う気にはなれないので、そこは諦めることにする。

動作確認

根本のソースは正しいロジックなのか疑心暗鬼になりつつ動作確認してみる。

// 12月の木土
var rrule = new RRule({
    freq: RRule.WEEKLY,
    byweekday: [RRule.TH, RRule.SU],
    dtstart: new Date(2016, 11, 1),  // 2016/12/01(木)
    until: new Date(2016, 11, 31)    // 2016/12/31(土)
});
rrule.all();
=> 結果がDate配列で返される
Thu Dec 01 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 03 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 08 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 10 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 15 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 17 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 22 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 24 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 29 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 31 2016 00:00:00 GMT+0900 (東京 (標準時))

dtstart ≦ ● ≦ untilで木・土が列挙されているので問題なさそう。

between()で期間を絞ってみる。

// 12/08(木)から12/24(土)の間だけ取得する
rrule.between(new Date(2016, 11, 8), new Date(2016, 11, 24));
=>
Sat Dec 10 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 15 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 17 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 22 2016 00:00:00 GMT+0900 (東京 (標準時))

rrule.between(new Date(2016, 11, 8), new Date(2016, 11, 24), true);
=>
Thu Dec 08 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 10 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 15 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 17 2016 00:00:00 GMT+0900 (東京 (標準時))
Thu Dec 22 2016 00:00:00 GMT+0900 (東京 (標準時))
Sat Dec 24 2016 00:00:00 GMT+0900 (東京 (標準時))

第3引数にtrue指定すると、between()引数の日時も含んでくれる。

RRuleSetが使えないは残念だが、rrule解析してくれるだけでもかなりありがたい。

Javaで年号を追加する

Java

POIを使って年号(元号、和暦)付き日付をExcelに出そうとすると少し面倒なことになった。
年号関連のセル書式判定がイマイチな処理になる上、Excelのバージョンが変わると仕様が変わるかもしれない。

そんなわけでJava側で処理した方がいいという考えになったが、年号をカスタマイズしたい時にどうするのか調べたのでメモ。

年号が変わるなんて先の話だと思っていたが、現実になりそうだよなぁ。
日本中のSEが狂喜乱舞するんだろうなぁ。  

・・・の前に、Java8で日付/時刻API(Date and Time API)が追加されているので、そちらでの年号の扱いも確認。
参考:日本人のためのDate and Time API Tips - Programming Studio

Java8 Date and Time API概要

Date and Time APIでは、日付のみ、時刻のみ、日付/時刻の3種クラスが用意されていて、用途によって使い分ける。

  • 日付のみ:通常はLocalDateクラスを使う。ChronoLocalDateを実装したクラスが当てはまる。
  • 時刻のみ:LocalTimeクラスを使う。元インターフェイスは特に無し?
  • 日付/時刻:通常はLocalDateTimeクラスを使う。ChronoLocalDateTimeを実装したクラスが当てはまる。

now / of

現在日時を取得する際はnow()、指定日時を取得する際はof()を使う。

LocalDate date = LocalDate.now();  // 今日
LocalTime time = LocalTime.now();  // 現在時刻
LocalDateTime dateTime = LocalDateTime.now();  // 現在日時

LocalDate date = LocalDate.of(2016, 1, 2);  // 2016年1月2日
LocalTime time = LocalTime.of(12, 34, 56);  // 12時34分56秒
LocalDateTime dateTime1 = LocalDateTime.of(2016, 1, 2, 12, 34, 56);  // 2016年1月2日 12時34分56秒
LocalDateTime dateTime2 = LocalDateTime.of(date, time);  // 2016年1月2日 12時34分56秒

at / to

クラス間の変換メソッドがat・・・、to・・・で用意されている。
atは情報を足すイメージ、toはそのまま変換するイメージ。

LocalDate date = LocalDate.of(2016, 1, 2);  // 2016年1月2日
LocalTime time = LocalTime.of(12, 34, 56);  // 12時34分56秒

// 全て2016年1月2日 12時34分56秒
LocalDateTime dateTime1 = date.atTime(time);
LocalDateTime dateTime2 = date.atTime(12, 34, 56);
LocalDateTime dateTime3 = time.atDate(date);
LocalDateTime dateTime = LocalDateTime.of(2016, 1, 2, 12, 34, 56);  // 2016年1月2日 12時34分56秒
LocalDate date = dateTime.toLocalDate();  // 2016年1月2日
LocalTime time = dateTime.toLocalTime;  // 12時34分56秒

format / parse

DateTimeFormatterを使う。DateTimeと付いているが日付/時刻専用ではなく、LocalDate, LocalTimeも同じ。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

LocalDate date1 = LocalDate.of(2016, 1, 2); 
System.out.println(date.format(formatter));  // 2016/01/02

LocalDate date2 = LocalDate.parse("2016/01/02", formatter);  // 2016-01-02のLocalDate


あと演算系のAPIも用意されているが、今回は本筋と逸れるので省略。

年号を扱う

年号を扱うにはChronoLocalDate実装(日付のみ系)のクラスJapaneseDateを使う。
now(), of()が用意されていて、かつ年号ベースでのof()もある。

JapaneseDate date1 = JapaneseDate.now();
JapaneseDate date2 = JapaneseDate.of(2016, 1, 2);
JapaneseDate date3 = JapaneseDate.of(JapaneseEra.HEISEI, 28, 1, 2);  // 平成28年1月2日 = 2016/01/02

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gyy年MM月dd日");
System.out.println(date2.format(formatter));  // 平成28年01月02日

JapaneseDateは出力用

JapaneseEraは明治以降の年号を扱うEnum型。おまけに各valueが直感的でない。
明治[-1]、大正[0]、昭和[1]、平成[2]になっている。
1970年が起点なので昭和が1ということらしい。 なのでプログラム上で使うならJapaneseEraを使ったof()は使わ(え)ない。

更にJapaneseDateにはparse()がない。ナンテコッタイ。
おそらく"平成28年1月2日"という文字列をparseするのが面倒なのだろう、気持ちは分かる。
ただそのおかげで、文字列"2016/01/02"からJapaneseDateを直接作る術も無い。
一度LocalDate(or LocalDateTime)にparse()して、from()で読み込むのが正解か?

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date1 = LocalDate.parse("2016/01/02", formatter);
JapaneseDate date2 = JapaneseDate.from(date1);

あと、JapaneseDateクラスはあるが、JapaneseDateTimeというのは無い。

そんなわけで、日付はLocalDateやLocalDateTimeで処理するのが基本で、表示などのformatにだけJapaneseDateを使うことになるだろう。

年号を追加する (!! Java8ではできません !!)

参考:Javaで新元号に対応する - Qiita
jre/lib/calendar.propertiesに年号行を追加する。
仮に「2017/10/01から新年号『現合』開始、短縮文字は『G』」だとしたら、

name=\u73fe\u5408,abbr=G,since=1506783600000

と書く。 \u73fe\u5408は「現合」をnative2asciiした文字、1506783600000は1970/01/01からのミリ秒。

テストする。

JapaneseDate date1 = JapaneseDate.of(2016, 1, 2);  // 2016/01/02 => 平成28年1月2日
JapaneseDate date2 = JapaneseDate.of(2020, 7, 24);  // 2020/07/24 => 現合4年7月24日 (東京オリンピック開会式)

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gyy年MM月dd日");
System.out.println(date1.format(formatter));  // 平成28年01月02日
System.out.println(date2.format(formatter));  // 304年07月24日 ?????

304年てなんぞや。。。
これ年号が「3」で「04年」と表示されている様子。たぶん3はJapaneseEraのvalue値だと思われる。

つまり、壮絶に時間を費やしてダメだったという。。。
できると書いてあるサイトがあったがエアプということか。まともなSEならエアプは恥ずかしいからやめましょう。

誤解の無いように言うと、参考:Javaで新元号に対応する - Qiitaに書いてあるcalendar.propertiesに年号追加する方法自体は間違っていない。
calendar.propertiesに追加してDateFormatでDate型をformatすれば、意図した通りに追加した年号が計算されて表示される。
Date and Time APIがcalendar.propertiesに対応できていない、というかこれバグのレベルなんじゃ。。。

結論

  • 年号追加はJavaのバージョンアップまで待つ、という人

    • Date and Time APIを使っておく
  • バージョンアップできない、先行で年号追加したい、という人

    • Date and Time APIを使いつつ、年号表示の処理だけDate(+ DateFormat)使う

iCalのrruleを必要な分だけ抜き出して使う

つれづれ iCal

システム上でスケジュール情報を登録・保持するようにしたいが、一から考えるのは面倒、特に繰り返し条件が。
なのでiCalのrruleの仕様をそのまま踏襲することにするが、日本語情報がほとんど無い。
一番参考になったのがここ。
CalDAV(iCalendar)のrrule(繰り返し設定)一覧まとめ
FREQを基点にしてまとめ直す。

FREQ 繰り返し条件 DAILY WEEKLY MONTHLY YEARLY
UNTIL 終了日
BYMONTHDAY 月の日付指定 × ×
BYDAY 曜日指定 ×
INTERVAL 間隔
BYMONTH 月指定 × × ×
COUNT 繰り返し回数

ここから機能を絞っていく。
まず前提として、開始日・終了日は必須で保持する。
無期限の予定とかほぼ入れない。特に仕事では大抵年末や年度末で期間を切る。

  • FREQ:DAILY

    • そもそも要る? 開始 - 終了日を指定して連続させればいい
    • 仕事上のスケジュールで2日おき/3日おき(INTERVAL)とかお目にかからない
    • 大体は毎週 x曜日とかになる
  • FREQ:YEARLY

    • たぶん要らない。毎年この日、という指定はあれば便利だと思うけど
    • 毎年予定は忘れないようにするために使うことが多い気がする
    • 必要なら個人のGoogleカレンダーに入れてもらう、今回のシステムでは必要性がない
    • 関連してBYMONTHも不要になる
  • UNTIL

    • 要らない。終了日で表現すればいい、二重に持つのはややこしい
  • COUNT

    • 要らない。仮に「毎週火曜 全5回」とかの予定があっても、普通は最終日を意識するし、終了日を指定すればいい

というわけで棚卸しした結果こうなる。

FREQ 繰り返し条件 WEEKLY MONTHLY
BYMONTHDAY 月の日付指定 ×
BYDAY 曜日指定 ×
INTERVAL 間隔
  • 毎日 (開始 - 終了日の範囲指定)
  • 毎週 x曜日、2(3, 4・・・)週ごとの x曜日
  • 毎月 x日、2(3, 4・・・)ヶ月ごとの x日

これだけ表現できれば大抵の予定はカバーできるだろう。

SourceTreeアップデート(しても大差なし)

つれづれ

SourceTreeダウングレードしたかったが、する時間などもちろん無く、その間にバージョンアップ通知が来たのでバージョンアップしてみる。現在は1.9.6.1。

ツリー表示は復活して見やすくはなったが、フォルダ内のファイル全選択ができないのでポチポチ選択しないといけないのは変わり無し。
あと重くなったのはPC自体のせい? 常時起動しているわけではないからいいけど、もう少し何とかして欲しいところ。

SourceTreeが1.8でヘタレになった

つれづれ

SourceTreeをバージョンアップ(1.7.0 → 1.8.3)したら、ファイルのツリー表示できなくなった。
おかげでフォルダ内全部の選択ができずに、1ファイルずつポチポチ選択しないといけない。

どうも新UIはバグが多く評判悪い様子。大きな変更するならバージョン番号をメジャーバージョンで変えて欲しい。
それならしばらく様子見するからバージョンアップしてなかっただろうし。
時間に余裕ができたらバージョンダウン予定。

JPA的なもの(回顧)

Java つれづれ

Javaがまだ1.3とか1.4で、自分も駆け出しのペーペーだった頃、社内製品の開発に携わったことがあった。
そこで使われていた自作フレームワークにはO/Rマッピングや自動mergeなどの機能が実装されていた。

あの頃に比べれば、Javaのバージョンも上がりライブラリも豊富にある現在は便利になったとつくづく思う。

それでも開発の苦労が減らないのは当時と変わらない。永遠のテーマなんだろうな。

Underscore.jsおさらい6(Utility、Chaining)

JavaScript Underscore.js
noConflict _.noConflict()

underscoreは「_」がデフォルトだが、他のライブラリで使っていると衝突するので衝突防止。

var underscore = _.noConflict();


identity _.identity(value)

引数自身を返す。f(x) = xの処理。underscoreのiterateeデフォルト判定。

var stooge = {name: 'moe'};
stooge === _.identity(stooge);
=> true


constant _.constant(value)

value自身を返す関数を作る。

var stooge = {name: 'moe'};
stooge === _.constant(stooge)();
=> true


noop _.noop()

空の関数 function(){} を返す。

times _.times(n, iteratee, [context])

n回分 iterateeを実行する。各結果の戻り値が詰められた配列を返す。
iterateeの引数はindex。

_.times(5, function(n) { return n * n; });
=> [0, 1, 4, 9, 16]


random _.random(min, max)

min以上、max以下の整数値乱数を返す。

mixin _.mixin(object)

underscore関数を新たに定義する。

_.mixin({
  capitalize: function(string) {
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  }
});
_("fabio").capitalize();
=> "Fabio"
_.capitalize("mario");
=> Mario


iteratee _.iteratee(value, [context])

underscoreのiteratee用関数を作成する。
valueがnullなら.identity、オブジェクトなら.matcher、関数ならその関数、それ以外(文字列とか)なら_.propertyを返す。

uniqueId _.uniqueId([prefix])

1から順番にカウントアップした数字を返す。prefixがあれば接頭語として付ける。

_.uniqueId();
=> 1
_.uniqueId('id');
=> id2
_.uniqueId('no_');
=> no_3


escape _.escape(string)

HTMLエスケープする。

unescape _.unescape(string)

HTMLアンエスケープする。

result _.result(object, property, [defaultValue])

オブジェクトのプロパティ値を返す。
指定したプロパティがメソッドならばメソッドの戻り値を返す。 propertyに指定したキー、メソッドが無い場合、defaultValueが指定されていればdefaultValueの値が返る。

_.result(object, 'cheese');
=> "crumpets"
_.result(object, 'stuff');
=> "nonsense"
_.result(object, 'meat', 'ham');
=> "ham"


now _.now()

現在時刻のミリ秒を返す。

template _.template(templateString, [settings])

backboneを使うなら再優先で覚えるべきもの。
文字列テンプレートにオブジェクトの値をマッピングさせる、いわゆるテンプレートエンジン。
JavaならVelocity、PHPならSmartyをイメージすればいい。
<%= %>はそのまま、<%- %>はHTMLエスケープして出力する。<% %>を使えばコードも埋め込める。
_.templateSettingsでテンプレート文字を好きに変更できる。

var compiled = _.template("hello: <%= name %>");
compiled({name: 'moe'});
=> "hello: moe"

var template = _.template("<b><%- value %></b>");
template({value: '<script>'});
=> "<b>&lt;script&gt;</b>"

var compiled = _.template("<% print('Hello ' + epithet); %>");
compiled({epithet: "stooge"});
=> "Hello stooge"

_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g
};

var template = _.template("Hello {{ name }}!");
template({name: "Mustache"});
=> "Hello Mustache!"


chaning

underscoreの各関数は関数呼び出し風にも書ける。

_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

そのため、特にコレクション系なんかで複数の処理をまとめてやりたいときに、メソッドチェーンで書ける。
Javaな人はJava8のStream APIと言えば分かる。

chain _.chain(obj)
value _(obj).value()

chainはメソッドチェーン用の関数。引数のobjをラップする。
valueは最終的な値の取り出し用。

_.chain(lyrics)
  .map(function(line) { return line.words.split(' '); })
  .flatten()
  .reduce(function(counts, word) {
    counts[word] = (counts[word] || 0) + 1;
    return counts;
  }, {})
  .value();

=> {lumberjack: 2, all: 4, night: 2 ... }

おさらい、完了!