tkcalendarからの指定日付を使ったデータの振り分け(追記)

# 目次
[はじめに](#はじめに)
[作成した経緯](#作成した経緯)
[実行環境](#実行環境)
[フォルダダイアログを使ったフォルダパスの指定方法_追加](#フォルダダイアログを使ったフォルダパスの指定方法_追加)
[月と日の取得方法](#月と日の取得方法)
[フォルダ内のすべてのファイル名の取得方法](#フォルダ内のすべてのファイル名の取得方法)
[フォルダ内のファイルの最終更新月日の取得方法](#フォルダ内のファイルの最終更新月日の取得方法)
[最終更新月日のフォルダの作成方法](#最終更新月日のフォルダの作成方法)
[最終更新月日ごとでフォルダに振り分ける方法](#最終更新月日ごとでフォルダに振り分ける方法)
[ソースコード](#ソースコード)
[結果](#結果)
[注意点](#注意点)
[まとめ](#まとめ)

# はじめに
今回は、<font color="red"><ins>**投稿月**</ins></font>と<font color="red"><ins>**投稿日**</ins></font>を<font color="red"> **`tkcalendar`** </font>のウィジェットである[<font color="red"> **`DateEntry`(クリックでDateEntryのドキュメントへ)**</font>](https://tkcalendar.readthedocs.io/en/stable/DateEntry.html)を用いて指定しその日付をもとに特定のフォルダ内の<font color="red"><ins>**最終更新月日**</ins></font>のファイルを振り分けるプログラムを作成したのでそれについて紹介したいと思います。

--------

よろしければこちらもどうぞ!!

<details><summary>マイページについて</summary>

:::note info
主に<ins>**Pythonについての記事**</ins>を投稿してます!!
:::

[<font color="blue"><ins>**マイページはこちら**</ins></font>](https://qiita.com/blueman,"マイページ")

----------------------

</details>

<details><summary>X(Twitter)について</summary>

:::note info
<ins>**Qiitaの新着記事の通知**</ins>や<ins>**おススメの理工学書の紹介**</ins>をツイートしてます!!
:::
[<font color="blue"><ins>**X(Twitter)はこちら**</ins></font>](https://twitter.com/0ca00118726208m,"X(Twitter)")

--------------------------------

</details>

<details><summary>Zennについて</summary>

:::note info
主に<ins>**自分の書いたQiitaの記事の転載**</ins>と<ins>**Pythonを使っていて遭遇したエラーの対処法のような記事**</ins>を投稿していく予定です!!
:::
[<font color="blue"><ins>**Zennはこちら**</ins></font>](https://zenn.dev/blueman,"Zenn")

--------------------------------

</details>

# 作成した経緯
**`Qiita`** に記事を投稿しているときに<ins>こんなことをやりたい</ins>と思いました。

- これまで作成したものが入っているフォルダ内のファイルを<font color="red">**投稿日付ごとに振り分けたい**</font>
    - [**GitHub**](https://github.com/blueman3/Qiita_data_storage)をやっており**後でデータを格納する際に<ins>分かりやすく</ins>させたかった**
- これまでの作成したものが入っているフォルダ内のファイルを<font color="red">**一括して**</font>振り分けたい
    - **投稿した都度**日付の名前のフォルダを作り**更新日付ごとにファイルを振り分けるのは<ins>大変</ins>だった**
    - 時間があるときに<ins>**まとめて**</ins>処理を実行させたかった

このような経緯から作成しました。

# 実行環境
実行環境は次の通りです。

<details><summary>実行環境</summary>

- **環境**
    -  **`Windows10`**
    -  **`Python 3.10.5`**

- **ライブラリ**
    -  **`tkcalendar 1.5.0`**

:::note
<font color="red"> **`os`** </font>・<font color="red"> **`datetime`** </font>・<font color="red"> **`tkinter`** </font> は、<font color="red"><ins>**標準ライブラリ**</ins></font>なので<font color="red">**特に気にしなくても大丈夫です!!**</font>
:::

------------------

</details>

**処理内容**と**モジュール・ライブラリ**の対応を分かりやすく表にすると次の通りです。

<details><summary>説明</summary>

| <font color="red">処理内容</font> | <font color="red">モジュール・ライブラリ</font> |
|:-:|:-:|
|**ファイル操作**|<font color="red"> **`os`** </font>|
|**ファイルの更新月日の取得**|<font color="red"> **`datetime`** </font>|
|**GUI作成**|<font color="red"> **`tkinter`** </font>|
|**カレンダー形式のGUI作成**|<font color="red"> **`tkcalendar`** </font>|

-------------------------------

</details>

# フォルダダイアログを使ったフォルダパスの指定方法_追加
前回のプログラムだと<font color="red"><ins>**前もって**</ins></font>**フォルダを指定した状態**で日付を指定していました。

この方法だと<font color="red"><ins>**自由度が低い**</ins></font>と感じたため<ins>前回のプログラムに新しく追加</ins>しました。

こちらのソース

https://pg-chain.com/python-filedialog-dir

では、<font color="red"> **`tkinter`** </font>の<font color="red"> **`filedialog.askdirectory`** </font>を使うことで<ins>**フォルダダイアログ**</ins>**を<font color="red"> **`tkinter`** </font>を用いて表示**できるそうです。

この方法を用いて<ins>**フォルダダイアログを表示**</ins>させました。

# 月と日の取得方法
[**tkcalendarの公式ドキュメント**](https://tkcalendar.readthedocs.io/en/stable/DateEntry.html)を見ると、<font color="red"> **`get_date`** </font>で<font color="red"> **`datetime`** </font>形式のデータを取得できるらしいのでこれを使いました。

こちらのソース

https://www.beyoufree.com/2022/04/29/python-tkinter%e3%81%a7%e3%82%ab%e3%83%ac%e3%83%b3%e3%83%80%e3%83%bc%e3%81%ae%e5%80%a4%e3%82%92%e5%8f%96%e5%be%97%e3%81%99%e3%82%8b/

では、<font color="red"> **`<<DateEntrySelected>>`** </font>というキーイベントで <font color="red"> **`tkcalendar`** </font>での<ins>カレンダーの日付</ins>を取得できるらしいのでこれを使いました。

公式ドキュメントにあった<font color="red"> **`get_date`** </font>から月(<font color="red"> **`month`** </font>)の部分と日(<font color="red"> **`day`** </font>)の部分を<font color="red"> **`get_date().month`** </font>と<font color="red"> **`get_date().day`** </font>で拾えるっぽいのでこの方法で<font color="red"> **`tkcalendar`** </font>から月と日を取得しました。

# フォルダ内のすべてのファイル名の取得方法
こちらのソース

https://www.sejuku.net/blog/63568

によると、
> **<font color="red"> **`os.listdir`** </font>でフォルダ内のファイル一覧を取得できる**

らしいので、この方法でフォルダ内のすべてのファイル名を取得しました。

# フォルダ内のファイルの最終更新月日の取得方法
こちらのソース

https://note.nkmk.me/python-os-stat-file-timestamp/

によると、

> **最終更新日時は<font color="red"> **`mtime`** </font>で取得できる**

らしいので、この方法で<ins>**ファイルの最終更新日時**</ins>を取得しました。
また、[公式ドキュメント](https://docs.python.org/ja/3/library/datetime.html)によると、<font color="red"> **`datetime.datetime`** </font>クラスの属性には<font color="red"> **`month`** </font>属性と<font color="red"> **`day`** </font>属性がありこれらの属性にアクセスすることで月と日を取得できるらしいのでこの方法で<ins>**ファイルの最終更新日時**</ins>から<ins>**ファイルの最終更新月日**</ins>を取得しました。

# 最終更新月日のフォルダの作成方法
こちらのソース

https://note.nkmk.me/python-os-mkdir-makedirs/#osmakedirs

によると、

> <font color="red"> **`os.makedirs`** </font>**でフォルダを作成できる**
> **引数である<font color="red"> **`exist_ok`** </font>を<font color="red"> **`True`** </font>に指定すると<ins>既に存在しているフォルダを指定してもエラーにならない</ins>**

らしいので、この方法でフォルダを作成しました。

また、この処理を次の条件のとき
- [**フォルダ内のファイルの最終更新月日の取得方法**](#フォルダ内のファイルの最終更新月日の取得方法)**で取得された月**が[**月と日の取得方法**](#月と日の取得方法)**で取得された月から1を引いた数**であり[**フォルダ内のファイルの最終更新月日の取得方法**](#フォルダ内のファイルの最終更新月日の取得方法)**で取得された日**が[**月と日の取得方法**](#月と日の取得方法)**で取得された日よりも大きい**場合
- [**フォルダ内のファイルの最終更新月日の取得方法**](#フォルダ内のファイルの最終更新月日の取得方法)**で取得された月**が[**月と日の取得方法**](#月と日の取得方法)**で取得された月**であり[**フォルダ内のファイルの最終更新月日の取得方法**](#フォルダ内のファイルの最終更新月日の取得方法)**で取得された日**が[**月と日の取得方法**](#月と日の取得方法)**で取得された日以下**の場合

に実行しました。

また、<ins>**フォルダ名**</ins>は[**月と日の取得方法**](#月と日の取得方法)**を **`string`** 型(文字型)に型変換して作成**しました。

<details><summary>具体例</summary>

:::note
例えば、
`tkcalendar`で<ins>**「8」月「2」日**</ins>と入力された場合
→　ファイルの最終更新月日が<ins>**「7」月「2」日以降**</ins>か<ins>**「8」月「2」日まで**</ins>

としました。

こうすることで、<font color="red"><ins>**前月分のデータも参照することができる**</ins></font>ようにしました。

:::

-----------

</details>

:::note alert

**月日を指定する時**には<font color="red">**順次指定**</font>してください。

<font color="red">**指定した月日のフォルダ内に指定した月日以前のデータも含んでファイルに入ってしまいます**</font>

:::

# 最終更新月日ごとでフォルダに振り分ける方法
こちらのソース

https://python-academia.com/file-transfer/

によると、

> <font color="red"> **`os.rename`** </font>**でファイルを移動させることができる**

らしいので、この方法でファイルを移動させました。
また、**対象のフォルダ(<ins>最終更新月日でフォルダ分けさせたいファイルが格納されているフォルダ</ins>)内にファイルがある場合に実行**するようにしました。

こちらのソース

https://note.nkmk.me/python-os-exists-isfile-isdir/

の情報から<font color="red"><ins>**ファイルの存在確認**</ins></font>を行う処理を実装しました。

# ソースコード
下にソースコードを示します。おそらく[実行環境](#実行環境)で示した環境では動くはず。

<details><summary>ソースコード</summary>

```python:data_separate(time).py
import os                                                                                             #ファイル操作のためにインポート
import datetime                                                                                       #ファイルの更新月日を取得するためにインポート
from tkinter import *                                                                                 #tkinterライブラリのすべてのモジュールをインポート
from tkinter import filedialog
from tkcalendar import DateEntry                                                                      #tkcalendarの入力を取得するためにインポート

dir = filedialog.askdirectory(initialdir='C:\\')                                                      #-*追加部分*-フォルダの指定(tkinter.filedialogを使ったフォルダダイアログで指定(初期フォルダをC:\としている))

root=Tk()                                                                                             #tkinterのセットアップ
root.title("カレンダー")                                                                              #「カレンダー」という名前のウインドウを作成

date_form = DateEntry(locale='ja_JP')                                                                 #tkcalendarをデータ取得用のカレンダー形式の要素(ウィジェット)を作成(locale='ja_JP'と指定することで日本語形式で設定)

def func(event):                                                                                      #tkinterの処理関数を定義
	date_get = date_form.get_date()                                                                   #tkcalendarから日付を取得
	month = date_get.month                                                                            #date_get中の月(month)の部分を取得
	day = date_get.day                                                                                #date_get中の日(day)の部分を取得
	files=os.listdir(dir)                                                                             #「python」フォルダの中のすべてのファイル一覧を取得
	for file in files:                                                                                #すべてのファイルについて処理を実行
		date = datetime.datetime.fromtimestamp(os.path.getmtime(dir+"/"+file))                        #最終更新日時をタイムスタンプ形式で取得
		if ((date.month == month-1 and date.day > day) or (date.month == month and date.day <= day)):
			'''
			date中の月(date.month)がdate_get中の月(month)-1でありdate中の日(date.day)がdate_get中の日(day)より大きいか、date中の月(date.month)がdate_get中の月(month)であり
			date中の日(date.day)がdate_get中の日(day)以下である場合
			ex)
			「8」月「2」日と入力された場合 → 「7」月「3」日以降から「8」月「2」日まで
			※この処理だと順番に日付を指定しないとすべてのデータが指定した日付のフォルダに格納されてしまうという問題が発生する
			'''
			os.makedirs(dir+"/"+str(month)+"_"+str(day),exist_ok=True)                                #フォルダが存在しない場合には、指定した日付のフォルダを「python」フォルダの下に配置
			if(os.path.isfile(dir+"/"+file)):                                                         #上のフォルダがある場合に処理を実行
				os.rename(dir+"/"+file,dir+"/"+str(month)+"_"+str(day)+"/"+file)                      #条件に一致したファイル(指定した日付までのファイル)を作成したフォルダ
				                                                                                      #(指定した日付のフォルダ)の配下に移動させる

date_form.bind("<<DateEntrySelected>>",func)                                                          #tkcalendarでのカレンダーで任意の日付が押されたときに処理関数(func)を実行


date_form.pack()                                                                                      #カレンダー形式で作成したウィジェット(date_form)を画面上に配置
root.mainloop()                                                                                       #GUIの表示(この処理がないと一瞬表示されて消えるので表示されていないように見える)

```

-----------------------

</details>

# 結果
下に結果を示します。

<details><summary>フォルダダイアログ</summary>

![フォルダダイアログ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/5d600fbe-67c1-0ae9-e58f-6d46d91503c8.jpeg)

-----------------------------

</details>

<details><summary>入力画面</summary>

![実行画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/7c1974a2-72ce-2c32-fcb2-b7f03be3bb95.png)

----------------------------

</details>

<ins>この入力画面</ins>を使って<ins>**投稿月日**</ins>を指定します。

<ins>処理前のフォルダ内</ins>がこちらです。

<details><summary>処理前のフォルダ内</summary>

![処理前のフォルダ内.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/6c653ef5-7b33-1947-dafa-d2cdd088e142.jpeg)

----------------------

</details>

例えば、<ins>「7」月「28」日と入力</ins>した場合にはこうなります。

<details><summary>処理例</summary>

![処理例.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/c0e17d71-f7fe-ff5b-08cf-6c9c6c8498b4.jpeg)

-----------------------

</details>

ちゃんと<ins>**「7」月「28」日という名前のフォルダ**</ins>が作成されています。

<ins>フォルダの中身</ins>がこちらです!!

<details><summary>フォルダ内</summary>

![フォルダ内.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/91ad3bfe-7ee2-bbbe-0b13-079eda6485d8.jpeg)

----------------------------

</details>

ちゃんと<ins>**「7」月「28」日までのファイル**</ins>が格納されています。

# 注意点

[最終更新月日のフォルダの作成方法](#最終更新月日のフォルダの作成方法)でも示したように投稿月日を<ins>**日付順**</ins>に指定しないと<ins>**それ以前に投稿したデータも含んでフォルダに格納**</ins>されてしまいます。

具体的にはこんな感じになります。

<details><summary>実行結果</summary>

![実行結果_1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/5c773dd7-7af4-ddf6-d7f8-5570f2f528e1.jpeg)
![実行結果_2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3198998/ab436475-ebf2-58b9-e06b-af00a2977311.jpeg)

---------------------------

</details>

# まとめ
今回は、<ins>**投稿月日を指定することでフォルダ内のファイルを最終更新月日から投稿月日ごとに振り分ける**</ins>ことを行いました。
この記事が実際に役に立つかどうかは分かりませんが、誰かの役に立ってくれると嬉しいです。
記事を執筆する余力があれば、<ins>次回も記事を投稿する予定</ins>です。
次回の予定としては、今回振り分けたフォルダごとに<font color="red"><ins>**拡張子から「結果・素材」フォルダと「ソースコード」フォルダにデータを振り分ける**</ins></font>プログラムを作成できたのでそれに関する記事を投稿予定です。