From 90e08b4863dfaecafee5b151478bda4513b12e85 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 23 Mar 2012 19:29:40 -0500 Subject: Make all datetime objects fully timezone aware This is most of the transition to Django 1.4 `USE_TZ = True`. We need to ensure we don't mix aware and non-aware datetime objects when dealing with datetimes in the code. Add a utc_now() helper method that we can use most places, and ensure there is always a timezone attached when necessary. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 12 +++++--- devel/views.py | 33 +++++++++------------- ...mpressed_size__chg_field_package_installed_s.py | 5 +++- .../0054_auto__add_field_donor_created.py | 5 +++- main/models.py | 4 +-- main/utils.py | 12 ++++++-- mirrors/management/commands/mirrorcheck.py | 5 +++- mirrors/utils.py | 13 +++++---- news/models.py | 7 +++-- packages/management/commands/signoff_report.py | 5 ++-- ...0007_auto__add_field_packagerelation_created.py | 5 +++- packages/views/flag.py | 5 ++-- packages/views/signoff.py | 4 +-- releng/management/commands/syncisos.py | 5 ++-- settings.py | 10 +++++-- sitemaps.py | 3 +- templates/devel/clock.html | 4 +-- 17 files changed, 80 insertions(+), 57 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index a8c58ba..60ee6ec 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -22,6 +22,7 @@ import tarfile import logging from datetime import datetime from optparse import make_option +from pytz import utc from django.core.management.base import BaseCommand, CommandError from django.db import connections, router, transaction @@ -29,8 +30,10 @@ from django.db.utils import IntegrityError from devel.utils import UserFinder from main.models import Arch, Package, PackageDepend, PackageFile, Repo +from main.utils import utc_now from packages.models import Conflict, Provision, Replacement + logging.basicConfig( level=logging.WARNING, format='%(asctime)s -> %(levelname)s: %(message)s', @@ -113,7 +116,8 @@ class RepoPackage(object): self.epoch = int(match.group(2)) elif k == 'builddate': try: - self.builddate = datetime.utcfromtimestamp(int(v[0])) + builddate = datetime.utcfromtimestamp(int(v[0])) + self.builddate = builddate.replace(tzinfo=utc) except ValueError: try: self.builddate = datetime.strptime(v[0], @@ -281,7 +285,7 @@ def populate_files(dbpkg, repopkg, force=False): filename=filename) pkg_files.append(pkgfile) PackageFile.objects.bulk_create(pkg_files) - dbpkg.files_last_update = datetime.utcnow() + dbpkg.files_last_update = utc_now() dbpkg.save() @@ -351,7 +355,7 @@ def db_update(archname, reponame, pkgs, force=False): dbpkg = Package(pkgname=pkg.name, arch=architecture, repo=repository) try: with transaction.commit_on_success(): - populate_pkg(dbpkg, pkg, timestamp=datetime.utcnow()) + populate_pkg(dbpkg, pkg, timestamp=utc_now()) except IntegrityError: logger.warning("Could not add package %s; " "not fatal if another thread beat us to it.", @@ -378,7 +382,7 @@ def db_update(archname, reponame, pkgs, force=False): if not force and pkg_same_version(pkg, dbpkg): continue elif not force: - timestamp = datetime.utcnow() + timestamp = utc_now() # The odd select_for_update song and dance here are to ensure # simultaneous updates don't happen on a package, causing diff --git a/devel/views.py b/devel/views.py index 328d52e..3a9be75 100644 --- a/devel/views.py +++ b/devel/views.py @@ -1,3 +1,10 @@ +from datetime import datetime, timedelta +import operator +import pytz +import random +from string import ascii_letters, digits +import time + from django import forms from django.http import HttpResponseRedirect from django.contrib.auth.decorators import \ @@ -16,19 +23,13 @@ from django.views.generic.simple import direct_to_template from django.utils.http import http_date from main.models import Package, PackageDepend, PackageFile, TodolistPkg -from main.models import Arch, Repo -from main.models import UserProfile +from main.models import Arch, Repo, UserProfile +from main.utils import utc_now from packages.models import PackageRelation from packages.utils import get_signoff_groups from todolists.utils import get_annotated_todolists from .utils import get_annotated_maintainers -from datetime import datetime, timedelta -import operator -import pytz -import random -from string import ascii_letters, digits -import time @login_required def index(request): @@ -85,22 +86,14 @@ def clock(request): devs = User.objects.filter(is_active=True).order_by( 'first_name', 'last_name').select_related('userprofile') - now = datetime.now() - utc_now = datetime.utcnow().replace(tzinfo=pytz.utc) - # now annotate each dev object with their current time - for dev in devs: - tz = pytz.timezone(dev.userprofile.time_zone) - dev.current_time = utc_now.astimezone(tz) - + now = utc_now() page_dict = { 'developers': devs, - 'now': now, - 'utc_now': utc_now, + 'utc_now': now, } response = direct_to_template(request, 'devel/clock.html', page_dict) if not response.has_header('Expires'): - # why this works only with the non-UTC date I have no idea... expire_time = now.replace(second=0, microsecond=0) expire_time += timedelta(minutes=1) expire_time = time.mktime(expire_time.timetuple()) @@ -168,12 +161,12 @@ def report(request, report_name, username=None): if report_name == 'old': title = 'Packages last built more than two years ago' - cutoff = datetime.utcnow() - timedelta(days=365 * 2) + cutoff = utc_now() - timedelta(days=365 * 2) packages = packages.filter( build_date__lt=cutoff).order_by('build_date') elif report_name == 'long-out-of-date': title = 'Packages marked out-of-date more than 90 days ago' - cutoff = datetime.utcnow() - timedelta(days=90) + cutoff = utc_now() - timedelta(days=90) packages = packages.filter( flag_date__lt=cutoff).order_by('flag_date') elif report_name == 'big': diff --git a/main/migrations/0050_auto__chg_field_package_compressed_size__chg_field_package_installed_s.py b/main/migrations/0050_auto__chg_field_package_compressed_size__chg_field_package_installed_s.py index 7aa0959..8368ae2 100644 --- a/main/migrations/0050_auto__chg_field_package_compressed_size__chg_field_package_installed_s.py +++ b/main/migrations/0050_auto__chg_field_package_compressed_size__chg_field_package_installed_s.py @@ -1,5 +1,6 @@ # encoding: utf-8 import datetime +from pytz import utc from south.db import db from south.v2 import SchemaMigration from django.db import models @@ -9,7 +10,9 @@ class Migration(SchemaMigration): def forwards(self, orm): db.alter_column('packages', 'compressed_size', self.gf('main.models.PositiveBigIntegerField')(default=0)) db.alter_column('packages', 'installed_size', self.gf('main.models.PositiveBigIntegerField')(default=0)) - db.alter_column('packages', 'last_update', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2000, 1, 1))) + old_date = datetime.datetime(2000, 1, 1) + old_date = old_date.replace(tzinfo=utc) + db.alter_column('packages', 'last_update', self.gf('django.db.models.fields.DateTimeField')(default=old_date)) def backwards(self, orm): db.alter_column('packages', 'compressed_size', self.gf('django.db.models.fields.BigIntegerField')(null=True)) diff --git a/main/migrations/0054_auto__add_field_donor_created.py b/main/migrations/0054_auto__add_field_donor_created.py index f4d5b15..c96c0f5 100644 --- a/main/migrations/0054_auto__add_field_donor_created.py +++ b/main/migrations/0054_auto__add_field_donor_created.py @@ -1,5 +1,6 @@ # encoding: utf-8 import datetime +from pytz import utc from south.db import db from south.v2 import SchemaMigration from django.db import models @@ -8,7 +9,9 @@ class Migration(SchemaMigration): def forwards(self, orm): # Adding field 'Donor.created' - db.add_column('donors', 'created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.date(2000, 1, 1)), keep_default=False) + old_date = datetime.datetime(2000, 1, 1) + old_date = old_date.replace(tzinfo=utc) + db.add_column('donors', 'created', self.gf('django.db.models.fields.DateTimeField')(default=old_date), keep_default=False) def backwards(self, orm): diff --git a/main/models.py b/main/models.py index 4f2d64e..7d01724 100644 --- a/main/models.py +++ b/main/models.py @@ -9,7 +9,7 @@ from django.contrib.auth.models import User from django.contrib.sites.models import Site from .fields import PositiveBigIntegerField, PGPKeyField -from .utils import cache_function, make_choice, set_created_field +from .utils import cache_function, make_choice, set_created_field, utc_now class UserProfile(models.Model): @@ -515,7 +515,7 @@ class TodolistPkg(models.Model): def set_todolist_fields(sender, **kwargs): todolist = kwargs['instance'] if not todolist.date_added: - todolist.date_added = datetime.utcnow() + todolist.date_added = utc_now() # connect signals needed to keep cache in line with reality from main.utils import refresh_latest diff --git a/main/utils.py b/main/utils.py index 8143ea6..e7e47c5 100644 --- a/main/utils.py +++ b/main/utils.py @@ -5,6 +5,7 @@ except ImportError: from datetime import datetime import hashlib +from pytz import utc from django.core.cache import cache @@ -91,12 +92,17 @@ def retrieve_latest(sender): return None +def utc_now(): + '''Returns a timezone-aware UTC date representing now.''' + return datetime.utcnow().replace(tzinfo=utc) + + def set_created_field(sender, **kwargs): - '''This will set the 'created' field on any object to datetime.utcnow() if - it is unset. For use as a pre_save signal handler.''' + '''This will set the 'created' field on any object to the current UTC time + if it is unset. For use as a pre_save signal handler.''' obj = kwargs['instance'] if hasattr(obj, 'created') and not obj.created: - obj.created = datetime.utcnow() + obj.created = utc_now() def groupby_preserve_order(iterable, keyfunc): diff --git a/mirrors/management/commands/mirrorcheck.py b/mirrors/management/commands/mirrorcheck.py index 8eb8b01..c2928e6 100644 --- a/mirrors/management/commands/mirrorcheck.py +++ b/mirrors/management/commands/mirrorcheck.py @@ -21,9 +21,11 @@ import sys import time from threading import Thread import types +from pytz import utc from Queue import Queue, Empty import urllib2 +from main.utils import utc_now from mirrors.models import MirrorUrl, MirrorLog logging.basicConfig( @@ -50,7 +52,7 @@ class Command(NoArgsCommand): def check_mirror_url(mirror_url): url = mirror_url.url + 'lastsync' logger.info("checking URL %s", url) - log = MirrorLog(url=mirror_url, check_time=datetime.utcnow()) + log = MirrorLog(url=mirror_url, check_time=utc_now()) try: start = time.time() result = urllib2.urlopen(url, timeout=10) @@ -61,6 +63,7 @@ def check_mirror_url(mirror_url): parsed_time = None try: parsed_time = datetime.utcfromtimestamp(int(data)) + parsed_time = parsed_time.replace(tzinfo=utc) except ValueError: # it is bad news to try logging the lastsync value; # sometimes we get a crazy-encoded web page. diff --git a/mirrors/utils.py b/mirrors/utils.py index f05ffc7..0f8fef8 100644 --- a/mirrors/utils.py +++ b/mirrors/utils.py @@ -1,11 +1,12 @@ +from datetime import timedelta + from django.db.models import Avg, Count, Max, Min, StdDev -from main.utils import cache_function +from main.utils import cache_function, utc_now from .models import MirrorLog, MirrorProtocol, MirrorUrl -import datetime -default_cutoff = datetime.timedelta(hours=24) +default_cutoff = timedelta(hours=24) def annotate_url(url, delays): '''Given a MirrorURL object, add a few more attributes to it regarding @@ -13,7 +14,7 @@ def annotate_url(url, delays): url.completion_pct = float(url.success_count) / url.check_count if url.id in delays: url_delays = delays[url.id] - url.delay = sum(url_delays, datetime.timedelta()) / len(url_delays) + url.delay = sum(url_delays, timedelta()) / len(url_delays) hours = url.delay.days * 24.0 + url.delay.seconds / 3600.0 if url.completion_pct > 0: @@ -28,7 +29,7 @@ def annotate_url(url, delays): @cache_function(123) def get_mirror_statuses(cutoff=default_cutoff): - cutoff_time = datetime.datetime.utcnow() - cutoff + cutoff_time = utc_now() - cutoff protocols = list(MirrorProtocol.objects.filter(is_download=True)) # I swear, this actually has decent performance... urls = MirrorUrl.objects.select_related('mirror', 'protocol').filter( @@ -82,7 +83,7 @@ def get_mirror_statuses(cutoff=default_cutoff): @cache_function(117) def get_mirror_errors(cutoff=default_cutoff): - cutoff_time = datetime.datetime.utcnow() - cutoff + cutoff_time = utc_now() - cutoff errors = MirrorLog.objects.filter( is_success=False, check_time__gte=cutoff_time, url__mirror__active=True, url__mirror__public=True).values( diff --git a/news/models.py b/news/models.py index 33d958e..95026e1 100644 --- a/news/models.py +++ b/news/models.py @@ -1,9 +1,10 @@ -from datetime import datetime - from django.db import models from django.contrib.auth.models import User from django.contrib.sites.models import Site +from main.utils import utc_now + + class News(models.Model): slug = models.SlugField(max_length=255, unique=True) author = models.ForeignKey(User, related_name='news_author', @@ -28,7 +29,7 @@ class News(models.Model): def set_news_fields(sender, **kwargs): news = kwargs['instance'] - now = datetime.utcnow() + now = utc_now() news.last_modified = now if not news.postdate: news.postdate = now diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py index 3b67f51..ddf930d 100644 --- a/packages/management/commands/signoff_report.py +++ b/packages/management/commands/signoff_report.py @@ -17,12 +17,13 @@ from django.db.models import Count from django.template import loader, Context from collections import namedtuple -from datetime import datetime, timedelta +from datetime import timedelta import logging from operator import attrgetter import sys from main.models import Repo +from main.utils import utc_now from packages.models import Signoff from packages.utils import get_signoff_groups @@ -65,7 +66,7 @@ def generate_report(email, repo_name): new_hours = 24 old_days = 14 - now = datetime.utcnow() + now = utc_now() new_cutoff = now - timedelta(hours=new_hours) old_cutoff = now - timedelta(days=old_days) diff --git a/packages/migrations/0007_auto__add_field_packagerelation_created.py b/packages/migrations/0007_auto__add_field_packagerelation_created.py index 37321fd..b030909 100644 --- a/packages/migrations/0007_auto__add_field_packagerelation_created.py +++ b/packages/migrations/0007_auto__add_field_packagerelation_created.py @@ -1,5 +1,6 @@ # encoding: utf-8 import datetime +from pytz import utc from south.db import db from south.v2 import SchemaMigration from django.db import models @@ -7,7 +8,9 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): - db.add_column('packages_packagerelation', 'created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.utcnow()), keep_default=False) + old_date = datetime.datetime(2000, 1, 1) + old_date = old_date.replace(tzinfo=utc) + db.add_column('packages_packagerelation', 'created', self.gf('django.db.models.fields.DateTimeField')(default=old_date), keep_default=False) def backwards(self, orm): db.delete_column('packages_packagerelation', 'created') diff --git a/packages/views/flag.py b/packages/views/flag.py index c0990e8..0d2f900 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -1,5 +1,3 @@ -from datetime import datetime - from django import forms from django.conf import settings from django.contrib.auth.decorators import permission_required @@ -12,6 +10,7 @@ from django.views.decorators.cache import never_cache from ..models import FlagRequest from main.models import Package +from main.utils import utc_now def flaghelp(request): @@ -61,7 +60,7 @@ def flag(request, name, repo, arch): @transaction.commit_on_success def perform_updates(): - now = datetime.utcnow() + now = utc_now() pkgs.update(flag_date=now) # store our flag request flag_request = FlagRequest(created=now, diff --git a/packages/views/signoff.py b/packages/views/signoff.py index e3daf0d..cf00b0b 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -1,4 +1,3 @@ -from datetime import datetime from operator import attrgetter from django import forms @@ -13,6 +12,7 @@ from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template from main.models import Package, Arch, Repo +from main.utils import utc_now from ..models import SignoffSpecification, Signoff from ..utils import (get_signoff_groups, approved_by_signoffs, PackageSignoffGroup) @@ -45,7 +45,7 @@ def signoff_package(request, name, repo, arch, revoke=False): package, request.user, False) except Signoff.DoesNotExist: raise Http404 - signoff.revoked = datetime.utcnow() + signoff.revoked = utc_now() signoff.save() created = False else: diff --git a/releng/management/commands/syncisos.py b/releng/management/commands/syncisos.py index 270c6c3..62f005f 100644 --- a/releng/management/commands/syncisos.py +++ b/releng/management/commands/syncisos.py @@ -1,4 +1,3 @@ -from datetime import datetime import re import urllib from HTMLParser import HTMLParser, HTMLParseError @@ -6,8 +5,10 @@ from HTMLParser import HTMLParser, HTMLParseError from django.conf import settings from django.core.management.base import BaseCommand, CommandError +from main.utils import utc_now from releng.models import Iso + class IsoListParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) @@ -53,7 +54,7 @@ class Command(BaseCommand): existing.active = True existing.removed = None existing.save() - now = datetime.utcnow() + now = utc_now() # and then mark all other names as no longer active Iso.objects.filter(active=True).exclude(name__in=active_isos).update( active=False, removed=now) diff --git a/settings.py b/settings.py index 9aba388..e1d4d4a 100644 --- a/settings.py +++ b/settings.py @@ -18,9 +18,13 @@ NOTIFICATIONS = ['arch-notifications@archlinux.org'] # Full path to the data directory DEPLOY_PATH = os.path.dirname(os.path.realpath(__file__)) -# Local time zone for this installation. All choices can be found here: -# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -TIME_ZONE = 'US/Eastern' +# If you set this to False, Django will not use timezone-aware datetimes. +USE_TZ = True + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +TIME_ZONE = 'UTC' # Language code for this installation. All choices can be found here: # http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes diff --git a/sitemaps.py b/sitemaps.py index 177555f..8e9ba36 100644 --- a/sitemaps.py +++ b/sitemaps.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from pytz import utc from django.contrib.sitemaps import Sitemap from django.core.urlresolvers import reverse @@ -62,7 +63,7 @@ class NewsSitemap(Sitemap): priority = "0.8" def __init__(self): - now = datetime.utcnow() + now = datetime.utcnow().replace(tzinfo=utc) self.one_day_ago = now - timedelta(days=1) self.one_week_ago = now - timedelta(days=7) diff --git a/templates/devel/clock.html b/templates/devel/clock.html index 72a57d0..3b00075 100644 --- a/templates/devel/clock.html +++ b/templates/devel/clock.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load tz %} {% block title %}Arch Linux - Developer World Clocks{% endblock %} @@ -10,7 +11,6 @@ depends on developers keeping the time zone information up to date, so if you see 'UTC' listed, pester them to update their settings.

- Arch Server Time: {{ now|date:"Y-m-d H:i T" }}
UTC Time: {{ utc_now|date:"Y-m-d H:i T" }}

@@ -33,7 +33,7 @@ {{ dev.userprofile.alias }} {{ dev.userprofile.location }} {{ dev.userprofile.time_zone }} - {{ dev.current_time|date:"Y-m-d H:i T" }} + {{ utc_now|timezone:dev.userprofile.time_zone|date:"Y-m-d H:i T" }} {% endfor %} -- cgit v1.2.3-24-g4f1b