Skip to content

Commit

Permalink
Fix the parsing of the ISO 8601 Z UTC designator (#448)
Browse files Browse the repository at this point in the history
* Fix parsing UTC designator

* Test the pure Python version first
  • Loading branch information
sdispater authored Mar 6, 2020
1 parent 205a86a commit 8cec473
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 18 deletions.
23 changes: 12 additions & 11 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ jobs:
run: |
source $HOME/.poetry/env
poetry install
- name: Test Pure Python
run: |
source $HOME/.poetry/env
PENDULUM_EXTENSIONS=0 poetry run pytest -q tests
- name: Test
run: |
source $HOME/.poetry/env
poetry run pytest -q tests
poetry install
- name: Test Pure Python
run: |
source $HOME/.poetry/env
PENDULUM_EXTENSIONS=0 poetry run pytest -q tests
MacOS:
needs: Linting
runs-on: macos-latest
Expand Down Expand Up @@ -89,14 +90,14 @@ jobs:
run: |
source $HOME/.poetry/env
poetry install
- name: Test
run: |
source $HOME/.poetry/env
poetry run pytest -q tests
- name: Test Pure Python
run: |
source $HOME/.poetry/env
PENDULUM_EXTENSIONS=0 poetry run pytest -q tests
- name: Test
run: |
source $HOME/.poetry/env
poetry run pytest -q tests
Windows:
needs: Linting
runs-on: windows-latest
Expand Down Expand Up @@ -130,12 +131,12 @@ jobs:
run: |
$env:Path += ";$env:Userprofile\.poetry\bin"
poetry install
- name: Test
- name: Test Pure Python
run: |
$env:Path += ";$env:Userprofile\.poetry\bin"
$env:PENDULUM_EXTENSIONS = "0"
poetry run pytest -q tests
- name: Test Pure Python
- name: Test
run: |
$env:Path += ";$env:Userprofile\.poetry\bin"
$env:PENDULUM_EXTENSIONS = "0"
poetry run pytest -q tests
2 changes: 2 additions & 0 deletions pendulum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ def _safe_timezone(obj):
# pytz
if hasattr(obj, "localize"):
obj = obj.zone
elif obj.tzname(None) == "UTC":
return UTC
else:
offset = obj.utcoffset(None)

Expand Down
25 changes: 20 additions & 5 deletions pendulum/parsing/_iso8601.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ int is_long_year(int year) {
typedef struct {
PyObject_HEAD
int offset;
char *tzname;
} FixedOffset;

/*
Expand All @@ -186,10 +187,16 @@ typedef struct {
*/
static int FixedOffset_init(FixedOffset *self, PyObject *args, PyObject *kwargs) {
int offset;
if (!PyArg_ParseTuple(args, "i", &offset))
char *tzname = NULL;

static char *kwlist[] = {"offset", "tzname", NULL};

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|s", kwlist, &offset, &tzname))
return -1;

self->offset = offset;
self->tzname = tzname;

return 0;
}

Expand Down Expand Up @@ -217,6 +224,10 @@ static PyObject *FixedOffset_dst(FixedOffset *self, PyObject *args) {
* return "%s%d:%d" % (sign, self.offset / 60, self.offset % 60)
*/
static PyObject *FixedOffset_tzname(FixedOffset *self, PyObject *args) {
if (self->tzname != NULL) {
return PyUnicode_FromString(self->tzname);
}

char tzname_[7] = {0};
char sign = '+';
int offset = self->offset;
Expand Down Expand Up @@ -292,16 +303,17 @@ static PyTypeObject FixedOffset_type = {
* Skip overhead of calling PyObject_New and PyObject_Init.
* Directly allocate object.
*/
static PyObject *new_fixed_offset_ex(int offset, PyTypeObject *type) {
static PyObject *new_fixed_offset_ex(int offset, char *name, PyTypeObject *type) {
FixedOffset *self = (FixedOffset *) (type->tp_alloc(type, 0));

if (self != NULL)
self->offset = offset;
self->tzname = name;

return (PyObject *) self;
}

#define new_fixed_offset(offset) new_fixed_offset_ex(offset, &FixedOffset_type)
#define new_fixed_offset(offset, name) new_fixed_offset_ex(offset, name, &FixedOffset_type)


/*
Expand Down Expand Up @@ -455,6 +467,7 @@ typedef struct {
int microsecond;
int offset;
int has_offset;
char *tzname;
int years;
int months;
int weeks;
Expand Down Expand Up @@ -487,6 +500,7 @@ Parsed* new_parsed() {
parsed->microsecond = 0;
parsed->offset = 0;
parsed->has_offset = 0;
parsed->tzname = NULL;

parsed->years = 0;
parsed->months = 0;
Expand Down Expand Up @@ -585,7 +599,7 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) {
}

// Checks
if (week > 53 || week > 52 && !is_long_year(parsed->year)) {
if (week > 53 || (week > 52 && !is_long_year(parsed->year))) {
parsed->error = PARSER_INVALID_WEEK_NUMBER;

return NULL;
Expand Down Expand Up @@ -850,6 +864,7 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) {
// Timezone
if (*c == 'Z') {
parsed->has_offset = 1;
parsed->tzname = "UTC";
c++;
} else if (*c == '+' || *c == '-') {
tz_sign = 1;
Expand Down Expand Up @@ -1258,7 +1273,7 @@ PyObject* parse_iso8601(PyObject *self, PyObject *args) {
if (!parsed->has_offset) {
tzinfo = Py_BuildValue("");
} else {
tzinfo = new_fixed_offset(parsed->offset);
tzinfo = new_fixed_offset(parsed->offset, parsed->tzname);
}

obj = PyDateTimeAPI->DateTime_FromDateAndTime(
Expand Down
5 changes: 3 additions & 2 deletions pendulum/parsing/iso8601.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..helpers import is_leap
from ..helpers import is_long_year
from ..helpers import week_day
from ..tz.timezone import UTC
from ..tz.timezone import FixedTimezone
from .exceptions import ParserError

Expand Down Expand Up @@ -230,7 +231,7 @@ def parse_iso8601(text):
tz = m.group("tz")
if tz:
if tz == "Z":
offset = 0
tzinfo = UTC
else:
negative = True if tz.startswith("-") else False
tz = tz[1:]
Expand All @@ -248,7 +249,7 @@ def parse_iso8601(text):
if negative:
offset = -1 * offset

tzinfo = FixedTimezone(offset)
tzinfo = FixedTimezone(offset)

if is_time:
return datetime.time(hour, minute, second, microsecond)
Expand Down
6 changes: 6 additions & 0 deletions tests/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,9 @@ def test_parse_now():

with pendulum.test(mock_now):
assert pendulum.parse("now") == mock_now


def test_parse_with_utc_timezone():
dt = pendulum.parse("2020-02-05T20:05:37.364951Z")

assert "2020-02-05T20:05:37.364951Z" == dt.to_iso8601_string()

0 comments on commit 8cec473

Please sign in to comment.