-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdate-organizer.py
More file actions
124 lines (99 loc) · 4.19 KB
/
date-organizer.py
File metadata and controls
124 lines (99 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/usr/bin/env python3
"""
Date Organizer — Organize files into YYYY/MM/DD folders by date modified.
Usage:
python date-organizer.py <directory>
python date-organizer.py <directory> --move
python date-organizer.py <directory> --date-created
Options:
--move Move files instead of copying (default: copy)
--date-created Use creation date instead of modification date
--dry-run Show what would be done without making changes
--exclude PAT Exclude files matching pattern (repeatable)
--help Show this help message and exit
"""
import os
import sys
import shutil
import argparse
import datetime
from pathlib import Path
def get_file_date(filepath: Path, use_created: bool) -> datetime.date:
"""Get the relevant date for a file."""
try:
stat = filepath.stat()
if use_created:
ts = stat.st_ctime
else:
ts = stat.st_mtime
return datetime.datetime.fromtimestamp(ts).date()
except Exception:
return datetime.date.today()
def organize_files(directory: Path, move: bool, use_created: bool,
dry_run: bool, exclude: list[str]) -> tuple[int, int]:
"""Organize files into YYYY/MM/DD folders. Returns (organized, errors)."""
organized = errors = 0
for item in directory.iterdir():
if not item.is_file():
continue
# Check exclusion patterns
if any(pat in item.name for pat in exclude):
continue
# Skip itself
if item.name == Path(__file__).name:
continue
try:
d = get_file_date(item, use_created)
dest_dir = directory / str(d.year) / f"{d.month:02d}" / f"{d.day:02d}"
dest_path = dest_dir / item.name
if dest_path == item:
continue # already there
if dry_run:
action = "Would move" if move else "Would copy"
print(f" {action} {item.name} → {dest_dir}/")
else:
dest_dir.mkdir(parents=True, exist_ok=True)
if move:
shutil.move(str(item), str(dest_path))
else:
shutil.copy2(str(item), str(dest_path))
print(f" {'Moved' if move else 'Copied'} {item.name} → {dest_dir}/")
organized += 1
except (OSError, shutil.Error) as e:
print(f" ✗ {item.name}: {e}")
errors += 1
return organized, errors
def main():
parser = argparse.ArgumentParser(
description="Organize files into YYYY/MM/DD folders by date modified.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" python date-organizer.py ~/Downloads --dry-run\n"
" python date-organizer.py ~/Downloads --move\n"
" python date-organizer.py ./photos --move --date-created\n"
" python date-organizer.py ./stuff --exclude .tmp --exclude .log\n"
),
)
parser.add_argument("directory", help="Directory to organize")
parser.add_argument("--move", action="store_true", help="Move files instead of copying")
parser.add_argument("--date-created", action="store_true",
help="Use creation date instead of modification date")
parser.add_argument("--dry-run", action="store_true", help="Preview without making changes")
parser.add_argument("--exclude", action="append", default=[], help="Exclude pattern (repeatable)")
args = parser.parse_args()
target = Path(args.directory)
if not target.is_dir():
print(f"Error: '{args.directory}' is not a valid directory.")
sys.exit(1)
mode = "Move" if args.move else "Copy"
date_type = "created" if args.date_created else "modified"
label = " [DRY-RUN]" if args.dry_run else ""
print(f"Date Organizer{label}")
print(f" Directory: {target}")
print(f" Mode: {mode} (by date {date_type})\n")
organized, errors = organize_files(target, args.move, args.date_created,
args.dry_run, args.exclude)
print(f"\nDone: {organized} file(s) organized, {errors} error(s)")
if __name__ == "__main__":
main()