更新日時

ファイルの時刻

[1] 多くのファイルを扱うシステムにファイルの時刻属性がいくつかあります。

種別

[2] ほとんどが

... のいずれかに分類できます。

[7] ただし各日時の厳密な意味はプラットフォームファイルシステム/データ形式/プロトコルによって異なります。

[9] NTFS では $STANDARD_INFORMATION (ファイルの内容のデータ側) と $FILE_NAME (ファイル名側) にそれぞれ4種類あります。 大まかに言って、内容が変更されれば両者が変更され、 ファイル操作が行われると後者が変更されます。 ファイル1個の情報の読み書きでは基本的に前者が使われ、 ファイル一覧の表示では後者が使われます。 ハードリンクが作られると、後者が複数個になります。

時間帯

[10] FATの日時形式地方時を保存します。時差の情報が含まれず、 どこの時刻かわかりませんし、標準時夏時刻かもわかりません。

[11] それより新しいほとんどのシステムは時差の概念のない絶対的な時刻の値を保存します。 利用者に表示するときはシステムの時間帯に合わせて表示されます。

[12] ZIP のように、古くからある標準のファイルの時刻>>10 で、後から拡張として追加された (つまりすべての ZIPファイルに含まれるわけではない) ファイルの時刻>>11 で両方が共存しているという書庫形式もあります。

データ形式

[8] ファイルの時刻に関係するプロトコル要素と機能

日時の換算

[190] ファイルの時刻の取り扱いで、意外と難関となるのが日時形式 (日時データ型) の換算の問題です。

[191] OS, ファイルシステム, プログラミング言語, 圧縮形式ファイルを扱う各種プロトコルでそれぞれ採用日時データ型が違うことが多いので、 処理の種類と過程に応じてどのデータ型日時を保持し、 どこでどう換算するかを開発者は熟慮しなければなりません。

[192] 個別具体的には各項を参照されたいのですが、概略して次のような事項に配慮が必要です。

FAT

FATの日時形式

他の日時形式との換算

[171] [PATCH V5 0/4] fat: timestamp updates, https://lore.kernel.org/all/2663d3083c4dd62f00b64612c8eaf5542bb05a4c.1538363961.git.sorenson@redhat.com/T/

[189] >>188

[213] 生成時、2秒未満は切り捨て :

[215] 生成時、 round :

地方時の時刻の取り扱い

[164] Re: [PATCH] fat: Allow out-of-range FAT modification timestamps, , https://lists.gnu.org/archive/html/grub-devel/2021-09/msg00021.html

[165] timezone - VFAT, Linux: Invalid file timestamps shown after a reboot - Unix & Linux Stack Exchange, https://unix.stackexchange.com/questions/640555/vfat-linux-invalid-file-timestamps-shown-after-a-reboot

[166] c# - How do I get the correct modified datetime of a FAT32 file, regardless of timezone in .NET? - Stack Overflow, https://stackoverflow.com/questions/10068855/how-do-i-get-the-correct-modified-datetime-of-a-fat32-file-regardless-of-timezo

[167] ファイル時間 - Win32 apps | Microsoft Learn, stevewhims, , https://learn.microsoft.com/ja-jp/windows/win32/sysinfo/file-times?redirectedfrom=MSDN

[168] Handle daylight savings time changes with FAT filesystems · Issue #9227 · syncthing/syncthing, https://github.com/syncthing/syncthing/issues/9227

[169] Unwanted full rescan&resync @ SD card @ Syncthing Android after timezone/daylight saving change - Support / Android - Syncthing Community Forum, https://forum.syncthing.net/t/unwanted-full-rescan-resync-sd-card-syncthing-android-after-timezone-daylight-saving-change/21064

ZIP

[15] ZIPファイルに対して日時を表す欄を多数有しています。

[60] ZIPファイルごとに central directory headerlocal header があります。前者は ZIP 全体の索引として一括されたものであり、 後者は個別のファイルのデータに付与されたメタデータです。 どちらにも書き込まれる情報、一方にのみ書き込まれる情報、 両方に別の値で書き込まれる情報があります。 どう書き込むか仕様上で定まっている場合もあれば曖昧な場合もあり、 実データが仕様と一致していないこともよくあります。 こうした性格は日時情報各種にも当てはまります。

