matobaの備忘録

和歌山と東京を往復しつつ活動するエンジニアの記録

Pythonを使って、macOSのファイル作成日時を取得する

macOSのマシンを使っている。 ローカルのファイルを整理するスクリプトを書いていて、ファイルの作成日時を取得した時の話をする。

ローカルに大量の作業用ファイルがある

ある。作業用ファイルといってもいろんな種類があるが。 直近で言えば、私は考え事を整理するときにXmindを使っているのだが、XMindのファイルがたくさんある。 そして普通に多いので、整理したい。

「作成日付ごとにディレクトリを切って、そこに入れておきたいな」と思った。

Finderだとファイルの作成日時がある

知っての通り、Finderで見ると、ファイルにはファイルの作成日時が表示されている。 だから、行けるはずだろうと持ってスクリプトを書いてみた。Pythonである。

(「書いてみた」と言ったが、実際はClaudeに指示を出してスクリプトを出力し動作確認していた。)

しかし、何かうまくいかない。特定のファイルがFinderの作成日時と違うディレクトリに移動される。

os.path.getctimeを使っていた

そのスクリプトではファイルの作成日時を取得するために、Pythonのos.path.getctimeを確認していた。

docs.python.org

これが間違っている。

システムの ctime、Unix系など一部のシステムでは最後にメタデータが変更された時刻、Windows などその他のシステムでは path の作成時刻を返します。返り値はエポック (time モジュールを参照) からの経過時間を示す秒数になります。ファイルが存在しない、あるいはアクセスできなかった場合は OSError を送出します。

ctimeはUNIX系のシステムのファイルシステムの情報であるが、ファイルの作成日時ではない。ファイルシステムのメタデータの変更日時である。

じゃあ、どうすればいいのか?

ファイルシステムのメタデータの変更日時ではなくて、macOSのFinderで表示されている情報が欲しい。 と言うわけで調べてみた。

どうやらmacOSのファイルシステムに拡張メタデータがあり、それを見れば良さそうだ。

stackoverflow.com

Pythonで取得する方法を探す

上記のスタックオーバーフローでは、コマンドラインから取得している。 が、こう言うのはたいていライブラリがある。と思って探してみた。

github.com

あった。

さらにそこから、以下のライブラリを紹介されていた。

github.com

これが良さそうに見える。あとは、属性名が分かれば取得できそうだ。

必要な属性を探す

macOSの開発者ドキュメントを見ながら、ファイルのメタデータで、欲しいものを探す。

developer.apple.com

kMDItemContentCreationDate がそれっぽい。

developer.apple.com

Python経由で取得する

from osxmetadata import OSXMetaData, kMDItemContentCreationDate

def get_creation_date(file_path: str)-> datetime.datetime:
    md = OSXMetaData(file_path)
    creation_date = md.get(kMDItemContentCreationDate)
    return creation_date

できた。

めでたしめでたし。

Djangoのtimezone.nowに関するモヤモヤを調べる

Djangoのtimezone.nowを使っていてモヤモヤした。特に、「なぜ、UTCの時刻を返すのか」と思ったので、調べてみた。 その記録。

背景

Djangoにはtimezoneのutilsがあり、nowという関数を持っている。 これを実行すると次のようになる。

>>> from django.utils import timezone
>>> timezone.now()
datetime.datetime(2024, 3, 27, 20, 49, 31, 114299, tzinfo=datetime.timezone.utc)

また、Djangoプロジェクトにはsettingsでタイムゾーンが設定できる。 例えば次のような感じ。

TIME_ZONE = "Asia/Tokyo"

しかし、タイムゾーンを設定していても timezone.now() はUTC時刻を返す。 設定したタイムゾーンでの時刻にする場合は、 timezone.localtime() を使えることを知っていた。 なので、次に私はこんな風に書いてみた。

>>> timezone.localtime(timezone.now())
datetime.datetime(2024, 3, 28, 5, 55, 30, 810238, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))

めんどくさい。というか、私は何度も timezone.now() で設定したタイムゾーンの時刻が返ってくると勘違いしている。

「あれ?なんでUTCの時刻返ってきてるんだ?あ、そうか。localtimeという概念あった。」

みたいなことを毎回考えている。モヤモヤするので、もうちょっとこの辺の経緯に関する情報を調べてみる。

ドキュメントを読む

とりあえず、Djangoのtimezone.nowに関するドキュメントを読んでみよう。 5.0でのドキュメントである。

Django ユーティリティ (django.utils) | Django ドキュメント | Django

次の記載がある。

