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""" 23import datetime 24 25from apitools.base.protorpclite import messages 26from apitools.base.protorpclite import util 27 28__all__ = [ 29 'DateTimeField', 30 'DateTimeMessage', 31 'VoidMessage', 32] 33 34 35class VoidMessage(messages.Message): 36 """Empty message.""" 37 38 39class DateTimeMessage(messages.Message): 40 """Message to store/transmit a DateTime. 41 42 Fields: 43 milliseconds: Milliseconds since Jan 1st 1970 local time. 44 time_zone_offset: Optional time zone offset, in minutes from UTC. 45 """ 46 milliseconds = messages.IntegerField(1, required=True) 47 time_zone_offset = messages.IntegerField(2) 48 49 50class DateTimeField(messages.MessageField): 51 """Field definition for datetime values. 52 53 Stores a python datetime object as a field. If time zone information is 54 included in the datetime object, it will be included in 55 the encoded data when this is encoded/decoded. 56 """ 57 58 type = datetime.datetime 59 60 message_type = DateTimeMessage 61 62 @util.positional(3) 63 def __init__(self, 64 number, 65 **kwargs): 66 super(DateTimeField, self).__init__(self.message_type, 67 number, 68 **kwargs) 69 70 def value_from_message(self, message): 71 """Convert DateTimeMessage to a datetime. 72 73 Args: 74 A DateTimeMessage instance. 75 76 Returns: 77 A datetime instance. 78 """ 79 message = super(DateTimeField, self).value_from_message(message) 80 if message.time_zone_offset is None: 81 return datetime.datetime.utcfromtimestamp( 82 message.milliseconds / 1000.0) 83 84 # Need to subtract the time zone offset, because when we call 85 # datetime.fromtimestamp, it will add the time zone offset to the 86 # value we pass. 87 milliseconds = (message.milliseconds - 88 60000 * message.time_zone_offset) 89 90 timezone = util.TimeZoneOffset(message.time_zone_offset) 91 return datetime.datetime.fromtimestamp(milliseconds / 1000.0, 92 tz=timezone) 93 94 def value_to_message(self, value): 95 value = super(DateTimeField, self).value_to_message(value) 96 # First, determine the delta from the epoch, so we can fill in 97 # DateTimeMessage's milliseconds field. 98 if value.tzinfo is None: 99 time_zone_offset = 0 100 local_epoch = datetime.datetime.utcfromtimestamp(0) 101 else: 102 time_zone_offset = util.total_seconds( 103 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