Apache POI (HSSF/XSSF) の書式適用の注意点

(この記事は2010年7月31日に投稿したものの再掲です)

Apache POIのHSSF/XSSFを使用して、まっさらの状態からExcelのワークシートを作っていくことを考えてみます。

例えば、ワークシートを新規作成してセルA1に何か値を設定する場合、次のようなコードを書くことになります。

Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue(...);
...

別段、何の問題もないコードです。ただし、設定するデータが日付でなければの話ですが。

日付の場合、JavaではDateクラスやJSR-310のLocalDateクラスなどになりますが、Excel上では起点からの経過時間を表す数値+日付書式によって表します。それを考慮すると、上記のプログラムは以下のようになります。

Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(new Date());  // とりあえず現在日付
...

こうするとセルA1には、この記事を書いている2010年7月30日の場合、40389.88...という数値が入ります。これに以下のように日付書式を適用すれば問題ないように思われます。

Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(new Date());  // とりあえず現在日付

cell.getCellStyle().setDataFormat(0xe);  // Excel 組み込みの日付書式
...

さて問題は、ワークシート上に他のセル、特に数値が入ったセルがあった場合です。POIの実装では、どうやらデフォルトのCellStyleオブジェクトは同じものを指しているようで、上記のように単純に書式を変えると、他の数値セルにまで日付書式が適用されてしまうという事象が発生します。


これを回避するには、Cellオブジェクトが最初に参照しているCellStyleを変更するのではなく、CellStyleのクローンを作成してそれを編集したものをセットし直す必要があります。例を示します。

Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(new Date());  // とりあえず現在日付

CellStyle style = workbook.createCellStyle();
style.cloneStyleFrom(cell.getCellStyle());  // スタイルを複製する
style.setDataFormat(0xe);  //  Excel 組み込みの日付書式
cell.setCellStyle(style);

これで一応、正しい書式が適用されるようになります。


この件については、テクニック云々よりは、むしろPOIの実装の方が不適切だと思います。Excelのワークシート上では、セルのスタイルは共有されないからです(もし共有されているように見えるのなら、それはExcelの便利機能がちょっとお節介なだけです)。


追記:


Row#createCellメソッドこそ、本来はPrototypeパターンの見本であるべきものですね。クローニングをディープコピーでやる仕様にしておけば良かったのに、シャローコピーにしたものだから。もっとも、シャローコピーがすべて悪いと言っているわけではないですが。