1 /* This code was originally copied from Pendulum
2 (https://github.com/sdispater/pendulum/blob/13ff4a0250177f77e4ff2e7bd1f442d954e66b22/pendulum/parsing/_iso8601.c#L176)
3 Pendulum (like ciso8601) is MIT licensed, so we have included a copy of its
4 license here.
5 */
6 
7 /*
8 Copyright (c) 2015 Sébastien Eustace
9 
10 Permission is hereby granted, free of charge, to any person obtaining
11 a copy of this software and associated documentation files (the
12 "Software"), to deal in the Software without restriction, including
13 without limitation the rights to use, copy, modify, merge, publish,
14 distribute, sublicense, and/or sell copies of the Software, and to
15 permit persons to whom the Software is furnished to do so, subject to
16 the following conditions:
17 
18 The above copyright notice and this permission notice shall be
19 included in all copies or substantial portions of the Software.
20 
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 */
29 
30 #include "timezone.h"
31 
32 #include <Python.h>
33 #include <datetime.h>
34 #include <structmember.h>
35 
36 #define SECS_PER_MIN 60
37 #define SECS_PER_HOUR (60 * SECS_PER_MIN)
38 #define TWENTY_FOUR_HOURS_IN_SECONDS 86400
39 
40 #define PY_VERSION_AT_LEAST_36 \
41     ((PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 6) || PY_MAJOR_VERSION > 3)
42 
43 /*
44  * class FixedOffset(tzinfo):
45  */
46 typedef struct {
47     // Seconds offset from UTC.
48     // Must be in range (-86400, 86400) seconds exclusive.
49     // ie. (-1440, 1440) minutes exclusive.
50     PyObject_HEAD int offset;
51 } FixedOffset;
52 
53 /*
54  * def __init__(self, offset):
55  *     self.offset = offset
56  */
57 static int
FixedOffset_init(FixedOffset * self,PyObject * args,PyObject * kwargs)58 FixedOffset_init(FixedOffset *self, PyObject *args, PyObject *kwargs)
59 {
60     int offset;
61     if (!PyArg_ParseTuple(args, "i", &offset))
62         return -1;
63 
64     if (abs(offset) >= TWENTY_FOUR_HOURS_IN_SECONDS) {
65         PyErr_Format(PyExc_ValueError,
66                      "offset must be an integer in the range (-86400, 86400), "
67                      "exclusive");
68         return -1;
69     }
70 
71     self->offset = offset;
72     return 0;
73 }
74 
75 /*
76  * def utcoffset(self, dt):
77  *     return timedelta(seconds=self.offset * 60)
78  */
79 static PyObject *
FixedOffset_utcoffset(FixedOffset * self,PyObject * args)80 FixedOffset_utcoffset(FixedOffset *self, PyObject *args)
81 {
82     return PyDelta_FromDSU(0, self->offset, 0);
83 }
84 
85 /*
86  * def dst(self, dt):
87  *     return timedelta(seconds=self.offset * 60)
88  */
89 static PyObject *
FixedOffset_dst(FixedOffset * self,PyObject * args)90 FixedOffset_dst(FixedOffset *self, PyObject *args)
91 {
92     Py_RETURN_NONE;
93 }
94 
95 /*
96  * def tzname(self, dt):
97  *     sign = '+'
98  *     if self.offset < 0:
99  *         sign = '-'
100  *     return "%s%d:%d" % (sign, self.offset / 60, self.offset % 60)
101  */
102 static PyObject *
FixedOffset_tzname(FixedOffset * self,PyObject * args)103 FixedOffset_tzname(FixedOffset *self, PyObject *args)
104 {
105 
106     int offset = self->offset;
107 
108     if (offset == 0){
109 #if PY_VERSION_AT_LEAST_36
110         return PyUnicode_FromString("UTC");
111 #else
112         return PyUnicode_FromString("UTC+00:00");
113 #endif
114     } else {
115         char result_tzname[10] = {0};
116         char sign = '+';
117 
118         if (offset < 0) {
119             sign = '-';
120             offset *= -1;
121         }
122         snprintf(result_tzname, 10, "UTC%c%02u:%02u", sign,
123              (offset / SECS_PER_HOUR) & 31,
124              offset / SECS_PER_MIN % SECS_PER_MIN);
125         return PyUnicode_FromString(result_tzname);
126     }
127 }
128 
129 /*
130  * def __repr__(self):
131  *     return self.tzname()
132  */
133 static PyObject *
FixedOffset_repr(FixedOffset * self)134 FixedOffset_repr(FixedOffset *self)
135 {
136     return FixedOffset_tzname(self, NULL);
137 }
138 
139 /*
140  * def __getinitargs__(self):
141  *     return (self.offset,)
142  */
143 static PyObject *
FixedOffset_getinitargs(FixedOffset * self)144 FixedOffset_getinitargs(FixedOffset *self)
145 {
146     PyObject *args = PyTuple_Pack(1, PyLong_FromLong(self->offset));
147     return args;
148 }
149 
150 /*
151  * Class member / class attributes
152  */
153 static PyMemberDef FixedOffset_members[] = {
154     {"offset", T_INT, offsetof(FixedOffset, offset), 0, "UTC offset"}, {NULL}};
155 
156 /*
157  * Class methods
158  */
159 static PyMethodDef FixedOffset_methods[] = {
160     {"utcoffset", (PyCFunction)FixedOffset_utcoffset, METH_VARARGS, ""},
161     {"dst", (PyCFunction)FixedOffset_dst, METH_VARARGS, ""},
162     {"tzname", (PyCFunction)FixedOffset_tzname, METH_VARARGS, ""},
163     {"__getinitargs__", (PyCFunction)FixedOffset_getinitargs, METH_VARARGS,
164      ""},
165     {NULL}};
166 
167 static PyTypeObject FixedOffset_type = {
168     PyVarObject_HEAD_INIT(NULL, 0) "ciso8601.FixedOffset", /* tp_name */
169     sizeof(FixedOffset),                                   /* tp_basicsize */
170     0,                                                     /* tp_itemsize */
171     0,                                                     /* tp_dealloc */
172     0,                                                     /* tp_print */
173     0,                                                     /* tp_getattr */
174     0,                                                     /* tp_setattr */
175     0,                                                     /* tp_as_async */
176     (reprfunc)FixedOffset_repr,                            /* tp_repr */
177     0,                                                     /* tp_as_number */
178     0,                                                     /* tp_as_sequence */
179     0,                                                     /* tp_as_mapping */
180     0,                                                     /* tp_hash  */
181     0,                                                     /* tp_call */
182     (reprfunc)FixedOffset_repr,                            /* tp_str */
183     0,                                                     /* tp_getattro */
184     0,                                                     /* tp_setattro */
185     0,                                                     /* tp_as_buffer */
186     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,              /* tp_flags */
187     "TZInfo with fixed offset",                            /* tp_doc */
188 };
189 
190 /*
191  * Instantiate new FixedOffset_type object
192  * Skip overhead of calling PyObject_New and PyObject_Init.
193  * Directly allocate object.
194  * Note that this also doesn't do any validation of the offset parameter.
195  * Callers must ensure that offset is within \
196  * the range (-86400, 86400), exclusive.
197  */
198 PyObject *
new_fixed_offset_ex(int offset,PyTypeObject * type)199 new_fixed_offset_ex(int offset, PyTypeObject *type)
200 {
201     FixedOffset *self = (FixedOffset *)(type->tp_alloc(type, 0));
202 
203     if (self != NULL)
204         self->offset = offset;
205 
206     return (PyObject *)self;
207 }
208 
209 PyObject *
new_fixed_offset(int offset)210 new_fixed_offset(int offset)
211 {
212     return new_fixed_offset_ex(offset, &FixedOffset_type);
213 }
214 
215 /* ------------------------------------------------------------- */
216 
217 int
initialize_timezone_code(PyObject * module)218 initialize_timezone_code(PyObject *module)
219 {
220     PyDateTime_IMPORT;
221     FixedOffset_type.tp_new = PyType_GenericNew;
222     FixedOffset_type.tp_base = PyDateTimeAPI->TZInfoType;
223     FixedOffset_type.tp_methods = FixedOffset_methods;
224     FixedOffset_type.tp_members = FixedOffset_members;
225     FixedOffset_type.tp_init = (initproc)FixedOffset_init;
226 
227     if (PyType_Ready(&FixedOffset_type) < 0)
228         return -1;
229 
230     Py_INCREF(&FixedOffset_type);
231     if (PyModule_AddObject(module, "FixedOffset",
232                            (PyObject *)&FixedOffset_type) < 0) {
233         Py_DECREF(module);
234         Py_DECREF(&FixedOffset_type);
235         return -1;
236     }
237 
238     return 0;
239 }
240