DateFormatは前方一致だったのか
yyyy/MMという日付書式に、2010/06/15っていう文字列日付を入れてもパースできちゃうっていうのはどうゆう仕様なんだろうか。
JavaのDateFormat - 何言語でも話したいPGの開発日記
気になったので調べてみました。
メソッドは pos によって指定されたインデックスを開始位置としてテキストの解析を試みます。解析が完了すると、pos のインデックスは、使用された最後の文字 (解析では、文字列の最後までのすべての文字を使用する必要はありません) のあとのインデックスに更新され、解析された日付が返されます。更新された pos は、このメソッドの次の呼び出しの開始点を示すのに使用できます。エラーが発生した場合は、pos のインデックスは変更されず、エラーが発生した文字のインデックスに pos のエラーインデックスが設定され、null が返されます。
DateFormat#parseObject (Java Platform SE 6)
DateFormatの基底クラスにあたるFormatクラスは、区切り文字で区切られてる連続した文字列の集まりをパースできるように作られているらしく、そのため解析を完了したらその後に続く文字列は無視する仕様みたいです。
正規表現でのチェックや、日付に変換してから文字列に戻して変換前の文字列になるかっていうチェックでも問題ない気がしますが、コストを重視するなら文字列長のチェックだけ行うのが良いと思いました。また、parseメソッドにParsePositionっていうのを渡してあげると、チェックNGのときにParseExceptionが発生しないようです。これもコスト的なメリットになりそうです。
せっかくなので書いてみました
import java.text.DateFormat; import java.text.ParsePosition; import java.text.SimpleDateFormat; public class DateValidator { public static boolean isValid(String date, String format) { if(date.length() != format.length()) { return false; } DateFormat dateFormat = createStrictDateFormat(format); ParsePosition pos = new ParsePosition(0); return dateFormat.parse(date, pos) != null; } protected static DateFormat createStrictDateFormat(String format) { DateFormat dateFormat = new SimpleDateFormat(format); dateFormat.setLenient(false); return dateFormat; } }
テスト
import static org.junit.Assert.*; import org.junit.Test; public class DateValidatorTest { @Test public void testIsValid() { assertFalse(DateValidator.isValid("2010/06/30", "yyyy/MM")); assertFalse(DateValidator.isValid("2010/6/31", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/06/00", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/06/30<script>...</script>", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/06/31", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/6/3", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("13:57", "hh:mm")); assertTrue(DateValidator.isValid("2010", "yyyy")); assertTrue(DateValidator.isValid("2010/06", "yyyy/MM")); assertTrue(DateValidator.isValid("2010/06/30", "yyyy/MM/dd")); assertTrue(DateValidator.isValid("2010年06月30日", "yyyy年MM月dd日")); } }
あ、書いてから気がつきました。DateFormatはスレッドセーフでないから毎回newしなければいけないと思ってましたが、ThreadLocalクラスを使えば、同一スレッド内で使いまわすことができてもう少し節約できそうです。今度やってみよう。
追記
元記事の方からコメントを頂いて気がつきました。
SimpleDateFormatは各フィールドの桁数はチェックしてくれないのですね。
書式 | yyyy/MM |
日付文字列 | 2010/0000006/ |
とか
書式 | yyyy/MM/dd |
日付文字列 | 1/2/3 |
とかもエラーにならずにパースしてくれます。setLenient(false)しててもエラーになりません。なんてこったい。
さらに、前方一致での解析というのが手伝って
書式 | yyyy/MM/dd |
日付文字列 | 1/2/3あいうえお |
という場合でもエラーにならないため「書式文字列の長さと日付文字列の長さを比較する」という方法が通用しなくなってしまいました。なんてこったい。
「SimpleDateFormatはバリデーションには使えない。やっぱり正規表現しかないのか」と悲しみに暮れていたら、ParsePositionが役に立ってくれました。ParsePostionはパースした文字数を保持しているので、それと書式文字列の長さを比較したらいい感じになりました。
修正版DateValidator
import java.text.DateFormat; import java.text.ParsePosition; import java.text.SimpleDateFormat; public class DateValidator { public static boolean isValid(String date, String format) { if(date.length() != format.length()) { return false; } DateFormat dateFormat = createStrictDateFormat(format); ParsePosition pos = new ParsePosition(0); return dateFormat.parse(date, pos) != null && pos.getIndex() == format.length(); // <-- 解析した長さと書式の長さを比較 } protected static DateFormat createStrictDateFormat(String format) { DateFormat dateFormat = new SimpleDateFormat(format); dateFormat.setLenient(false); return dateFormat; } }
テスト
import static org.junit.Assert.*; import org.junit.Test; public class DateValidatorTest { @Test public void testIsValid() { assertFalse(DateValidator.isValid("2010/06/30", "yyyy/MM")); assertFalse(DateValidator.isValid("2010/6/", "yyyy/MM")); // <- 書式と日付の長さが一致するケース(7文字) assertFalse(DateValidator.isValid("1/2/3あいうえお", "yyyy/MM/dd")); // <- 書式と日付の長さが一致するケース(10文字) assertFalse(DateValidator.isValid("2010/6/31", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/06/00", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/06/30<script>...</script>", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/06/31", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("2010/6/3", "yyyy/MM/dd")); assertFalse(DateValidator.isValid("13:57", "hh:mm")); assertTrue(DateValidator.isValid("2010", "yyyy")); assertTrue(DateValidator.isValid("2010/06", "yyyy/MM")); assertTrue(DateValidator.isValid("2010/06/30", "yyyy/MM/dd")); assertTrue(DateValidator.isValid("2010年06月30日", "yyyy年MM月dd日")); } }
できたにはできたけど、なんだか無理やり感が出てきました。やっぱりparse()したものをformat()で元に戻して、元の文字列と一致するか調べるのがわかりやすくて良いのだろうなあ。
CaseFormat
guava-librariesのCaseFormatというenumを見ていました。
キャメルケースとかハイフン区切りとか大文字小文字とか、文字列の書式を表す列挙のようです。
「helloWorld」を「HELLO_WORLD」に変換してくれる「to」というメソッドが提供されていました。
LOWER_CAMEL.to(UPPER_UNDERSCORE, "helloWorld")); // HELLO_WORLD
エンティティのクラス名からテーブル名に変換するのに使えそうです。
テスト
package com.google.common.base; import static com.google.common.base.CaseFormat.*; import static org.junit.Assert.*; import java.util.Arrays; import java.util.List; import org.junit.Test; public class CaseFormatTest { @Test public void testValues() { List<CaseFormat> actual = Arrays.asList(CaseFormat.values()); assertEquals(Arrays.asList( LOWER_HYPHEN, // 小文字 ハイフン区切り LOWER_UNDERSCORE, // 小文字 アンダースコア区切り LOWER_CAMEL, // 小文字で始まるキャメルケース UPPER_CAMEL, // 大文字で始まるキャメルケース UPPER_UNDERSCORE // 大文字 アンダースコア区切り ), actual); } @Test public void testTo() { String word = "hello-world"; assertEquals("hello_world", LOWER_HYPHEN.to(LOWER_UNDERSCORE, word)); assertEquals("helloWorld" , LOWER_HYPHEN.to(LOWER_CAMEL , word)); assertEquals("HelloWorld" , LOWER_HYPHEN.to(UPPER_CAMEL , word)); assertEquals("HELLO_WORLD", LOWER_HYPHEN.to(UPPER_UNDERSCORE, word)); word = "hello_world"; assertEquals("hello-world", LOWER_UNDERSCORE.to(LOWER_HYPHEN , word)); assertEquals("helloWorld" , LOWER_UNDERSCORE.to(LOWER_CAMEL , word)); assertEquals("HelloWorld" , LOWER_UNDERSCORE.to(UPPER_CAMEL , word)); assertEquals("HELLO_WORLD", LOWER_UNDERSCORE.to(UPPER_UNDERSCORE, word)); word = "helloWorld"; assertEquals("hello-world", LOWER_CAMEL.to(LOWER_HYPHEN , word)); assertEquals("hello_world", LOWER_CAMEL.to(LOWER_UNDERSCORE, word)); assertEquals("HelloWorld" , LOWER_CAMEL.to(UPPER_CAMEL , word)); assertEquals("HELLO_WORLD", LOWER_CAMEL.to(UPPER_UNDERSCORE, word)); word = "HelloWorld"; assertEquals("hello-world", UPPER_CAMEL.to(LOWER_HYPHEN , word)); assertEquals("hello_world", UPPER_CAMEL.to(LOWER_UNDERSCORE, word)); assertEquals("helloWorld" , UPPER_CAMEL.to(LOWER_CAMEL , word)); assertEquals("HELLO_WORLD", UPPER_CAMEL.to(UPPER_UNDERSCORE, word)); word = "HELLO_WORLD"; assertEquals("hello-world", UPPER_UNDERSCORE.to(LOWER_HYPHEN , word)); assertEquals("hello_world", UPPER_UNDERSCORE.to(LOWER_UNDERSCORE, word)); assertEquals("helloWorld" , UPPER_UNDERSCORE.to(LOWER_CAMEL , word)); assertEquals("HelloWorld" , UPPER_UNDERSCORE.to(UPPER_CAMEL , word)); } }
GoogleのJavaライブラリ guava-libraries
夜中にインターネットを見ていたらguava-librariessというJavaライブラリを見つけました。
GoogleのJavaライブラリだから「guava(ぐわば)」ライブラリ。ダジャレですね。ダジャレは嫌いじゃないです。
パッケージはこんな感じ
- com.google.common.base
- com.google.common.collect
- com.google.common.io
- com.google.common.primitives
- com.google.common.util.concurrent
apache-commonsみたいな感じで、言語のコアな部分を提供してくれるようです。Google Collections Libraryも、この中に含まれています。concurrentっていうのは並列処理の便利ライブラリなのかな。
面白そうだなあ
面白そうなのでjavadoc読んだり遊んでみたりしたいです。
noopのBoolean型
noopにBoolean型が実装されたみたいです。
10f733a293 - noop - Project Hosting on Google Code
Booleanクラスには以下のメソッドが定義されてました。
- and
- or
- xor
- not
- toString
試してみよう
import noop.Application; import noop.Console; class HelloBoolean(Console console) implements Application { Int main(List args) { Boolean t = true; Boolean f = false; console.println( t ); // true console.println( f ); // false console.println( t.and(f) ); // false console.println( t.or(f) ); // true console.println( t.xor(f) ); // true console.println( t.not() ); // false return 0; } }
Booleanもオブジェクトなので、下のように直接メソッドを呼び出すことができました。
import noop.Application; import noop.Console; class HelloBoolean(Console console) implements Application { Int main(List args) { console.println( true.and(false) ); // false console.println( true.or(false) ); // true console.println( true.xor(false) ); // true console.println( true.not() ); // false return 0; } }
noopでフィボナッチ数列
whileループが実装されました。
4c84f065ce - noop - Project Hosting on Google Code
まだ比較演算子ができてない状態なので、10回実行されるループ文みたいなのは書けないみたいです。
無限ループとか
while(true){ ... }
一度も実行されないループしか書けません。
while(false){ ... }
と思ってたら、新しいサンプルが追加されてて、一度だけ実行されるwhileループのサンプルがありました。
Boolean b = true; while(b){ console.println("Hello World!"); b = false; }
なるほどなー
あれ?そういえば変数はデフォルトでfinalになるんじゃなかったっけ?!その辺はまだ実装されていないのか。
フィボナッチ数列
ループが書けるようになったので、無限ループ上等でフィボナッチ数列を出力するコードを書いてみました。
import noop.Application; import noop.Console; class Fibonacci(Console console) implements Application { Int main(List args) { Int prev = 0; Int curr = 1; Int next = 1; while(true){ console.println(curr); next = curr.plus(prev); // 「next = curr + prev;」と同じ prev = curr; curr = next; } return 0; } }
実行結果
target>scala -cp classes;resources;"%HOME%\.m2\repository\org\antlr\antlr\3.1.1\antlr-3.1.1.jar" noop.interpreter.InterpreterMain Fibonacci resources\stdlib resources\helloworld 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 -1323752223 512559680 -811192543
おおー
思ったこと
まだ実装途中のようで、できないことがいっぱいあるのだけど、その制約が逆に面白くなってきました。できることが段々増えていくのを見てるのも楽しいです。
「Intクラスにeqメソッドがあればいいのになー」と思い、自分で書いてみようと思ったのだけどよくわからなくなって諦めました。無念です。
エンジニアの未来サミット0905見た
色々あって最初の30分くらいしか見られなかったー。残念だー。
去年のが好評だったので、今年もリンク集を作って参加した気になろう。
前でしゃべってた人
質問した人
記録
- 「エンジニアの未来サミット 0905」私的不完全議事録 - 酒と蕎麦と IT と (ほとんど全文掲載されてる!すごい!)
- エンジニアの未来サミット0905 第二部 弾vs個性派エンジニアーサバイバル討論 - shikaku’s memo blog
- エンジニアの未来サミット 0905 エンジニア・サバイバル - プログラマ 福重 伸太朗 〜基本へ帰ろう〜
- 「エンジニアの未来サミット0905」ログ - niwaka diary
- エンジニアの未来サミット0905 第一部 おしえて!あるファギークーエンジニアが幸せになる方法 - shikaku’s memo blog
- エンジニアの未来サミット エンジニアサバイバル - 朝は眠い日記 from hell -- Luck is for losers --
- エンジニアの未来サミット - 0905:エンジニア・サバイバル - kawaguti の日記 (id:wayaguchi)
感想
- 「エンジニアの未来サミット 0905 エンジニア・サバイバル」 - yochilopの伝説
- 「エンジニアの未来サミット 0905 エンジニア・サバイバル」見ましたよ - ぷろぽすダイアリー
- 未来サミット0905とか | 眠る開発屋blog
- エンジニアの未来サミット0905感想+アクションリスト - niwaka diary
- エンジニアの未来サミット0905 レポート - L’Isle joyeuse
- F's Garage:記事未満〜エンジニアの未来サミットとか。
- エンジニアの未来サミット 0905に行ってきたよ - hoopfuyuの日記
- エンジニアの未来サミットにいってきた - カオスへDive
- 「エンジニアの未来サミット0905」に行ってきました:止まらずに走れ!
- 「エンジニアの未来サミット 0905」に行ってきた - BUENA VISTA SOCIAL BLOG
- エンジニア未来サミット0905 - c9日記 -カタヤマンがプログラマチックに今日もコードアシスト
- エンジニア未来サミット0905 - 泣いてばかりいてはいけない
- 秋葉原は、UDXビルまで行ってきた。 - 葛西に住むプログラマーの日記
- エンジニアの未来サミット0905行ってきました - Talking Nonstop
- エンジニアの未来サミット 0905 エンジニア・サバイバルの感想 - hya10の日記
- エンジニアの未来サミット0905へ行ってきた - 虎塚
- 計算された鈍感さ - 総合的な学習のお時間
- 「エンジニアの未来サミット」に参加してきました(前段) - 僕はお仕事が出来ない
- エンジニアの未来サミット感想 - lack of xx
- 「エンジニアの未来サミット 0905」でセッションを聞きながら、IT資格について考えた - 何かしらの言語による記述を解析する日記
- エンジニアの未来サミット0905 前半感想 - コンデンサの隣からひとこと
- エンジニアの未来サミット0905 後半感想 - コンデンサの隣からひとこと
- 【20090523】エンジニアの未来サミット0905 に行ってきた! - たまにアクティブ/wasi0220の日記
- 2009-05-23 - NightFlags
- 941::blog : 第二回エンジニアの未来サミットに行ってきた
- エンジニアの未来サミット0905 に行ってきまし|79さん@アメブロ
- エンジニアの未来サミット0905の参加感想|西新宿で働く夢見る部長のアメブロ
- エンジニア未来サミット0905を観て | ブログ.武田ソフト.jp
- エンジニアの未来サミット 0905 エンジニア・サバイバル - forest book
- エンジニアの未来サミットに行って来た - 悪あがき
- エンジニアの未来サミット0905に行ってきた - しんどい日記
- エンジニアの未来サミット0905に行ってきた - web新参
- エンジニアの未来サミット0905に行ってきたよ 感想編 - shikaku’s memo blog
- エンジニアの未来サミットの雑感 - mirao420の日記
- エンジニアの未来サミット 0905 - tetu1984の日記
- エンジニアの未来サミットをみて - 反言子
- myfinder's blog: エンジニアの未来サミット0905に行ってきた
- エンジニアの未来サミット 陽だまりの里/ウェブリブログ
- エンジニアの未来サミット0905 - ちんたろサンの日記
- エンジニアの未来サミット0905 - - ebacky way
- エンジニアの未来サミット0905 - tknzk blog
- エンジニアの未来サミット 0905 エンジニア・サバイバル にいってきた - みーと@肉になるメモ
- 「エンジニアの未来サミット0905」に参加してきた - とあるモバイル系エンジニアの日々
- "Q"uwahara Blog: エンジニアの未来サミット 0905 エンジニア・サバイバル
- 一流のWebエンジニアを目指す社会人日記 今回もいってきたエンジニアの未来サミット
- VIVA!桜子のいつも喜んでみよう日記−VIVA!Cherry(=Sakurako)'s Diary | エンジニアの未来サミット0905/エンジニアサバイバル
- エンジニアの未来サミット0905に行ってきました - nmon
- エンジニアの未来サミットに行ってきた | takulab
- エンジニアの未来サミットをUStreamで見た - Life goes on.
- 研究日誌 : エンジニアの未来サミットに行ってきた - livedoor Blog(ブログ)
- ましまろ日記 - vaderグループ
- エンジニアの未来サミット 0905 エンジニア・サバイバルに行ってきた! | ダオカオス☆D流のライフスタイル
- エンジニアの未来サミット0905 - しんさんの出張所 はてな編
まとめ
その他
検索ツール
編集ツール
javascript:(function(){var%20isMSIE%20=%20/*@cc_on!@*/false;var%20opacity%20=%2070;var%20setOpacity%20=%20function(elm){if(isMSIE)%20{elm.style.filter%20=%20'alpha(opacity='+%20opacity%20+')';}%20else%20{elm.style.MozOpacity%20=%20(opacity/100);}};var%20info%20=%20document.createElement('div');info.style.position%20=%20isMSIE%20?%20'absolute':'fixed';info.style.top%20=%20'0';info.style.left%20=%20'0';info.style.width%20=%20'100%';info.style.background%20=%20'#000000';info.style.color%20=%20'#FCFCFC';info.style.textAlign%20=%20'left';info.style.zIndex%20=%201000;info.style.MozBorderRadius%20=%20'0%200%2020px%2020px';info.style.height%20=%200;setOpacity(info);var%20body%20=%20document.getElementsByTagName('body')[0];body.appendChild(info);var%20h%20=%200;show%20=%20setInterval(function(){info.style.height%20=%20(h%20+%20'em');h%20+=%200.4;if(h%20>%206)%20{clearInterval(show);info.innerHTML%20=%20'<div%20style="margin:1em%200%200%202em">-['%20+%20location.href%20+%20":title="%20+%20document.title.replace(/\[\]/,"")%20+%20']</div>';wait%20=%20setTimeout(function(){hide%20=%20setInterval(function(){opacity%20-=%2010;setOpacity(info);if(opacity%20==%200)%20{body.removeChild(info);clearInterval(hide);clearTimeout(wait);}},25);},%205000);}},30);})();void(0);