#!/usr/bin/env python # # Copyright 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """Simple protocol message types. Includes new message and field types that are outside what is defined by the protocol buffers standard. """ __author__ = 'rafek@google.com (Rafe Kaplan)' import datetime from . import messages from . import util __all__ = [ 'DateTimeField', 'DateTimeMessage', 'VoidMessage', ] class VoidMessage(messages.Message): """Empty message.""" class DateTimeMessage(messages.Message): """Message to store/transmit a DateTime. Fields: milliseconds: Milliseconds since Jan 1st 1970 local time. time_zone_offset: Optional time zone offset, in minutes from UTC. """ milliseconds = messages.IntegerField(1, required=True) time_zone_offset = messages.IntegerField(2) class DateTimeField(messages.MessageField): """Field definition for datetime values. Stores a python datetime object as a field. If time zone information is included in the datetime object, it will be included in the encoded data when this is encoded/decoded. """ type = datetime.datetime message_type = DateTimeMessage @util.positional(3) def __init__(self, number, **kwargs): super(DateTimeField, self).__init__(self.message_type, number, **kwargs) def value_from_message(self, message): """Convert DateTimeMessage to a datetime. Args: A DateTimeMessage instance. Returns: A datetime instance. """ message = super(DateTimeField, self).value_from_message(message) if message.time_zone_offset is None: return datetime.datetime.utcfromtimestamp(message.milliseconds / 1000.0) # Need to subtract the time zone offset, because when we call # datetime.fromtimestamp, it will add the time zone offset to the # value we pass. milliseconds = (message.milliseconds - 60000 * message.time_zone_offset) timezone = util.TimeZoneOffset(message.time_zone_offset) return datetime.datetime.fromtimestamp(milliseconds / 1000.0, tz=timezone) def value_to_message(self, value): value = super(DateTimeField, self).value_to_message(value) # First, determine the delta from the epoch, so we can fill in # DateTimeMessage's milliseconds field. if value.tzinfo is None: time_zone_offset = 0 local_epoch = datetime.datetime.utcfromtimestamp(0) else: time_zone_offset = util.total_seconds(value.tzinfo.utcoffset(value)) # Determine Jan 1, 1970 local time. local_epoch = datetime.datetime.fromtimestamp(-time_zone_offset, tz=value.tzinfo) delta = value - local_epoch # Create and fill in the DateTimeMessage, including time zone if # one was specified. message = DateTimeMessage() message.milliseconds = int(util.total_seconds(delta) * 1000) if value.tzinfo is not None: utc_offset = value.tzinfo.utcoffset(value) if utc_offset is not None: message.time_zone_offset = int( util.total_seconds(value.tzinfo.utcoffset(value)) / 60) return message