I am in the process of migrating an application from django 1.2 To 1.4.
I have a daily task object which contains a time of day that task should be completed:
class DailyTask(models.Model):
time = models.TimeField()
last_completed = models.DateTimeField()
name = models.CharField(max_length=100)
description = models.CharField(max_length=1000)
weekends = models.BooleanField()
def __unicode__(self):
return '%s' % (self.name)
class Meta:
db_table = u'dailytask'
ordering = ['name']
In order to check if a task is still required to be completed today, I have the following code:
def getDueDailyTasks():
dueDailyTasks=[]
now = datetime.datetime.now()
try:
dailyTasks = DailyTask.objects.all()
except dailyTask.DoesNotExist:
return None
for dailyTask in dailyTasks:
timeDue = datetime.datetime(now.year,now.month,now.day,dailyTask.time.hour,dailyTask.time.minute,dailyTask.time.second)
if timeDue<now and timeDue>dailyTask.last_completed:
if dailyTask.weekends==False and now.weekday()>4:
pass
else:
dueDailyTasks.append({'id':dailyTask.id,
'due':timeDue,
'name': dailyTask.name,
'description':dailyTask.description})
return dueDailyTasks
This worked fine under 1.2, But under 1.4 I get the error:
can't compare offset-naive and offset-aware datetimes
due to the line
if timeDue<now and timeDue>dailyTask.last_completed
and both comparison clauses throw this error.
I have tried making timeDue timezone aware by adding pytz.UTC as an argument, but this still raises the same error.
I've read some of the docs on timezones but am confused as to whether I just need to make timeDue timezone aware, or whether I need to make a fundamental change to my db and existing data.
Check the thorough document for detail info.
Normally, use django.utils.timezone.now to make an offset-aware current datetime
>>> from django.utils import timezone
>>> timezone.now()
datetime.datetime(2012, 5, 18, 13, 0, 49, 803031, tzinfo=<UTC>)
And django.utils.timezone.make_aware to make an offset-aware datetime
>>> timezone.make_aware(datetime.datetime.now(), timezone.get_default_timezone())
datetime.datetime(2012, 5, 18, 21, 5, 53, 266396, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)
You could then compare both offset-aware datetimes w/o trouble.
Furthermore, you could convert offset-awared datetime to offset-naive datetime by stripping off timezone info, then it could be compared w/ normal datetime.datetime.now(), under utc.
>>> t = timezone.now() # offset-awared datetime
>>> t.astimezone(timezone.utc).replace(tzinfo=None)
datetime.datetime(2012, 5, 18, 13, 11, 30, 705324)
USE_TZ is True 'by default' (actually it's False by default, but the settings.py file generated by django-admin.py startproject set it to True), then if your DB supports timezone-aware times, values of time-related model fields would be timezone-aware. you could disable it by setting USE_TZ=False(or simply remove USE_TZ=True) in settings.
Related
I am using factory-boy for creating instances of a Django model, and I am always getting the same value returned when using factory.fuzzy.FuzzyDateTime.
Minimal example:
# factory class
class FooFactory(DjangoModelFactory):
class Meta:
# models.Foo has a dt_field that is a DateTimeField
model = models.Foo
# creation of object, in unit tests
# I can't move the dt_field declaration
# into the factory definition since different
# unit tests use different start / end points
# for the datetime fuzzer
now = datetime.datetime.now(tz=pytz.timezone("America/New_York"))
one_week_ago = now - datetime.timedelta(days=7)
FooFactory.create_batch(
10,
dt_field=factory.fuzzy.FuzzyDateTime(
start_dt=one_week_ago, end_dt=now
)
)
When inspecting the Foo models after factory creation, the dt_field has the same date:
>>> [r.dt_field for r in Foo.objects.all()]
>>> [datetime.datetime(2022, 12, 10, 20, 15, 31, 954301, tzinfo=<UTC>), datetime.datetime(2022, 12, 10, 20, 15, 31, 961147, tzinfo=<UTC>),
datetime.datetime(2022, 12, 10, 20, 15, 31, 967383, tzinfo=<UTC>), ...]
The core issue was that the model in my code had auto_now_add=True in the model definition. This is a Django internal that always overrides the provided value and therefore can't be used with factory-boy.
Instead, the auto_now_add field is removed in favor of default=timezone.now, and the field can be fuzzed with factory-boy correctly.
I still can't find a working solution, hence I decided to throw my first post. Note, I'm a beginner in Django.
Building a portal that displays days since the user has been verified.
I tested my model without using custom DateField (verified_since), just with date_joined, and it works properly - showing the count of days remaining (I countdown from 365 days, I am not worried about the leap year yet) since the user has registered in the database vs. today.
class Profile(models.Model):
verified_since = models.DateField(default=now)
#property
def ver_since(self):
return 365 - (now() - self.user.date_joined).days
Now if I use verified_since instead date_joined, I get an error. I suspect this is due to a different format of a date, or maybe a string format instead of a date which can't be subtracted then from datetime.now()
verified_since is manually entered date on the form by user.
class Profile(models.Model):
verified_since = models.DateField(default=now)
#property
def ver_since(self):
return 365 - (now() - self.user.verified_since).days
Here is settings:
TIME_ZONE = 'CET'
USE_I18N = True
USE_L10N = False
USE_TZ = True
TIME_INPUT_FORMATS = ('%H:%M',)
DATE_INPUT_FORMATS = ['%d/%m/%Y']
datetime.now() gives you datetime object, while your 'verified_since' is a date field. So just convert your current datetime to date.
>>> verified_since
datetime.date(2022, 4, 8)
>>> datetime.now().date()
datetime.date(2022, 4, 12)
>>> 365 - (datetime.now().date() - verified_since).days
361
PS: I don't know what ver_since is for, but logically speaking, it's definitely not "verified since - in days"
My model as these fields:
date = models.DateField()
start_time = models.TimeField()
end_time = models.TimeField()
I would like to annotate the queryset with start_datetime and end_datetime, like so:
class SessionQuerySet(models.QuerySet):
def with_datetimes(self):
return self.annotate(
start_datetime=ExpressionWrapper(
F('date') + F('start_time'),
output_field=models.DateTimeField()
),
end_datetime=ExpressionWrapper(
F('date') + F('end_time'),
output_field=models.DateTimeField()
),
)
However, the output field in the query results in a naive datetime:
>>> Session.objects.with_datetimes()[0].start_datetime
<<< datetime.datetime(2021, 9, 20, 17, 0)
I would like the dates to be localized within the query.
I tried wrapping the above expressions in django.db.models.functions.Cast(), with output_field=DateTimeField(), but it casts to UTC and not the local timezone.
Essentially what I need is the equivalent of the Postgres at time zone feature to convert a naive time to localtime. Is there a way to do that in Django?
Yes. You can use any Postgres function by writing a custom django database function.
Here is a custom django database function for the equivalent of the Postgres at time zone.
Django 4.0
from django.db.models import ExpressionWrapper, F, Func
from django.db import models
class AtTimeZone(Func):
function = 'AT TIME ZONE'
template = "%(expressions)s %(function)s '%(timezone)s'"
class SessionQuerySet(models.QuerySet):
def with_datetimes(self):
return self.annotate(
start_datetime=ExpressionWrapper(
F('date') + F('start_time'),
output_field=models.DateTimeField()
),
end_datetime=ExpressionWrapper(
F('date') + F('end_time'),
output_field=models.DateTimeField()
),
start_local_datetime=AtTimeZone(F('start_datetime', timezone='Europe/Berlin')
)
The above is for a model with the initial fields of: date, start_time, and end_time.
Here are the docs regarding django's custom database functions. https://docs.djangoproject.com/en/4.0/ref/models/expressions/#func-expressions.
As of the start of 2022, the docs don't provide many examples on how to create custom database functions. This should help.
I think you can use the library pytz (https://pypi.org/project/pytz/) to localize the naive datetime.
from datetime import datetime
from pytz import timezone
tz = timezone('Europe/Amsterdam')
naive_dt = datetime(2021, 9, 20, 17, 0)
localized_dt = tz.localize(naive_dt)
print(localized_dt)
I created a form with a DateField and a TimeField. When printing these from the routes.py I get these example values:
TimeField: 17:30:00
DateField: 2021-07-12
How can I turn those values into a datetime object, which I can submit to my Postgres Database? The required object format is DateTime. The Postgres Table is set up as TIMESTAMPTZ. I tried replace() to add the time to the DateField Data. But that does not work. I am new to datetimes and such, so please excuse my ignorance. How do the Timezones work? because I probably need to add the timezone somehow for the TIMESTAMPTZ, right?
Below is the minimal code for the functioning
forms.py
class CreateEvent(Form):
dt = DateField('DateTimePicker')
start_time = TimeField('Start')
submit = SubmitField("Submit")
models.py
class Events(db.Model):
__tablename__='events'
eid = db.Column(db.Integer, primary_key = True)
event_day = db.Column(db.DateTime, nullable=False)
start_time = db.Column(db.DateTime, nullable=False)
def __init__(self, event_day, start_time):
self.event_day = event_day
self.start_time = start_time
routes.py
if request.method == 'POST':
if form.validate() == False:
return render_template('create.html', form=form)
else:
event_day = form.dt.data
start_time = form.start_time.data
print(start_time)
print(event_day)
start_time = event_day.replace(time=start_time)
newevent = Events(event_day, start_time)
db.session.add(newevent)
db.session.commit()
return "Success"
Just in case, here is the Postgres Create Statement for the table:
CREATE TABLE events (
eid serial PRIMARY KEY NOT NULL,
event_day TIMESTAMPTZ NOT NULL,
start_time TIMESTAMPTZ NOT NULL,
);
pip install dateutil:
https://dateutil.readthedocs.io/en/stable/
then:
from dateutil.parser import parse
TimeField = "17:30:00"
DateField = "2021-07-12"
parse(DateField + ' ' + TimeField)
datetime.datetime(2021, 7, 12, 17, 30)
You probably don't need to add a timezone, Postgres will use the timezone setting for the server. Postgres does not actually store the timezone in a timestamptz field. It just uses the timezone setting to rotate the value to a UTC time for storage.
Not tested but as an example, and you may have to install pytz from PIP (if not already present on your system). Python knows so-called naive datetime objects (that have no timezone context), and time zone-aware datetime objects.
So the goal is to build a string with the date and the time, then parse it into a datetime object. Next, we add the desired time zone (in this example it is assumed US/Pacific so change as appropriate).
import datetime
import pytz
TimeField = "17:30:00"
DateField = "2021-07-12"
current_tz = pytz.timezone('US/Pacific')
# parse string into naive datetime object
dt_naive = datetime.datetime.strptime(f"{DateField} {TimeField}", "%Y-%m-%d %H:%M:%S")
# convert to time zone-aware datetime
dt_aware = current_tz.localize(dt_naive)
i created a todo model in django with a method of clear old todo that is supposed to delete todos that were published more than 24 hours ago, i can't seem to be able to compare datetime and timezone instances in my if condition
class Todo(models.Model):
description = models.CharField(max_length=200)
Todo_date = models.DateTimeField('Todo Date')
pub_date = models.DateTimeField('Date Published')
def __str__(self):
return self.description
def create_todo(self, description, Todo_date, pub_date):
todo = Todo(description=description,
Todo_date=Todo_date, pub_date=pub_date)
todo.save()
return todo
def delete_todo(self, description):
todo = Todo.objects.get(description=description)
todo.delete()
return "Todo removed"
def clear_old_todo(self):
todos = Todo.objects.all()
time_limit = datetime.timedelta(hours=24)
for todo in todos:
if (timezone.now()-todo.pub_date) > (timezone.now()-time_limit):
todo.delete()
return "old todo cleared"
>>> Todo.clear_old_todo("self")
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "E:\projects\1stDjangoApp\ToDoList\ToDo\models.py", line 36, in clear_old_todo
if (timezone.now()-todo.pub_date) > (timezone.now()-time_limit):
TypeError: '>' not supported between instances of 'datetime.timedelta' and 'datetime.datetime'
Subtracting a datetime from a datetime gives you a timedelta, the difference between the two times. Subtracting a timedelta from a datetime gives you a datetime, a new timestamp different from the first by the amount of the timedelta.
In timezone.now()-todo.pub_date, you're subtracting two datetime.
In timezone.now()-time_limit, you're subtract a timedelta from a datetime.
You either want to do timezone.now() - todo.pub_date to produce a timedelta and check if that timedelta is >/< some specific value (i.e. compare two timedeltas), or you want to do timezone.now() - time_limit to produce a datetime in the past and check whether that is >/< your todo.pub_date datetime.