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