-
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Serializer DateTimeField has unexpected timezone information #3732
Comments
I asked on stack overflow and the conclusion is that the current timezone is only used for user-provided datetimes, as the datetime is serialized as-is after validation (ie after create or update). From the Django documentation, I find it hard to believe that this is intended behavior, but this is an issue with Django itself rather than the Rest Framework, so I will close this issue. |
Hi @tomchristie , I think this is a DRF bug. |
Hi, looking into the code in I have seen that using model serializer like this:
for the field In otherwords, if I'm not wrong, the Django timezone is considered only when the value is wrote into the storage backend. IMHO I think the return value of I write a pull request for this. Ciao |
@vpistis It'd be easier to review this if you could give an example of the API behavior you currently see, and what you'd expect to see (rather than discussing the implementation aspects first) |
Set your During create/update, send:
and get back:
But if you retrieve or list that object again, you get:
During creation, if the Z isn't specified, DRF assumes the client is using the timezone specified by There is also the question of whether to return times in the configured timezone when the trailing Z is provided during creation/update, such that sending:
returns:
I would be for this, as it keeps the response consistent with the response for lists/retrievals. Another option entirely would be to always return UTC times, even during creation/updates, but I find that less useful. Either way, having consistent timezones would be preferable to the 50/50ish situation we have right now. |
thanx @jonathan-golorry this is the exact behavior that I actually see. During create/update with default DATETIME_FORMAT, send: {
"appointment": "2016-12-19T10:00:00"
} and get back: {
"appointment": "2016-12-19T10:00:00+5:30"
} If you retrieve or list that object again , you get: {
"appointment": "2016-12-19T10:00:00+5:30"
} IMHO maybe should be a DRF setting to manage this behavior, for example, a setting to force the DateTimeField to be represented with the default timezone. |
thanx a lot @tomchristie |
The inconsistency between differing actions is the clincher for reopening here. I'd not realized that was the case. I'd expect us to use UTC throughout by default, although we could make that optional. |
For a production web app that use DRF 3.4.6, we have resolved with this workaround: |
If anyone wants to submit a pull request that either includes:
that'd be most welcome. |
I have wrote some code to test, but I'm not sure how use drf test cases. I don't know how can manage django settings to change Timezone and others settings at runtime. thanx |
I see this is closed but I am running drf 3.6.3 and in my postgres database I have this timestamp "2017-07-12 14:26:00-06" but when I get the data using postman I get this "timestamp": "2017-07-12T20:26:00Z". I looks like it is adding on the -06 hours. My django settings use tzlocal to set the timezone I am using a basic modelSerializer
Am I missing something? |
not closed, it's still open :) |
Oh ok. Thank you!
…On Jul 12, 2017 5:10 PM, "Valentino Pistis" ***@***.***> wrote:
not closed, it's still open :)
the red "closed button" that you see is for the reference issue.
...and you are right, the "bug" is still there ;(
The milestone is changed form 3.6.3 to 3.6.4.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#3732 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AIMBTcx-6PPbi_SOqeLCjeWV1Rb59-Ohks5sNVJ0gaJpZM4G0aRE>
.
|
Hi, I'm not sure if this is totally related to the original question, but should DRF be honouring any timezone overrides which are set, as per https://docs.djangoproject.com/en/1.11/ref/utils/#django.utils.timezone.activate ? I have a system where my users are associated with a timezone, and the API receives naiive datetimes. I expect to be able to convert those datetimes into the current user's timezone, but I notice that
Should this really be using |
Hi @RichardForshaw that seems like a distinct issue, but same ball park certainly. If we can get a decent set of test cases covering the expected behaviour, I think we’d certainly look at a PR here. My first thought is beyond that is to make sure you’re send the time zone info to the API, rather than relying on the server’s configured time zone. Beyond that I’m also inclined to think you need to be prepared to localise a time on the client. But yes, there’s an inconsistency here to be addressed. (Did I mention PRs Welcome? 🙂) |
carltongibson/django-filter#750 should be relevant here. I originally based django-filter's timezone handling on DRF, so the changes in 750 could easily be applied here. |
Sorry for my newbie-ness but what exactly is the issue here? The timestamp in my psql database is correct and Django is set to use the correct timezone. Is there a DRF settings to just make it not convert timestamps? |
Hi @michaelaelise — if you look at the example of the data (near the top):
There's no real issue on the assumption your client will handle the timezone conversion for you. (Except maybe No1, because who's to say your client has the same timezone setting as your server...?) But it's a little inconsistency, surely 2 and 3 should have the same format? (Even though a client will, correctly, accept either as equivalent values.) |
I'm inclined to close this.
I'm happy to look at a PR but I'm not sure I want to really consider this an Open Issue. |
Oh, I did not realize that in my original post in this issue thread that the "Z" meant that. |
One last thing. We are using django/drf on edge devices. So all datetimes being inserted do not care if it is naive or not because the edge device timezone is configured and postgres field will always be accurate for that devices datetime. The cloud server in the current scenario would then need to know the timezone of each device, it probably will, that just shifts the work from django/drf to the cloud app to handle. |
Assuming USE_TZ, DRF is already returning date times with the time zone info. So it’s already doing what you need there. The only issue here is whether the same DT is formatted as in one time zone or another. (But they’re still the same time.) |
IMHO this is the problem: the returned string! Anyway, thanx a lot for your patience, support and your great work to this fantastic project! |
@vpistis my point here is just that the represented date is correct, just the representation Is not expected. As soon as you parse that to a native Date, however your language handles that, there is no difference. I would expect users to be parsing the date string to a Date, however their client language provides for that, rather than consuming the raw string. I accept if you’re consuming the raw string your expectations wont be met here. (But don’t do that: imagine if we sent UNIX timestamps; there’s no way you’d consume those raw. Convert to a proper Date object, whatever that is in your client language.) I’m really happy to take a PR on this. (I haven’t closed it yet!) But it’s been nearly two years since reported and nine months since the first comment (yours, a year later). Nobody has even given us a failing test case. It can’t be that important to anyone. As such it’s hard to allocate it time. (As such I’m inclined to close it on the basis that we’ll take a PR if one ever turns up) |
Hi all, this should be fixed by #5408. If you have the time to install the branch and verify that everything is working as expected, that would be fantastic. Thanks! |
I think the issue was somehow, re-introduced: When I changed the default TZ from UTC to Europe/Amsterdam, one of the tests failed and I noticed DRF is serializing to something different from the default TZ edit: issue was related to test/factory setup. Test setup below.model class Something(StampedModelMixin):
MIN_VALUE = 1
MAX_VALUE = 500
id = models.BigAutoField(primary_key=True) # pylint: disable=blacklisted-name
product_id = models.BigIntegerField(db_index=True)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
percentage = models.IntegerField()
enabled = models.IntegerField() factory class SomethingFactory(factory.django.DjangoModelFactory):
""" Base Factory to create records for Something
"""
start_time = factory.Faker('date_time', tzinfo=get_default_timezone())
end_time = factory.Faker('date_time', tzinfo=get_default_timezone())
percentage = factory.Faker('random_int', min=1, max=500)
enabled = factory.Faker('random_element', elements=[0, 1])
class Meta: # pylint: disable=missing-docstring
model = Something unit test class TestSomething:
def test__get__empty(self):
# preparation of data
series = SeriesFactory.create(product_id=2)
SomethingFactory.create_batch(3, product_id=1)
# prepare request params
url = reverse('series-somethings', kwargs={'pk': series.pk})
# call the endpoint
response = self.client.get(url)
# asserts
assert response.data == []
def test__get__single(self):
# preparation of data
series = SeriesFactory.create(product_id=1)
old_somethings = SomethingFactory.create_batch(1, product_id=1)
# prepare request params
url = reverse('series-somethings', kwargs={'pk': series.pk})
# call the endpoint
response = self.client.get(url)
# asserts
assert SomethingSerializer(old_somethings, many=True).data == response.data view class SomethingElseView(APILogMixin, ModelViewSet):
@action(detail=True, methods=['get'])
def somethings(self, request, pk=None):
""" GET endpoint for Somethings
.. seealso:: :func:`rest_framework.decorators.action`
"""
otherthings = self.get_object()
something_qs = Something.objects.all()
something_qs = something_qs.filter(product_id=otherthings.product_id)
serializer = self.something_serializer_class(something_qs, many=True)
return Response(serializer.data) Serializer class SomethingSerializer(serializers.ModelSerializer):
class Meta:
model = Something
list_serializer_class = SomethingListSerializer
fields = '__all__'
extra_kwargs = {
'percentage': {'min_value': Something.MIN_VALUE,
'max_value': Something.MAX_VALUE}
}
read_only_fields = ('id',
'ts_activated',
'ts_created',
'ts_updated') Test ipdb result ipdb> old_somethings
[{'product_id': 1, 'start_time': datetime.datetime(2011, 7, 13, 1, 10, 33, tzinfo=<DstTzInfo 'Europe/Amsterdam' LMT+0:20:00 STD>), 'end_time': datetime.datetime(2003, 3, 10, 9, 31, tzinfo=<DstTzInfo 'Europe/Amsterdam' LMT+0:20:00 STD>), 'percentage': 103, 'enabled': 0}]
ipdb> response.data
[OrderedDict([('id', 1), ('ts_created', '2019-02-27 14:16:33'), ('ts_updated', '2019-02-27 14:16:33'), ('product_id', 1), ('start_time', '2011-07-13 02:50:33'), ('end_time', '2003-03-10 10:11:00'), ('percentage', 103), ('enabled', 0)])] Test Result E AssertionError: assert [OrderedDict(...nabled', 0)])] == [OrderedDict([...nabled', 0)])]
E At index 0 diff: OrderedDict([('id', 1), ('ts_created', '2019-02-27 14:38:15'), ('ts_updated', '2019-02-27 14:38:15'), ('product_id', 1), ('start_time', '2011-07-13 01:10:33'), ('end_time', '2003-03-10 09:31:00'), ('percentage', 103), ('enabled', 0)]) != OrderedDict([('id', 1), ('ts_created', '2019-02-27 14:38:15'), ('ts_updated', '2019-02-27 14:38:15'), ('product_id', 1), ('start_time', '2011-07-13 02:50:33'), ('end_time', '2003-03-10 10:11:00'), ('percentage', 103), ('enabled', 0)])
E Full diff:
E - [OrderedDict([('id', 1), ('ts_created', '2019-02-27 14:38:15'), ('ts_updated', '2019-02-27 14:38:15'), ('product_id', 1), ('start_time', '2011-07-13 01:10:33'), ('end_time', '2003-03-10 09:31:00'), ('percentage', 103), ('enabled', 0)])]
E ? ^^^^^^^^^^ ^^^^^^^^^^^
E + [OrderedDict([('id', 1), ('ts_created', '2019-02-27 14:38:15'), ('ts_updated', '2019-02-27 14:38:15'), ('product_id', 1), ('start_time', '2011-07-13 02:50:33'), ('end_time', '2003-03-10 10:11:00'), ('percentage', 103), ('enabled', 0)])]
E ? Stack:
|
Hi @diegueus9 - could you possible reduce this to a simpler test case? You're comparing serialized fake data vs a view's response data. So it's not clear what the actual expected value is. I'd recommend comparing some result to a hardcoded value. e.g., assert SomethingSerializer(old_somethings, many=True).data == {'blah':'blah'} |
@rpkilby thanks for your answer. It was my mistake, the problem with my unit tests is that I was using factory-boy/faker for it without refreshing from DB, hence the difference, I just added
Should I remove my previous comment or should I leave it in case anyone else experiences the same false positive? |
Hi @diegueus9, no worries - I just hid the code in a |
I have
TIME_ZONE = 'Asia/Kolkata'
andUSE_TZ = True
in my settings.When I create a new object with the browsable api, the serializer displays the newly created object with datetimes that have a trailing
+5:30
, indicating the timezone. The database is storing the times in UTC.The unexpected behavior is that when the object is serialized again later, the datetimes are all in UTC format with a trailing
Z
. According to the Django docs on current and default time zone, I would expect the serializer to convert the datetimes to the current time zone, which defaults to the default time zone, which is set byTIME_ZONE = 'Asia/Kolkata'
.Am I missing something?
The text was updated successfully, but these errors were encountered: