1

I have USE_TZ = True enabled in my settings.py file, so dateTime values are stored as UTC. When I want to display these dates/times on a template, they will be displayed in whatever timezone I have set in setings.py with TIME_ZONE = 'America/Toronto'. But what if my users are in different timezones and I want it to automatically change the time based on the users timezone? I found that using javascript I can do :

new Date(djangoDate)).toLocaleString()

will format the date and time to my timezone, regardless of what I have in TIME_ZONE = 'America/Toronto'

But is there a way to do this in python/Django?

Everything I have tried, like {% load tz %} relies on TIME_ZONE = 'America/Toronto'. in settings.py.

I have also tried: timezone.activate(pytz.timezone('America/Winnipeg')) in my view, which seems to display the dates as intended, but found that this will actually change the UTC value in the database when using forms to update dateTime data, which is behavior I do not want. I want to store in UTC, and render/display in local time, automatically without the user having to tell the application what timezone they are in. Is this possible?

Edit Elaborating on timezone.activate - it will remove 1 hour from buildStart when form is submitted, without user changing this field

views.py:

class BuildUpdateView_Building(LoginRequiredMixin,UpdateView):
    model = Build
    form_class = EditBuild_building
    template_name = 'build_edit_building.html'
    login_url = 'login'

    def get(self, request, *args, **kwargs):
        proceed = True
        try:
            instance = Build.objects.get(id = (self.kwargs['pk']))
        except:
            return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
        if instance.buildActive == False:
            proceed = False
        if instance.deleted == True:
            proceed = False
        #all appears to be well, process request
        if proceed == True:
            form = self.form_class(instance=instance)
            timezone.activate(pytz.timezone(self.request.user.t_zone))
            customer = self.request.user.PSScustomer
            choices = [(item.id, (str(item.first_name) + ' ' + str(item.last_name)))  for item in CustomUser.objects.filter(isDevice=False, PSScustomer = customer)]
            choices.insert(0, ('', 'Unconfirmed'))
            form.fields['buildStrategyBy'].choices = choices
            form.fields['buildProgrammedBy'].choices = choices
            form.fields['operator'].choices = choices
            form.fields['powder'].queryset = Powder.objects.filter(PSScustomer = customer)
            context = {}
            context['buildID'] = self.kwargs['pk']
            context['build'] = Build.objects.get(id = (self.kwargs['pk']))
            return render(request, self.template_name, {'form': form, 'context': context})
        else:
            return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer editable here, or has been deleted, please return to dashboard</h2>")


    def form_valid(self, form):
        proceed = True
        try:
            instance = Build.objects.get(id = (self.kwargs['pk']))
        except:
            return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
        if instance.buildActive == False:
            proceed = False
        if instance.deleted == True:
            proceed = False
        #all appears to be well, process request
        if proceed == True:
            form.instance.editedBy = (self.request.user.first_name)+ " " +(self.request.user.last_name)
            form.instance.editedDate = timezone.now()
            print('edited date ' + str(form.instance.editedDate))
            form.instance.reviewed = True
            next = self.request.POST['next'] #grabs prev url from form template
            form.save()
            build = Build.objects.get(id = self.kwargs['pk'])
            if build.buildLength >0:
                anticipated_end = build.buildStart + (timedelta(hours = float(build.buildLength)))
                print(anticipated_end)
            else:
                anticipated_end = None
            build.anticipatedEnd = anticipated_end
            build.save()
            build_thres_updater(self.kwargs['pk'])#this is function above, it updates threshold alarm counts on the build
            return HttpResponseRedirect(next) #returns to this page after valid form submission
        else:
            return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")

form.py

class EditBuild_building(forms.ModelForm):
    buildStart = forms.DateTimeField(input_formats = ['%Y-%m-%dT%H:%M'],widget = forms.DateTimeInput(attrs={'type': 'datetime-local','class': 'form-control'},format='%Y-%m-%dT%H:%M'), label = "Build Start Time")
    def __init__(self, *args, **kwargs):# for ensuring fields are not left empty
        super(EditBuild_building, self).__init__(*args, **kwargs)
        self.fields['buildDescrip'].required = True

    class Meta:
        model = Build
        fields = ['buildDescrip', 'buildStart','buildLength','project', 'customer','powder','buildNotes','buildLayerHeight',
                'parameters','recoaterType','buildProgrammedBy','buildStrategyBy','operator',
                'tempHighThres','tempLowThres',
                'humidHighThres','humidLowThres','hardRecoatThres',]

        labels = {
            'buildDescrip': ('Build Description'),
            'buildStart': ('Build Start Time'),
            'buildLength': ('Build Length (hours)'),
            'buildNotes': ('Build Notes'),
            'powder': ('Powder Used for Build'),
            'tempHighThres': ('High Temperature Alarm (℃)'),
            'tempLowThres': ('Low Temperature Alarm (℃)'),
            'humidHighThres': ('High Humidity Alarm (%)'),
            'humidLowThres': ('Low Humidity Alarm (%)'),
            'hardRecoatThres': ('Hard Recoat Threshold'),
            'buildLayerHeight': ('Layer Height (microns)'),
            'recoaterType': ('Recoater Type'),
            'customer': ('Customer'),
            'operator': ('Machine Setup by'),
            'project': ('Project'),
            'parameters': ('Build Parameters'),
            'buildProgrammedBy': ('Build Programmed by'),
            'buildStrategyBy': ('Build Strategy by'),

        }

        widgets = {'buildDescrip': forms.TextInput(attrs={'class': 'required'}),

When I submit this form, it will subtract one hour from buildStart in the database. It also sets the seconds to 00 for some reason? When I remove timezone.activate this behavior disappears...

It appears to subtract the hour on the form.save() operation

MattG
  • 1,682
  • 5
  • 25
  • 45
  • 2
    You typically want the client side to handle this - the backend should deal with dates consistently – Trent May 19 '21 at 01:19
  • That's sorta what I figured - So is Javascript the best way to go on this? – MattG May 19 '21 at 01:23
  • yes, until you have a reason to lock the entire backend down to a specific timezone. – Trent May 19 '21 at 01:33
  • 1
    @Trent if you let the client side handle the time, your database will have unordered datetime. For timezone aware datetime objects djagno provide timezone utils. https://docs.djangoproject.com/en/3.2/topics/i18n/timezones/ – ItsMilann May 19 '21 at 01:50
  • 3
    *this will actually change the UTC value in the database when using forms to update dateTime data* You should expand on that, as that shouldn't be the case with `activate()` in most form situations. If you're letting Django handle the conversion on both sides, it will convert the UTC value to the user's timezone when populating the form, then convert it back to UTC when saving the form. More generally, Django does want you want except for the "automatic" part. See [here](https://docs.djangoproject.com/en/dev/topics/i18n/timezones/#selecting-the-current-time-zone) for why. – Kevin Christopher Henry May 19 '21 at 02:46
  • @KevinChristopherHenry I updated my question to expand on activate. I am not sure why it is subtracting an hour from the utc time when this is included – MattG May 19 '21 at 12:08
  • If you want to handle this server-side, you first of all need to know the timezone of your visitor. Unless you ask them in some way or another (e.g. preference settings in the user's account), you won't know that and can't adjust the time server-side. – deceze May 19 '21 at 12:27
  • Yes, this is a value in the database as `self.request.user.t_zone` – MattG May 19 '21 at 12:40
  • @MattG: One thing I notice is that you're calling `activate()` on `GET`, but I don't see it on the `POST` call. You need to use the same time zone on both sides of the operation, and a typical place to call `activate()` is in middleware (or somewhere else universal and early). Removing seconds is correct because you stripped that information from the form field with your choice of `format`. (You could also consider making this field read-only. Do you want people to be editing a build start time?) – Kevin Christopher Henry May 19 '21 at 12:44
  • @KevinChristopherHenry: Thanks for taking a look. I just added `activate()` at the beginning of the `POST` call, and it had no effect, still removing 1 hour from time. I do want the user to have the ability to change buildStart. – MattG May 19 '21 at 12:52
  • If I remove all the `activate()` calls, and instead use `{% timezone "Europe/Paris" %}` in the templates, it will display local time, but when I call `print(form.cleaned_data['buildStart'])` in `form_valid` it returns the local time, not utc time. So I almost need to convert it to UTC at this point? Seems dangerous and/or incorrect? – MattG May 19 '21 at 13:38
  • It doesn't matter what the timezone is in the Python object, Django will convert it to UTC when it saves to the database. – Kevin Christopher Henry May 19 '21 at 13:52
  • unfortunately, it's not. It seems like when `form.save()` is called, it is storing the time in the DB as whatever local time was set in the form. I almost need to re-convert it. Timezones are so confusing! – MattG May 19 '21 at 14:18
  • Not sure what the best approach is here, but after troubleshooting some more, I think it was best to re-write the question so it is more clear as to what is happening. I will leave this one open in case it is helpful for someone else. New question here: https://stackoverflow.com/questions/67606274/how-to-use-timezones-in-django-forms – MattG May 19 '21 at 15:30

0 Answers0