Move it even higher

This commit is contained in:
2023-01-19 21:41:31 +01:00
parent 458ee11a71
commit 274520c57a
38 changed files with 18 additions and 23 deletions

0
common/__init__.py Normal file
View File

30
common/import_data.py Normal file
View File

@ -0,0 +1,30 @@
import csv
from typing import TypeAlias
from timetracker.games.models import Game
DataList: TypeAlias = list[dict[str, str]] | None
def read_csv(filename: str) -> DataList:
with open(filename, "r") as csvfile:
writer = csv.DictReader(csvfile)
return writer
def import_data(data: DataList):
matching_names = {}
for line in data:
name = line["name"]
if name not in matching_names:
# try exact match first
try:
game_id = Game.objects.get(name__iexact=name)
except:
pass
matching_names[name] = game_id
print(f"Exact matched {len(matching_names)} games.")
def import_from_file(filename: str):
import_data(read_csv(filename))

91
common/plots.py Normal file
View File

@ -0,0 +1,91 @@
import base64
from datetime import datetime
from io import BytesIO
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
from django.db.models import F, IntegerField, QuerySet, Sum
from django.db.models.functions import TruncDay
from timetracker.games.models import Session
def key_value_to_value_value(data):
return {data["date"]: data["hours"]}
def playtime_over_time_chart(queryset: QuerySet = Session.objects):
microsecond_in_second = 1000000
result = (
queryset.exclude(timestamp_end__exact=None)
.annotate(date=TruncDay("timestamp_start"))
.values("date")
.annotate(
hours=Sum(
F("duration_calculated"),
output_field=IntegerField(),
)
)
.values("date", "hours")
)
keys = []
values = []
running_total = int(0)
for item in result:
# date_value = datetime.strftime(item["date"], "%d-%m-%Y")
date_value = item["date"]
keys.append(date_value)
running_total += int(item["hours"] / (3600 * microsecond_in_second))
values.append(running_total)
data = [keys, values]
return get_chart(
data,
title="Playtime over time (manual excluded)",
xlabel="Date",
ylabel="Hours",
)
def get_graph():
buffer = BytesIO()
plt.savefig(buffer, format="svg", transparent=True)
buffer.seek(0)
image_png = buffer.getvalue()
graph = base64.b64encode(image_png)
graph = graph.decode("utf-8")
buffer.close()
return graph
def get_chart(data, title="", xlabel="", ylabel=""):
x = data[0]
y = data[1]
plt.style.use("dark_background")
plt.switch_backend("SVG")
fig, ax = plt.subplots()
fig.set_size_inches(10, 4)
ax.plot(x, y)
first = x[0]
last = x[-1]
difference = last - first
if difference.days <= 14:
ax.xaxis.set_major_locator(mdates.DayLocator())
elif difference.days < 60 or len(x) < 60:
ax.xaxis.set_major_locator(mdates.WeekdayLocator())
ax.xaxis.set_minor_locator(mdates.DayLocator())
elif difference.days < 720:
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_minor_locator(mdates.WeekdayLocator())
else:
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.xaxis.set_minor_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
for label in ax.get_xticklabels(which="major"):
label.set(rotation=30, horizontalalignment="right")
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_title(title)
fig.tight_layout()
chart = get_graph()
return chart

63
common/time.py Normal file
View File

@ -0,0 +1,63 @@
import re
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from django.conf import settings
def now() -> datetime:
return datetime.now(ZoneInfo(settings.TIME_ZONE))
def _safe_timedelta(duration: timedelta | int | None):
if duration == None:
return timedelta(0)
elif isinstance(duration, int):
return timedelta(seconds=duration)
elif isinstance(duration, timedelta):
return duration
def format_duration(
duration: timedelta | int | None, format_string: str = "%H hours"
) -> str:
"""
Format timedelta into the specified format_string.
Valid format variables:
- %H hours
- %m minutes
- %s seconds
- %r total seconds
Values don't change into higher units if those units are missing
from the formatting string. For example:
- 61 seconds as "%s" = 61 seconds
- 61 seconds as "%m %s" = 1 minutes 1 seconds"
"""
minute_seconds = 60
hour_seconds = 60 * minute_seconds
day_seconds = 24 * hour_seconds
duration = _safe_timedelta(duration)
# we don't need float
seconds_total = int(duration.total_seconds())
# timestamps where end is before start
if seconds_total < 0:
seconds_total = 0
days = hours = minutes = seconds = 0
remainder = seconds = seconds_total
if "%d" in format_string:
days, remainder = divmod(seconds_total, day_seconds)
if "%H" in format_string:
hours, remainder = divmod(remainder, hour_seconds)
if "%m" in format_string:
minutes, seconds = divmod(remainder, minute_seconds)
literals = {
"%d": str(days),
"%H": str(hours),
"%m": str(minutes),
"%s": str(seconds),
"%r": str(seconds_total),
}
formatted_string = format_string
for pattern, replacement in literals.items():
formatted_string = re.sub(pattern, replacement, formatted_string)
return formatted_string