Repository: paperless-ngx/paperless-ngx

Issue:
[BUG] How to handle TZ differences on frontend.
### Description

Rather than continuing to hijack the thread in which it came up (https://github.com/paperless-ngx/paperless-ngx/issues/926#issuecomment-1125421989) I wanted to discuss this to see what everyone thinks is the best solution. Sorry its long, dates are... complicated AF.

To summarize: the backend currently passes UTC timestamps for document dates. There is no "timezone" but UTC dates always have an offset (e.g. +1:00) that is related to timezone ([they are not the same](https://spin.atomicobject.com/2016/07/06/time-zones-offsets/)) but the backend has an explicit `PAPERLESS_TIME_ZONE` which can be set and then can differ from the frontend which doesnt get that variable but rather pulls its timezone from the system (Javascript does this). That can lead to situations like the above linked comment where the date looks wrong because its being interpolated to a different timezone than the backend is set to.

First of all, **I think this is likely a rare situation**. I doubt most users would intentionally do this but perhaps thats more of a reason to discuss this, i.e. to warn them. Also it would happen if the user is traveling etc.

Proposed solutions:

Edit: #957!


Discussion:
Honestly, I still don't understand at all why the timezone (actually, the whole time part of the datetime) is stored and used at all for the created date?? What is the benefit of that? If a document has the date 15.05.2022 written on it, that's what I always wan't to see, of course also when I'm travelling and my timezone is different.
I don't think anybody would ever want the date displayed in paperless to change based on where they currently are.

That would be a different story if it would be possible to enter the exact time of when the document was created (which I don't think would be useful).

That's why I vote for **option 5: Completely remove the timezone stuff for the created date**.

Please correct me if I'm wrong and the timezone actually has an usecase here.
Funny enough, option 4 achieves your option 5 :) If the backend sends a localized date the frontend can then remove time info without worrying about changing the intended date.

Actually option 3 would allow for the same thing (if we know the time zone of the date we can get the date in that timezone and just ignore the time).

But you are right the backend could literally just return the date, in both cases described above the frontend does still have to interpolate back-and-forth a bit. In some ways this does seem cleanest.
Thanks for being the voice of reason :) 

I think this is one of those situations where I have just overthought things so much I’m missing the forest from the trees (American idiom).

I’m going to experiment with date-only a bit, you’ve given me an idea that hopefully will yield a clean solution!

Resolving patch:
diff --git a/src/documents/models.py b/src/documents/models.py
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -300,6 +300,10 @@ def thumbnail_path(self):
     def thumbnail_file(self):
         return open(self.thumbnail_path, "rb")
 
+    @property
+    def created_date(self):
+        return timezone.localdate(self.created)
+
 
 class Log(models.Model):
 
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -1,7 +1,13 @@
+import datetime
 import math
 import re
 
+try:
+    import zoneinfo
+except ImportError:
+    import backports.zoneinfo as zoneinfo
 import magic
+from django.conf import settings
 from django.utils.text import slugify
 from django.utils.translation import gettext as _
 from rest_framework import serializers
@@ -214,6 +220,7 @@ class DocumentSerializer(DynamicFieldsModelSerializer):
 
     original_file_name = SerializerMethodField()
     archived_file_name = SerializerMethodField()
+    created_date = serializers.DateField(required=False)
 
     def get_original_file_name(self, obj):
         return obj.get_public_filename()
@@ -224,6 +231,18 @@ def get_archived_file_name(self, obj):
         else:
             return None
 
+    def update(self, instance, validated_data):
+        if "created_date" in validated_data and "created" not in validated_data:
+            new_datetime = datetime.datetime.combine(
+                validated_data.get("created_date"),
+                datetime.time(0, 0, 0, 0, zoneinfo.ZoneInfo(settings.TIME_ZONE)),
+            )
+            instance.created = new_datetime
+            instance.save()
+        validated_data.pop("created_date")
+        super().update(instance, validated_data)
+        return instance
+
     class Meta:
         model = Document
         depth = 1
@@ -236,6 +255,7 @@ class Meta:
             "content",
             "tags",
             "created",
+            "created_date",
             "modified",
             "added",
             "archive_serial_number",


Test patch: