Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the parsing of the ISO 8601 Z UTC designator #448

Merged
merged 2 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()