1#!/usr/bin/env python 2# 3# Copyright 2010 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18"""Simple protocol message types. 19 20Includes new message and field types that are outside what is defined by the 21protocol buffers standard. 22""" 23 24__author__ = 'rafek@google.com (Rafe Kaplan)' 25 26import datetime 27 28from . import messages 29from . import util 30 31__all__ = [ 32 'DateTimeField', 33 'DateTimeMessage', 34 'VoidMessage', 35] 36 37class VoidMessage(messages.Message): 38 """Empty message.""" 39 40 41class DateTimeMessage(messages.Message): 42 """Message to store/transmit a DateTime. 43 44 Fields: 45 milliseconds: Milliseconds since Jan 1st 1970 local time. 46 time_zone_offset: Optional time zone offset, in minutes from UTC. 47 """ 48 milliseconds = messages.IntegerField(1, required=True) 49 time_zone_offset = messages.IntegerField(2) 50 51 52class DateTimeField(messages.MessageField): 53 """Field definition for datetime values. 54 55 Stores a python datetime object as a field. If time zone information is 56 included in the datetime object, it will be included in 57 the encoded data when this is encoded/decoded. 58 """ 59 60 type = datetime.datetime 61 62 message_type = DateTimeMessage 63 64 @util.positional(3) 65 def __init__(self, 66 number, 67 **kwargs): 68 super(DateTimeField, self).__init__(self.message_type, 69 number, 70 **kwargs) 71 72 def value_from_message(self, message): 73 """Convert DateTimeMessage to a datetime. 74 75 Args: 76 A DateTimeMessage instance. 77 78 Returns: 79 A datetime instance. 80 """ 81 message = super(DateTimeField, self).value_from_message(message) 82 if message.time_zone_offset is None: 83 return datetime.datetime.utcfromtimestamp(message.milliseconds / 1000.0) 84 85 # Need to subtract the time zone offset, because when we call 86 # datetime.fromtimestamp, it will add the time zone offset to the 87 # value we pass. 88 milliseconds = (message.milliseconds - 89 60000 * message.time_zone_offset) 90 91 timezone = util.TimeZoneOffset(message.time_zone_offset) 92 return datetime.datetime.fromtimestamp(milliseconds / 1000.0, 93 tz=timezone) 94 95 def value_to_message(self, value): 96 value = super(DateTimeField, self).value_to_message(value) 97 # First, determine the delta from the epoch, so we can fill in 98 # DateTimeMessage's milliseconds field. 99 if value.tzinfo is None: 100 time_zone_offset = 0 101 local_epoch = datetime.datetime.utcfromtimestamp(0) 102 else: 103 time_zone_offset = util.total_seconds(value.tzinfo.utcoffset(value)) 104 # Determine Jan 1, 1970 local time. 105 local_epoch = datetime.datetime.fromtimestamp(-time_zone_offset, 106 tz=value.tzinfo) 107 delta = value - local_epoch 108 109 # Create and fill in the DateTimeMessage, including time zone if 110 # one was specified. 111 message = DateTimeMessage() 112 message.milliseconds = int(util.total_seconds(delta) * 1000) 113 if value.tzinfo is not None: 114 utc_offset = value.tzinfo.utcoffset(value) 115 if utc_offset is not None: 116 message.time_zone_offset = int( 117 util.total_seconds(value.tzinfo.utcoffset(value)) / 60) 118 119 return message 120