1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 | import datetime
import warnings
import pytz
from django.conf import settings
from django.db import models
from django.dispatch import dispatcher
from django.db.models import signals
try:
pytz_exc = (pytz.UnknownTimeZoneError,)
except AttributeError:
# older versions of pytz
pytz_exc = (IOError, KeyError)
TIMEZONES = tuple((t, t) for t in pytz.all_timezones)
class UTCDateTimeField(models.DateTimeField):
def __init__(self, *args, **kw):
self.default_tz = kw.get('default_tz')
if 'default_tz' in kw:
del(kw['default_tz'])
super(UTCDateTimeField, self).__init__(*args, **kw)
self._name = None
def get_internal_type(self):
return 'DateTimeField'
def _model_init(self, signal, sender, instance, **kw):
setattr(instance, "_%s" % self._name, getattr(instance, self._name))
setattr(instance, "_%s_utc" % self._name, getattr(instance, "%s_utc" % self._name))
setattr(instance, "_%s_tz" % self._name, getattr(instance, "%s_tz" % self._name))
def _model_pre_save(self, signal, sender, instance, **kw):
dt = getattr(instance, self._name)
utc = getattr(instance, "%s_utc" % self._name)
tz = getattr(instance, "%s_tz" % self._name)
if not tz:
# try to get the default
if isinstance(self.default_tz, basestring):
tz = getattr(instance, self.default_tz)
if isinstance(tz, basestring):
# use it as a timezone
pass
elif callable(tz):
self.default_tz = tz
tz = None
else:
warnings.warn("UTCDateTimeField cannot use default_tz %s in model %s" % (self.default_tz, instance))
self.default_tz = lambda: getattr(settings, 'TIME_ZONE', 'UTC')
tz = None
if callable(self.default_tz):
tz = self.default_tz()
if not tz or not isinstance(tz, basestring):
warnings.warn("UTCDateTimeField cannot use default timezone %s, using Django default" % tz)
tz = getattr(settings, 'TIME_ZONE', 'UTC')
if not tz:
tz = getattr(settings, 'TIME_ZONE', 'UTC')
setattr(instance, "%s_tz" % self._name, tz)
_dt = getattr(instance, "_%s" % self._name)
_utc = getattr(instance, "_%s_utc" % self._name)
_tz = getattr(instance, "_%s_tz" % self._name)
if isinstance(tz, unicode):
tz = tz.encode('utf8')
try:
timezone = pytz.timezone(tz)
except pytz_exc, e:
warnings.warn("Error in timezone: %s" % e)
setattr(instance, "%s_tz" % self._name, 'UTC')
timezone = pytz.UTC
if not dt and not utc:
# do nothing
return
elif dt and dt != _dt:
utc = UTCDateTimeField.utc_from_localtime(dt, timezone)
setattr(instance, "%s_utc" % self._name, utc)
elif utc and (utc != _utc or tz != _tz):
dt = UTCDateTimeField.localtime_from_utc(utc, timezone)
setattr(instance, "_%s" % self._name, dt)
elif not dt:
dt = UTCDateTimeField.localtime_from_utc(utc, timezone)
setattr(instance, "_%s" % self._name, dt)
elif not utc:
utc = UTCDateTimeField.utc_from_localtime(dt, timezone)
setattr(instance, "%s_utc" % self._name, utc)
if dt.tzinfo:
dt = dt.replace(tzinfo=None)
if utc.tzinfo:
utc = utc.replace(tzinfo=None)
setattr(instance, "_%s" % self._name, dt)
setattr(instance, "_%s_utc" % self._name, utc)
setattr(instance, "_%s_tz" % self._name, tz)
setattr(instance, "%s" % self._name, dt)
setattr(instance, "%s_utc" % self._name, utc)
def _model_post_save(self, signal, sender, instance, **kw):
setattr(instance, "%s" % self._name, getattr(instance, "_%s" % self._name))
setattr(instance, "%s_utc" % self._name, getattr(instance, "_%s_utc" % self._name))
def contribute_to_class(self, cls, name):
self._name = name
super(UTCDateTimeField, self).contribute_to_class(cls, name)
self.utc_field = models.DateTimeField(blank=True)
self.creation_counter = self.utc_field.creation_counter + 1
models.DateTimeField.contribute_to_class(self.utc_field, cls, '%s_utc' % name)
self.tz_field = models.CharField(max_length=38, blank=True, choices=TIMEZONES, default=getattr(settings, 'TIME_ZONE', 'UTC'))
self.creation_counter = self.tz_field.creation_counter + 1
models.CharField.contribute_to_class(self.tz_field, cls, '%s_tz' % name)
# add the properties for offset-aware datetimes
def get_timezone(s):
tz = getattr(s, '%s_tz' % name)
if isinstance(tz, unicode):
tz = tz.encode('utf8')
return pytz.timezone(tz)
setattr(cls, "%s_timezone" % name, property(get_timezone))
def get_dt_offset_aware(s):
dt = getattr(s, "%s_utc_offset_aware" % name)
tz = getattr(s, "%s_timezone" % name)
return dt.astimezone(tz)
setattr(cls, "%s_offset_aware" % name, property(get_dt_offset_aware))
def get_utc_offset_aware(s):
return getattr(s, '%s_utc' % name).replace(tzinfo=pytz.utc)
setattr(cls, "%s_utc_offset_aware" % name, property(get_utc_offset_aware))
signals.post_init.connect(self._model_init, sender=cls)
signals.pre_save.connect(self._model_pre_save, sender=cls)
signals.post_save.connect(self._model_post_save, sender=cls)
@staticmethod
def localtime_from_utc(utc, tz):
dt = utc.replace(tzinfo=pytz.utc)
return dt.astimezone(tz)
@staticmethod
def utc_from_localtime(dt, tz):
dt = dt.replace(tzinfo=tz)
_dt = tz.normalize(dt)
if dt.tzinfo != _dt.tzinfo:
# Houston, we have a problem...
# find out which one has a dst offset
if _dt.tzinfo.dst(_dt):
_dt -= _dt.tzinfo.dst(_dt)
else:
_dt += dt.tzinfo.dst(dt)
return _dt.astimezone(pytz.utc)
|
Comments
Anyone have a solution for a UTCTimeField()? I need similar functionality but only need the time portion. I guess I could use this snippet but ignore whatever date is there.
However, it seems this snippet is broken in Changeset 8223. The dispatcher functionality has been replaced with the new django.dispatch.Signal:
http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Signalrefactoring
I'm currently struggling to fix this but I'm still a beginner when it comes to all the custom fields, dispatching and signal jazz. Any help would be appreciated.
#
Updated to work with Django 1.1+ (I use it with trunk == 1.2)
#
You mean this code can be used with Django 1.1+?
#
I don't know about others but the
staticmethodutc_from_localtimewas off for timezones like 'Europe/Paris'. So after a quick Google search I found this threadThe following code is based on the original version of the method. You can see that the returned value is the the current Paris time minus 9 minutes. Just weird, probably because the localize function has not been called. It should be a -1 hour offset, not -9 min.
#