Reputation: 143
I'm developing a Django
project.
I want to sync classes automatically.
My code:
from datetime import timedelta
from celery.schedules import crontab
CELERYBEAT_SCHEDULE = {
'sync-classes': {
'task': 'scheduler.tasks.sync_classes',
'schedule': crontab(hour='0', minute=0),
}
CELERY_ENABLE_UTC = False
CELERY_TIMEZONE = 'Europe/London'
It works in the London time zone.
Each class has a different time zone. I want the schedule to run automatically based on the relevant time zone of classes.
In sync_classes()
method I get all classes and sync it in London Time zone.
Question: How can I sync every class based on its time zone?
Upvotes: 2
Views: 3120
Reputation: 18438
This code is untested when it comes to the Django/Celery part, but here's the idea:
Change your Celery-Beat's schedule to run every 15 minutes. That should pretty much cover all the TZ offsets in existence because most are either multiples of hour, or multiples of half an hour, with only a few cases being multiples of a quarter of hour (so this 15 minutes resolution should cover all the timezone offsets of the world):
CELERYBEAT_SCHEDULE = {
'sync-classes': {
'task': 'scheduler.tasks.sync_classes',
'schedule': crontab(minute='*/15'),
}
CELERY_ENABLE_UTC = False
CELERY_TIMEZONE = 'Europe/London'
At this point, Celery's timezone doesn't matter because we're gonna use the only decent timezone as a reference: UTC.
Create an auxiliary function that will give you the names of the timezones in which now ("now" meaning "when your task is run") is "almost" midnight. Your task will run every 15 minutes... Maybe a few milliseconds after the 15 minutes mark... so let's give it 10 minutes of buffer (that should be way, way more than enough). As long as the buffer is less than 15 minutes, you should be fine (fine meaning that you won't have one task and the next thinking that "now" is midnight and therefore running the synchronization twice)
This should help:
import pytz
import datetime
utc_now = pytz.utc.localize(datetime.datetime.utcnow())
collected_tz_names = []
for tz in pytz.all_timezones_set:
test_dt = utc_now.astimezone(pytz.timezone(tz))
print("tz: %s, test_dt.time() %s" % (tz, test_dt.time()))
is_midnight = (
datetime.time(hour=0, minute=0, second=0) <=
test_dt.time() <=
datetime.time(hour=0, minute=10, second=0)
)
if is_midnight:
collected_tz_names.append(tz)
print("collected %s" % collected_tz_names)
If you want to test it, change the utc_now = pytz.utc.localize(datetime.datetime.utcnow())
"probe" (or reference) to a few manual values, such as utc_now = pytz.utc.localize(datetime.datetime(year=2018, month=1, day=4, hour=4, minute=1, second=0)
)
Once you've collected the timezones in which now
is midnight in the collected_tz_names
list, run your sync method. Let's say the objects you need to synchronize are the User
(s), right? And that your User
model has an attribute tz_name
that indicates which is the timezone of each user. In that case, this should do:
for user in User.objects.filter(tz_name__in=collected_tz_names):
user.synchronize()
Keep in mind that in Daylight savings times switches, you might end up synchronizing twice.
Upvotes: 2