How to Fix Django Error While Posting into DateTimeField

I built a tiny events API in Django, pointed an Angular form at it, and boom every request died with a ValidationError. The fixes that worked, and a few extras I added so the same bug never trips me again.

My Code

# models.py
from django.db import models

class Event(models.Model):
title = models.CharField(max_length=120)
starts_at = models.DateTimeField()

On the Angular side I sent this:

// Angular (simplified)
$http.post('/api/events/', {
title: 'Launch',
starts_at: new Date('Tue Jun 30 2015 00:00:00 GMT-0500 (CDT)')
});

And the most naïve view ever tried to save it:

# views.py
import json
from django.http import JsonResponse
from .models import Event

def create_event(request):
payload = json.loads(request.body)
Event.objects.create(**payload) # 💥 kaboom
return JsonResponse({'ok': True})

The Crash Report

: ["'Tue Jun 30 2015 00:00:00 GMT-0500 (CDT)' value has an invalid "
"format. It must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."]

At first glance I thought “Django hates time-zones again.”
Not quite the issue was the string itself.

What Django Expects

DateTimeField only tries a small menu of parsers (all variations of ISO 8601).
Angular’s string contains extras Django does not parse:

  • weekday (Tue)
  • month name (Jun)
  • the literal text “GMT” plus offset and zone (GMT-0500 (CDT))

Result: validation fails before my code even runs.

Corect Code

Angular can emit ISO without plugins:

dt = new Date('Tue Jun 30 2015 00:00:00 GMT-0500 (CDT)');

$http.post('/api/events/', {
title: 'Launch',
starts_at: dt.toISOString() // "2015-06-30T05:00:00.000Z"
});

No Django changes, no tears.

If a legacy frontend (or another team) must keep that verbose format, extend Django’s input formats.

With Django REST Framework

# serializers.py
from rest_framework import serializers
from .models import Event

class EventSerializer(serializers.ModelSerializer):
starts_at = serializers.DateTimeField(
input_formats=['%a %b %d %Y %H:%M:%S GMT%z (%Z)']
)

class Meta:
model = Event
fields = '__all__'

Plain Django View

# utils.py
from datetime import datetime
FMT = '%a %b %d %Y %H:%M:%S GMT%z (%Z)'

def parse_browser_datetime(raw):
return datetime.strptime(raw, FMT)

Then:

['starts_at'] = parse_browser_datetime(payload['starts_at'])
Event.objects.create(**payload)

Stretch Goals

Accept both ISO and the verbose string

input_formats = [
'%Y-%m-%dT%H:%M:%S.%fZ', # ISO 8601
'%a %b %d %Y %H:%M:%S GMT%z (%Z)', # browser string
]
starts_at = serializers.DateTimeField(input_formats=input_formats)

Drop-in mixin for any serializer

# mixins.py
class BrowserDateTimeMixin(serializers.Serializer):
browser_formats = [
'%a %b %d %Y %H:%M:%S GMT%z (%Z)',
'%a %b %d %Y %H:%M:%S %Z%z'
]

def build_standard_field(self, name, model_field):
field_class, kwargs = super().build_standard_field(name, model_field)
if isinstance(model_field, models.DateTimeField):
extra = {'input_formats': self.browser_formats + kwargs.get('input_formats', [])}
kwargs.update(extra)
return field_class, kwargs

Unit test so no one breaks it later

rest_framework.test import APITestCase
from django.urls import reverse

class EventAPITests(APITestCase):
def test_verbose_datetime_is_accepted(self):
data = {
"title": "Launch",
"starts_at": "Tue Jun 30 2015 00:00:00 GMT-0500 (CDT)"
}
res = self.client.post(reverse('event-list'), data, format='json')
self.assertEqual(res.status_code, 201)

Angular helper that always returns ISO

// angular-date.service.js
angular.module('app').factory('datetimeISO', () => ({
toISO: d => (d instanceof Date ? d.toISOString() : d)
}));

Inject datetimeISO wherever you prepare POST bodies.

Final Thought

Whenever I control both ends of an API, I slam everything into ISO 8601 and call it a day it’s the universal handshake computers already understand.
But real projects inherit odd formats, third-party widgets, and historical sins. When that happens, extending input_formats (or writing one tiny parser) keeps the code honest and keeps clients happy.

Related blog posts