Date#formatとNumber#format2007年05月22日 03時52分11秒

.NET Framework風、再び

以前のエントリ「String.format」でやりたいと思っていた、書式指定子のサポートをとりあえずやってみた。

ダウンロード

こちらから。

能書き

元々.NET Frameworkの劣化コピー的な発想なので、DateとNumberに書式指定可能なformatメソッドを追加し、String.formatのプレースホルダに指定した書式指定子をそのまま各クラスのformatに渡すように考えた。

Dateはdateformat.jsを使わせてもらえば楽だったんだけど、クリエイティブ・コモンズのライセンス適用範囲に不安があったので結局自前で実装することにした。 ただし、書式指定文字のエスケープができない、パーサの実装がないなどの面であきらかにdateformat.jsより劣っているのでorz、たいていはそちらを使ったほうがよいと思われる。 (あと、組み込みオブジェクトの拡張が嫌いな場合とか)

なくても実装は可能だったが、prototype.jsを前提に実装したため、prototype.jsを必要とする。完全に確認はしていないが、1.4.0以降なら問題ないはず。

後述の通り、Date#format、Number#formatの実装に加え、String.formatも書式指定子サポートを追加して実装しなおした。

Formattableインターフェイス

Date、Numberとも似たようなことをするので、formatメソッドはFormattableオブジェクトに実装し、Date.prototoyep、Number.prototypeへ重ねるようにした。 また、書式指定子の定義をそれぞれのコンストラクタ関数のメンバ「formatters」として定義し、formatメソッド内からthis.constructor経由で参照させている。 具体的な実装は以下の通り。

var Formattable = {
	defaultFormat : "",
	format : function(format) {
		var formatters = this.constructor.formatters || [];
		
		return formatters.inject( format || this.defaultFormat, function(result, formatter) {
			return result.replace( formatter.pattern, function() {
				return formatter.format.apply( this, [ this ].concat( $A( arguments ) ) );
			}.bind( this ) );
		}.bind( this ) );
	}
}

書式指定子の基本設計

pattern(RegExp)プロパティとformatメソッドをメンバに持つオブジェクトを定義した。

formatメソッドの第一引数はフォーマット対象の値、第二引数以降にpatternプロパティの正規表現でexecした結果が渡される。例えばDate向けの年フォーマットは

{
	pattern : /yyyy|yy|y/,
	format : function(value, pattern) {
		return pattern.length == 1 ?
			value.getFullYear().toString() :
			( "0000" + value.getFullYear() ).slice(-1 * pattern.length);
	}
}
と定義してあり、formatの第二引数patternに一致文字列全体が渡されるので、1文字かそれ以上かを判断して0詰を行うかそのままtoStringするかなどの分岐をしている。

Date、Numberとも同一の設計のフォーマット情報を配列で持たせ、それをループ処理で適用している。

Dateの書式指定子

dateformat.jsに酷似しているが、ミリ秒の指定方法と解釈が若干違う

書式指定子 説明
yyyy、yy、y 年の指定。yyyyは4桁、yyは下2桁、yは桁詰なしになる。
MM、M 月の指定。MMは2桁、Mは桁詰なしになる。
dd、d 日の指定。MMは2桁、Mは桁詰なしになる。
HH、H 時の指定。HHは2桁、Hは桁詰なしになる。常に24時間書式になる。
mm、m 分の指定。mmは2桁、mは桁詰なしになる。
ss、s 秒の指定。ssは2桁、sは桁詰なしになる。
fff、ff、f ミリ秒の指定。桁数はfの桁数に一致し、実際のgetMilliseconds()よりも指定桁数が少ない場合は丸められるが、丸め方は処理系依存になる(みたい。IEとFirefox/Operaで(0.150).toFixed(2)の結果が違う)。

Numberの書式指定子

3桁のカンマ区切り書式、ゼロ詰、および桁数指定の16進数の書式を定義している。

書式指定子 説明
#,##0[.0+] 3桁ごとにカンマで区切る書式で、オプションで小数部を指定できる。小数部は0を必要桁数だけ指定する。例えば小数点以下第4位まで必要な場合は"#,##0.0000"とする。
0+[.0+] 指定桁数分だけ0詰を行う書式。オプションで小数部の指定も可能。整数部の桁数は書式指定子の0の桁数と実際の整数部の桁数の大きいほうが採用される。
x[n]、X[n] 16進数。nで指定した桁数になるようゼロ詰される。xの大文字小文字の違いは9より大きい数のアルファベット文字に反映される。

テスト

Date
Number

