The following script will generate a simple markdown worklog in the format listed below. It correctly handles the partial weeks at the beginning and the end of the year.
# Worklog
## Week 1 [2025-01-01 - 2025-01-03]
- **Wednesday**
- **Thursday**
- **Friday**
## Week 2 [2025-01-06 - 2025-01-10]
- **Monday**
- **Tuesday**
- **Wednesday**
- **Thursday**
- **Friday**
...
## Week 53 [2025-12-29 - 2025-12-31]
- **Monday**
- **Tuesday**
- **Wednesday**
Run the script with python3.12 -m worklog -y <4-digit-year>
.
# worklog.py
import argparse
from datetime import datetime, timedelta
from dataclasses import dataclass
from collections.abc import Iterator
import logging
# Set up logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
@dataclass(slots=True)
class WeekLog:
week_number: int
start_of_week: str
end_of_week: str
days_of_week: list[str]
def generate_weeklogs(year: int) -> Iterator[WeekLog]:
start_date = datetime(year, 1, 1)
end_of_year = datetime(year, 12, 31)
week_number = 1
# Handle the first partial week
if start_date.weekday() > 4: # If Jan 1 is a Saturday or Sunday, skip to next Monday
start_date += timedelta(days=(7 - start_date.weekday()))
first_week_end = min(start_date + timedelta(days=4 - start_date.weekday()), end_of_year)
yield WeekLog(
week_number,
start_date.strftime("%Y-%m-%d"),
first_week_end.strftime("%Y-%m-%d"),
[(start_date + timedelta(days=i)).strftime("%A") for i in range((first_week_end - start_date).days + 1)],
)
week_number += 1
current_date = first_week_end + timedelta(days=3) # Skip to next Monday
days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Handle full weeks
while current_date <= end_of_year:
start_of_week = current_date
end_of_week = min(start_of_week + timedelta(days=4), end_of_year)
if end_of_week == end_of_year:
days_of_week = days_of_week[: (end_of_week - start_of_week).days + 1]
yield WeekLog(week_number, start_of_week.strftime("%Y-%m-%d"), end_of_week.strftime("%Y-%m-%d"), days_of_week)
week_number += 1
current_date = end_of_week + timedelta(days=3) # Skip to next Monday
def export_worklog_to_markdown(weeklogs: Iterator[WeekLog], filename: str) -> None:
with open(filename, "w") as file:
file.write("# Worklog\n\n")
for week in weeklogs:
file.write(f"## Week {week.week_number} [{week.start_of_week} - {week.end_of_week}]\n\n")
for day in week.days_of_week:
file.write(f"- **{day}**\n")
file.write("\n")
def main():
parser = argparse.ArgumentParser(description="Generate a worklog for a specified year.")
parser.add_argument("-y", "--year", type=int, default=datetime.now().year)
args = parser.parse_args()
worklog = generate_weeklogs(args.year)
export_worklog_to_markdown(worklog, f"worklog_{args.year}.md")
logger.info(f"Worklog for {args.year} has been generated successfully.")
if __name__ == "__main__":
main()
The unittests can be run with python -m test_worklog
.
# test_worklog.py
import unittest
from worklog import generate_weeklogs
class TestGenerateWeeklogs(unittest.TestCase):
def test_full_year_start_on_monday(self):
year = 2024 # 2024 is a leap year and starts on a Monday
weeklogs = list(generate_weeklogs(year))
# Check the first week
self.assertEqual(weeklogs[0].start_of_week, "2024-01-01")
self.assertEqual(weeklogs[0].end_of_week, "2024-01-05")
self.assertEqual(weeklogs[0].days_of_week, ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])
# Check the last week
self.assertEqual(weeklogs[-1].start_of_week, "2024-12-30")
self.assertEqual(weeklogs[-1].end_of_week, "2024-12-31")
self.assertEqual(weeklogs[-1].days_of_week, ["Monday", "Tuesday"])
def test_partial_first_week(self):
year = 2025 # 2025 starts on a Wednesday
weeklogs = list(generate_weeklogs(year))
# Check the first partial week
self.assertEqual(weeklogs[0].start_of_week, "2025-01-01")
self.assertEqual(weeklogs[0].end_of_week, "2025-01-03")
self.assertEqual(weeklogs[0].days_of_week, ["Wednesday", "Thursday", "Friday"])
# Check the second week
self.assertEqual(weeklogs[1].start_of_week, "2025-01-06")
self.assertEqual(weeklogs[1].end_of_week, "2025-01-10")
self.assertEqual(weeklogs[1].days_of_week, ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])
def test_partial_last_week(self):
year = 2023 # 2023 ends on a Sunday
weeklogs = list(generate_weeklogs(year))
# Check the last partial week
self.assertEqual(weeklogs[-1].start_of_week, "2023-12-25")
self.assertEqual(weeklogs[-1].end_of_week, "2023-12-29")
self.assertEqual(weeklogs[-1].days_of_week, ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])
def test_start_on_weekend(self):
year = 2022 # 2022 starts on a Saturday
weeklogs = list(generate_weeklogs(year))
# Check the first week starts on the next Monday
self.assertEqual(weeklogs[0].start_of_week, "2022-01-03")
self.assertEqual(weeklogs[0].end_of_week, "2022-01-07")
self.assertEqual(weeklogs[0].days_of_week, ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])
def test_single_week_year(self):
year = 2021 # Set up for a theoretical test with a very short year
weeklogs = list(generate_weeklogs(year))
# Check that it handles a year with only one week
self.assertEqual(weeklogs[0].start_of_week, "2021-01-01")
self.assertEqual(weeklogs[0].end_of_week, "2021-01-01")
self.assertEqual(weeklogs[0].days_of_week, ["Friday"])
def test_full_year(self):
year = 2020 # 2020 is a leap year, starting on a Wednesday
weeklogs = list(generate_weeklogs(year))
# Ensure that all weeks are properly generated
self.assertTrue(len(weeklogs) > 50) # Check that there are at least 52 weeks
self.assertEqual(weeklogs[0].start_of_week, "2020-01-01")
self.assertEqual(weeklogs[-1].end_of_week, "2020-12-31")
def test_leap_year(self):
year = 2020 # 2020 is a leap year
weeklogs = list(generate_weeklogs(year))
# Ensure that the leap day is included in the correct week
for week in weeklogs:
if "Saturday" in week.days_of_week:
self.assertIn("2020-02-29", week.start_of_week + " to " + week.end_of_week)
if __name__ == "__main__":
unittest.main()