マルチプラットフォームでZipファイル中の日時を扱う場合の注意事項

FiremonkeyでもTZipFileを使って、ZIPファイルの操作ができる。
マルチプラットフォームなので、Windows以外のプラットフォームでも利用できるのだが、日時の操作に関して注意が必要だ。

Zipファイル中のファイルの日時は、TZipHeader構造体のModifiedDateTimeフィールドで得ることができる。
TZipHeader構造体のデータを得るにはFileInfo[]プロパティを使うほか、Read関数で読み込み時についでに得ることができる。

さて、Zipファイル中の日時はMSDOS形式の32ビット整数で保存されており、ModifiedDateTimeフィールドはその値をそのまま得ることができる。
これをTDateTimeに変換する場合、WindowsであればFileDateToDateTime関数で正しく変換ができる。
しかし、その他のプラットフォームでは正しい結果を得ることができない。
なぜなら他のプラットフォームではファイルシステムのフォーマットがMSDOS形式と互換性がないからだ。

このためWindows以外でZipファイル中の日付を得るためにFileDateToDateTime関数を用いることはできない。
標準のZipファイル操作関連のライブラリーで、日付変換のルーチンがあれば良いのだが、現状ではないので自作をすることになる。
共用体とビットフィールド定義を用いると、以下のように比較的簡単に作成できる。

TDateTime msdos_date_to_date_time( int nModifiedDateTime )
{
  union msdos_date
  {
    int n;
    struct
    { // 下位のビットフィールドから順に並べる(注:現状のC++Builderでは、この仮定は正しい)
      unsigned short s : 5;
      unsigned short n : 6;
      unsigned short h : 5;
      unsigned short d : 5;
      unsigned short m : 4;
      unsigned short y : 7;
    } t;
  } mt;
  mt.n = nModifiedDateTime;
  return TDateTime( mt.t.y + 1980, mt.t.m, mt.t.d, mt.t.h, mt.t.n, mt.t.s * 2, 0 );
}

ビットフィールドの配列順序に関しては、JP CERTの以下の記事を読むこと。上記コードは下位ビットフィールドから並べてあると仮定しているが、CLangに関してこの仮定をしても将来も変わらないだろうと思う。
EXP11-C. ビットフィールド構造体のレイアウトについて勝手な想定をしない

Zipファイルは広く利用されているが、上記の通り年のフィールドは7bitなので年として1980~2107の間の値しか定義できない。
まだ80年は持つが、広く流布しているファイル形式だけに、ぼちぼちこのあたりの拡張を議論しないとまずいのではないだろうか。

2023-11-8追記

RAD Studio 12 Athensで修正された。
ModifiedTimeがTZipHeaderのプロパティに追加された。型がTDateTimeなので上記のような問題を意識しなくても良い。
今後はこちらを使おう。