• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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