[120] 拡張の日付がどれも使われていない ZIPファイルは今でも非常に多いです。 つまり地方時だけで時差の情報が皆無のことが多いです。

[121] 拡張の日付が複数使われる場合もありますが、あまり多くありません。 拡張の日付が使われる場合はほとんどが mtime を指定していますが、 それ以外が指定される場合や、 mtime が 0 の場合もあります。 拡張の mtime と last mod file date / time は時差を除けば一致すると思われる場合が多いですが、 そうでない場合も散見されます。

[136] 拡張のどれが使われ、拡張のどの時刻が指定されるかは、 ファイルごとに異なる可能性があります。 特にディレクトリーと一般ファイルで違うケースが散見されます。

[135] last mod file date / time は地方時であり、どこの時刻であるかは、 ファイルごとに異なる可能性が否定できません。 拡張の時刻があれば、それとの比較で時差を算出しこれを検出できる可能性があります。

実装戦略

[122] 実装は複数の日付のうちどれを使うかの選択を迫られます。 利用実態や精度を鑑みれば、

  1. [123] 0x000A (NTFS)
  2. [124] 0x5455
  3. [125] 0x5855
  4. [126] 0x000D (Unix)
  5. [127] last mod file date / time

... の優先順が良いと思われます。それぞれ central directory headerlocal header の順で、値が 0 や無指定なら無視して次を参照します。

[128] 日時の読み書きでは、 ZIPファイル上のデータ構造プラットフォームファイルシステムプログラミング言語データ構造の違いと、 それに伴う値域精度の違いに注意する必要があります。

[173] からまでは Unix time で記述できますが、 FATの日時形式では記述できません。 ZIP生成を強行するなら値を不一致とせざるを得ません。 Unix time 0 () がこの範囲に含まれることに注意が必要です。

[174] 途中から途中までは32ビット符号付き整数 Unix time で記述できますが、32ビット符号無し整数 Unix time では記述できません。逆に32ビット符号無し整数 Unix time でなければまでが記述できません。ビット列だけを見てもこのどちらであるかを判定できません。ところで FATの日時形式では都合がいいことにまで記述できますので、32ビット符号無し整数以上であることはFATの日時形式から判断できます。都合が悪いことにFATの日時形式では以下が記述できないため、 32ビット符号付き整数であることを確信はできませんが、32ビット符号無し整数と確定できないなら32ビット符号付き整数と判断するのが良いと思われます。

[179] POSIX 系の多くの新しいプラットフォーム64ビット符号付き整数単位 Unix time を扱えます。 FATの日時形式32ビット整数 Unix time の範囲が完全に収まるので、 ZIPファイルからの読み込みには適切といえます。 ZIPファイルへの書き込みでは、32ビット符号付き整数の範囲外の値を適切に処理する必要があります。 Javaミリ秒単位や Goナノ秒単位の Unix time でも同じことが言えます。

[180] 64ビット符号無し整数は、以前を扱えないので、適切とはいえません。

[183] NTFSの日時形式符号付き整数説と符号無し整数説があります。 実装上も実用上も安定して使えるのは両者の重なりの更に一部だけとされます。 NTFSの日時形式 従って、 ZIPファイルに含まれるNTFSの日時形式がいずれであるかを問題とする必要はありません。 ZIP の処理では、その仕様に従い符号無し整数とするのが良いと考えられます。

[184] どちらと解釈するにせよ、 64ビット符号付き整数単位やミリ秒単位の Unix time を扱えるものの、精度を保つことはできません。実装は Unix time に統一して下位桁溢れを妥協するか、2種類のデータ型を併用するかの選択を求められます。

[185] 逆に Windows 系の実装は NTFSの日時形式を正とし、 Unix time の範囲の両端を扱えないことを妥協するという選択肢もあり、その方法でも実用上は問題ないと考えられます。

[186] ナノ秒単位の Unix timeNTFSの日時形式の精度を完全に記述できるものの、その範囲の両端を扱えないことを妥協する必要があり、それでも実用上は問題ないと考えられます。

[187] いずれにせよ、範囲外となる値は適切に処理する必要があります。

[130] Perlモジュール Archive::Zip は last mod file date / time が Unix time31.686060 未満の値に設定されたとき、 31.686060 に変更します。 時差の問題を避けるためにから12時間1分ずらされています。