USE_TZ が False の場合、システムのローカルタイムゾーンで現在の時刻を表す、関連するタイムゾーンのない naive な日時(タイムゾーンの関連がない日時)になります。

翻訳前

If USE_TZ is True, this will be an aware datetime representing the current time in UTC. Note that now() will always return times in UTC regardless of the value of TIME_ZONE; you can use localtime() to get the time in the current time zone.

USE_TZという概念が関係ありそうだ。

そもそも、 timezone.localtime() のデフォルト

また、ドキュメントを読んでいる時点で

timezone.localtime() はデフォルトでnowを返すことに気づいた。ドキュメントにそう書いてある。

value が省略された場合, デフォルトは now() になります。

Django ユーティリティ (django.utils) | Django ドキュメント | Django

つまり、タイムゾーンを意識した現在時刻は、これで取得すれば良い。

>>> timezone.localtime()
datetime.datetime(2024, 3, 28, 6, 8, 8, 700251, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))

具体的なコードも見てみたが、本当にそのままnowが実行されている。

django/django/utils/timezone.py at 617bcf611f3daa796e4054ba041089ece30a32fc · django/django · GitHub

というわけで、私は timezone.now() ではなく timezone.localtime() をもっと意識して使うと良さそうだ。

USE_TZについて

ここまで調べてみて、最初のめんどくささはある程度低減された。 ただ、せっかくなので、タイトルの問をもう少し深掘りしたい。

timezone.now() のドキュメントで USE_TZ の話が出てくるので、この設定値が関係してそうだ。 その辺をもう少し調べてみる。

USE_TZ

デフォルトで日時がタイムゾーンを意識するかどうかを指定する真偽値です。これが True に設定されている場合、Django は内部的にタイムゾーンを意識した日時を使用します。

Settings | Django documentation | Django

この記載を見て思うこととして、そもそも、この設定は必要なのか?(わざわざ、タイムゾーンを意識しない、という設定をすることがあり得るのか?)と思った。というのも、私は、Djangoを使い始めてから USE_TZ の値を変更したことがない。「デフォルト値から変更しない設定値」という印象で使ってきた。

なので、この設定が導入された経緯を追いかけてみよう。

UZE_TZはいつ導入されたのか

ドキュメントに USE_TZ の記載があるので、バージョンいくつから導入されているのかを確認してみよう

Djangoのドキュメントで閲覧できる最も古いバージョンは、1.8 である。そのドキュメントを見てみると、すでにUSE_TZがある。

Settings | Django documentation | Django

そこで次のことがわかった

  • UZE_TZが指定されていない場合、 Falseで扱われる(つまり、タイムゾーンを意識せずに扱う)
  • django-admin startproject でプロジェクトを作るとUSE_TZ = True が追加される。

というわけで、 1.8 以前に導入されていたことが見えるし、上記の記載からなんとなく以下の状況が推測できた。

  • 最初は、USE_TZがなかった。そして、タイムゾーンを意識せずに扱っていた。
  • データベースに日時を入れたり、結局のところ、タイムゾーンを意識する必要性が高まってきた。というか常にタイムゾーンを意識した方が望ましい状況になってきた。
  • 後方互換性を意識して、新規プロジェクトではデフォルトでタイムゾーンを意識させるように調整が入った。

これは事実かどうかの裏どりはしていない。が、面倒になってきたので、私の調査はここで終えるとする。

timezone.nowがUTCを返す理由もこの辺の兼ね合いなんだろうなあ、と想像した。

USE_TZの今後

少し余談だが、Django 5.0のドキュメントに USE_TZ のデフォルト値はTrueで、古いバージョンではFalseにだった。との情報があった。

Changed in Django 5.0: In older versions, the default value is False.

Settings | Django documentation | Django

これも推測の裏どりをしていないが、やはりそもそもの話として、 USE_TZ という設定値必要か?という話があるんだろうな、と想像した。 デフォルト値がTrueになったことで、 デフォルトの settings.pyから USE_TZ は姿を消すことになるような気がする。

そうすると、この記事に書いたような疑問を解消するのは、少しハードルが上がるようにも思う。 だから、どうという話ではないですが。

終わり

今回は、ふと思ったことを調べてみました。

ではでは〜

データレイクやデータウェアハウスについて

データ分析の話題に関連して、データレイクやデータウェアハウスといった単語を聞くことがあります。以前、聞いて調べたこともあるのですが、いまいちピンとくる説明が見つかりませんでした。今は、以前と比較して「データレイクやデータウェアハウスって、こういうこと?」と言う理解が進んできました。今回は、現時点の知識を元に当時の私が知りたかった情報を説明してみます。

続きを読む