Repository: bookwyrm-social/bookwyrm

Issue:
Pre-set user's timezone
**Is your feature request related to a problem? Please describe.**
I signed up and saw you can set a timezone. It was set to UTC by default.

**Describe the solution you'd like**
JavaScript has an Internationalization API that returns timezones. At attempt should be made to pre-set this for less friction.

```
const x = new Intl.DateTimeFormat()
x.resolvedOptions().timeZone
// => "Asia/Kolkata"
```


Reading:

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions


Discussion:
Almost every thing is done in Django, so JavaScript may not be able to help here, but I think Django can make a decent guess at timezones based on request headers
You're right, I think it can – what headers would be helpful here? Just curious to see if they're not a mess even though the standard is clean. :)

Originally, I thought of sending a hidden input with the timezone name at sign up and using that at some point down the line. HTTP headers would be every bit better.
Makes for some interesting reading, this: https://django.readthedocs.io/en/3.2.x/topics/i18n/timezones.html#selecting-the-current-time-zone

> The current time zone is the equivalent of the current [locale](https://django.readthedocs.io/en/3.2.x/topics/i18n/index.html#term-locale-name) for translations. However, there’s no equivalent of the Accept-Language HTTP header that Django could use to determine the user’s time zone automatically. Instead, Django provides time zone selection functions. Use them to build the time zone selection logic that makes sense for you.
>
> Most websites that care about time zones ask users in which time zone they live and store this information in the user’s profile. For anonymous users, they use the time zone of their primary audience or UTC. [pytz](http://pytz.sourceforge.net/) provides [helpers](http://pytz.sourceforge.net/#helpers), like a list of time zones per country, that you can use to pre-select the most likely choices.
On a related note, I was looking into fixing this and it seems like bookwyrm still uses pytz, but zoneinfo was made the official timezone implementation in django according to this (https://docs.djangoproject.com/en/4.1/topics/i18n/timezones/). Should we switch to zoneinfo or continue using pytz?
It would be good to switch to zoneinfo -- we're still using Django 3, which is why pytz is in use, but I'd like to get to Django 4 and that would be a helpful step towards it.
I've been having a look at this - two things : it seems like django does a very bad job at guessing - it just defaults to UTC (which isn't any better than what it currently is doing...) and headers don't seem to be useful, but I'll try and implement something with hidden fields like @MaybeThisIsRu suggested, I think that will work

Resolving patch:
diff --git a/bookwyrm/views/landing/register.py b/bookwyrm/views/landing/register.py
--- a/bookwyrm/views/landing/register.py
+++ b/bookwyrm/views/landing/register.py
@@ -1,4 +1,5 @@
 """ class views for login/register views """
+import pytz
 from django.contrib.auth import login
 from django.core.exceptions import PermissionDenied
 from django.shortcuts import get_object_or_404, redirect
@@ -55,6 +56,10 @@ def post(self, request):
         localname = form.data["localname"].strip()
         email = form.data["email"]
         password = form.data["password"]
+        try:
+            preferred_timezone = pytz.timezone(form.data.get("preferred_timezone"))
+        except pytz.exceptions.UnknownTimeZoneError:
+            preferred_timezone = pytz.utc
 
         # make sure the email isn't blocked as spam
         email_domain = email.split("@")[-1]
@@ -71,6 +76,7 @@ def post(self, request):
             local=True,
             deactivation_reason="pending" if settings.require_confirm_email else None,
             is_active=not settings.require_confirm_email,
+            preferred_timezone=preferred_timezone,
         )
         if invite:
             invite.times_used += 1


Test patch:
diff --git a/bookwyrm/tests/views/landing/test_register.py b/bookwyrm/tests/views/landing/test_register.py
--- a/bookwyrm/tests/views/landing/test_register.py
+++ b/bookwyrm/tests/views/landing/test_register.py
@@ -58,6 +58,7 @@ def test_register(self, *_):
                 "localname": "nutria-user.user_nutria",
                 "password": "mouseword",
                 "email": "aa@bb.cccc",
+                "preferred_timezone": "Europe/Berlin",
             },
         )
         with patch("bookwyrm.views.landing.register.login"):
@@ -68,6 +69,7 @@ def test_register(self, *_):
         self.assertEqual(nutria.username, f"nutria-user.user_nutria@{DOMAIN}")
         self.assertEqual(nutria.localname, "nutria-user.user_nutria")
         self.assertEqual(nutria.local, True)
+        self.assertEqual(nutria.preferred_timezone, "Europe/Berlin")
 
     @patch("bookwyrm.emailing.send_email.delay")
     def test_register_email_confirm(self, *_):
@@ -198,6 +200,58 @@ def test_register_invalid_username(self, *_):
         self.assertEqual(models.User.objects.count(), 1)
         validate_html(response.render())
 
+    def test_register_default_preferred_timezone(self, *_):
+        """invalid preferred timezone strings should just default to UTC"""
+        view = views.Register.as_view()
+        self.assertEqual(models.User.objects.count(), 1)
+
+        request = self.factory.post(
+            "register/",
+            {
+                "localname": "nutria1",
+                "password": "mouseword",
+                "email": "aa1@bb.cccc",
+                "preferred_timezone": "invalid-tz",
+            },
+        )
+        with patch("bookwyrm.views.landing.register.login"):
+            response = view(request)
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(models.User.objects.count(), 2)
+        nutria = models.User.objects.last()
+        self.assertEqual(nutria.preferred_timezone, "UTC")
+
+        request = self.factory.post(
+            "register/",
+            {
+                "localname": "nutria2",
+                "password": "mouseword",
+                "email": "aa2@bb.cccc",
+                "preferred_timezone": "",
+            },
+        )
+        with patch("bookwyrm.views.landing.register.login"):
+            response = view(request)
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(models.User.objects.count(), 3)
+        nutria = models.User.objects.last()
+        self.assertEqual(nutria.preferred_timezone, "UTC")
+
+        request = self.factory.post(
+            "register/",
+            {
+                "localname": "nutria3",
+                "password": "mouseword",
+                "email": "aa3@bb.cccc",
+            },
+        )
+        with patch("bookwyrm.views.landing.register.login"):
+            response = view(request)
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(models.User.objects.count(), 4)
+        nutria = models.User.objects.last()
+        self.assertEqual(nutria.preferred_timezone, "UTC")
+
     def test_register_closed_instance(self, *_):
         """you can't just register"""
         view = views.Register.as_view()