宿題

  • パースメソッドの実装 → 今のformattersの定義を流用したいが方法が浮かばないorz
  • Number#formatのパーセント対応 → なくてもいいけど

今日のコネタ2007年05月22日 12時22分36秒

dankogai氏の釣果がものすごい

昨日のコネタでも触れたそろそろPHPに関して一言いっとくかだが、さすがの影響力でトラバもコメントもなかなか香ばしいことになっている。(さすがにそろそろ落ち着いてきたみたいだけど)

まぁ、ヘタレなdara-jは普段はJavaScriptとかC#とかユル系の言語ばかりなので、リファレンスの「関数」群を見ただけで辟易するんですけど。昔のvbみたいで。

ともあれ、

PHP を仕事に使う奴は呪われるべきです。
てのにはワロタ。

グリッド表示ブックマークレット

あるSEのつぶやきさん経由で

これ、楽しいなぁ。目盛が表示されるだけなんだけど。ずるずると引きずってむやみに画像のサイズ計測したり。Firebug立ち上げりゃすむのにね。

ぐーぐる先生の地図もそうだけど、ずるずると引きずるのはなんか気持ちいい。

おっと、もうネタぎれかよ。

Ext JSとかぶってた2007年05月22日 16時21分11秒

Ext.jsにString.formatあるじゃん

「String.format.apply」という変わったキーワードでこのブログにたどり着いた人がいたみたいで、他にどんなんひっかかるかなと思ってぐーぐる先生に尋ねてみた。こちら

するとここのブログのエントリの他にExt JS(旧:yui-ext)関連が引っかかる。おりょ?

最初に目に付いたyui-ext0.40-alpha時の「yutil.js」を覗いたら、なんか、まんま「String.format」メソッドてのがあるじゃないですか。

String.format( "{0} ms", milli );
とかなんとかって使うやつ。モロにここに書いたのと似てるやつですよ。

いちおう現在(=Ext JS)でどうなってるか探したら、「Ext.js」中にやっぱりまんま定義されてるし。

まいったなー、こんな有名ドコのライブラリとモロかぶるとは...

まあ、おおもとの発想は.NETだし、あるっちゃあるものなのかなぁ。

ちなみにExtのやつはここの以前のバージョンと同じく書式指定子はなく、単純にパラメータ位置に対するプレースホルダ置換のみなんだけどね。ふぅ。

Number#toFixedの解釈2007年05月22日 17時55分04秒

またEnjoy*Studyさんの後追いかよ...

Date#formatのミリ秒フォーマットの実装部分で疑問を持ったのだが、Number#toFixedの丸め方が実装依存みたいだ。ちょっと気になって検索したら、dateformat.jsの作者さんがやはり「Number#toFixedがブラウザによって結果が異なる場合がある」なんてエントリをあげておられた。つくづく後追いしてるな > 俺

まあ、がんばって調べてみるか。

Under Translation of ECMA-262 3rd Editionを一生懸命読んでみる

一旦読んで「さっぱりだぜ」とあきらめていたがもう一度Nuber.prototype.toFixedのところを読み直し、Enjoy*Studyさんが例示しておられたパターンでどうなるかを確認してみた。

  • ステップ1から。対象の数値が「1.255」、toFixedのパラメータが「2」なので、f = 2x = 1.255となる。0 <= f <= 20 且つ ! isNaN( x ) 且つ 0 <= x <= Math.pow( 10, 21 ) を満たすのでステップ10へ
  • n / Math.pow( 10, f ) - x が限りなく0に近くなることを満たす整数nを考える。 → n / Math.pow( 10, 2 ) - 1.255 → n / 100 - 1.255 なので、n125126 あたりか。
  • 「そのようなnが2個あれば、大きいほうをnとする」とあるので一旦 126 か?とも思ったが、「厳密な数学値が限りなく0に近い」とあるので、1.26 - 1.255と1.25 - 1.255の絶対値を見てみることに。
  • Math.abs( 1.26 - 1.255 ) // → 5.00000000000012E-03(JScript)、0.0050000000000001155(Firefox)
  • Math.abs( 1.25 - 1.255 ) // → 4.99999999999989E-03(JScript)、0.004999999999999893(Firefox)
  • ということで、n125となり、n != 0 なのでm = 125で確定。(たぶん)
  • 後はステップ13 → 14 → 18 とすなおに解釈すると、(1.255).toFixed(2) = 1.25 となる。
なんだ、やっぱIEじゃん。

結論

IEのNumber#toFixedは仕様と違う

...とはいっても、dara-jの解釈が間違ってるかもしんないし...