[217] なお、範囲できない大きな値の検査はしておらず。成り行きとなります。

[218] FATの日時形式2秒単位しか表現できないの桁は切り捨てられます。

[131] >>78 の実装は範囲外の値となるとき、直近の値 (つまり境界の値) を入れます。

[188] なお、これらの問題は ZIP 固有でなく、 FATNTFSSMB などでも発生します。しかし、 どの種類の値が組合せて供給されるかなど、条件が少しずつ違います。

[78] GitHub - thejoshwolfe/yazl: yet another zip library for node, https://github.com/thejoshwolfe/yazl

When attempting to encode an mtime outside the supported range for either format, such as the year 1970 in the DOS format or the year 2039 for the modern format, the time will clamped to the closest supported time.

地方時の時差の推定

[139] 多くの実装は地方時しかないとき、プラットフォームの時差によるものと解釈します。 ZIPファイルの送受信者は同じ地域に所在することが多いと推測されますから、これでうまくいく場合が多いと思われます。

[140] ところが、 Webサイトからのダウンロードで遠隔地の ZIPファイルを入手することがあると、この前提は成り立たなくなります。あちこちから収集した ZIPファイルに機械的な処理をしたいときにも困ります。

[137] 地方時しか判明しないときでも、ファイル名の文字コードの情報があれば、文字コードと強く結びついた時間帯と推定できる場合があります。 ロケール等による文字コード判定の補助

[138] 文字コードから判断できない UTF-8 のような場合でも、言語判定によって言語と強く結びついた時間帯と推定できる場合があります。

[142] ZIPファイルインターネットから取得したものなら、入手に使った URLccTLD を利用できることがあります。政府機関等の公開データは ccTLD を使った Webサイトで配布されていることが多いので、多くの国で有効と期待できます。

[141] その他ファイルの中身の分析なども考えられ、例えばワープロ文書形式メタ情報として変更日時を記録していることが多そうですし、ただのテキストファイルでも言語判定で推測できるかもしれませんが、判定コストが高い割に都合がいいファイルが ZIPファイルに入っている確率は高くなさそうですし、推測の精度もそれほど良くならないことが多そうです。


[143] 推奨される判定方法 :

  1. [145] 強制的な指定値がある場合、
    1. [223] その値を返し、ここで停止します。
  2. [224] 各ファイルに対し、
    1. [144] 拡張の mtime がある場合、
      1. [221] t を、 last mod file date / time - 拡張の mtime に設定します。
      2. [222] tを、 sign (t) (15 × 60) ⌊ |t| + 15 × 60 2 15 × 60 ⌋ に設定します。
      3. [146] |t| ≦ 24 × 60 × 60 の場合、
        1. [147] tを返し、ここで停止します。
  3. [148] ファイル名の文字コードから推定時差を1つに定められる場合、
    1. [149] その値を返します。
  4. [150] ファイル名の文字コードから推定時差を2つ (標準時夏時刻) に定められる場合、
    1. [151] 地域を、その推定時差の地域に設定します。
    2. [152] ccTLDnull でなく、地域に属する場合、
      1. [153] ccTLDの代表の時間帯を返します。
    3. [154] それ以外の場合、
      1. [155] 地域の代表の時間帯を返します。
  5. [156] それ以外の場合、
    1. [157] ccTLDnull でない場合、
      1. [158] ccTLDの代表の時間帯を返します。
    2. [159] それ以外の場合、
      1. [160] 決定不能を返します。

利用例

[46] 実利用例:

[25] 人工的な利用例:

メモ

[34] アーカイブファイルフォーマットのタイムスタンプまとめ, https://zenn.dev/sorairolake/articles/timestamp-of-archive-formats

[59] Gitcommit に日付があり、ファイルには日付がありません。 他の多くの版管理システムでも同様です。

[163] Filesystem Timestamps: What Makes Them Tick?, https://www.sans.org/white-papers/36842

関連

システムの時間帯, 地方時

メモ

[172] FatFs - Configuration Options, , https://elm-chan.org/fsw/ff/doc/config.html#fs_nortc

[220] ファイルのタイムスタンプが変更されるタイミング, https://zenn.dev/megeton/articles/e58ea554ba5650