diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2e8b58e7..26fc743b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,7 @@ jobs: strategy: matrix: python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy3] + fail-fast: false steps: - uses: actions/checkout@v2 @@ -68,6 +69,7 @@ jobs: strategy: matrix: python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy3] + fail-fast: false steps: - uses: actions/checkout@v2 @@ -112,6 +114,7 @@ jobs: strategy: matrix: python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + fail-fast: false steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index bb25f8b3..30b9fddd 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ setup.py # editor .vscode + +# dev + +.venv diff --git a/docs/docs/duration.md b/docs/docs/duration.md index 0801d9ea..83403e75 100644 --- a/docs/docs/duration.md +++ b/docs/docs/duration.md @@ -175,3 +175,22 @@ It also has a handy `in_words()` method, which determines the duration represent >>> it.in_words(locale='de') '168 Wochen 1 Tag 2 Stunden 1 Minute 24 Sekunden' ``` + +Finally, it has an +[ISO-8601-compliant](https://en.wikipedia.org/wiki/ISO_8601#Durations) `isformat()` +method for cross-platform representation. + +```python + +>>> import pendulum + +>>> dur = pendulum.duration(years=1, months=3, days=6, seconds=3) + +>>> iso = dur.isoformat() + +>>> print(iso) +'P1Y3M6DT0H0M3S' + +>>> pendulum.parse(iso) == dur +True +``` diff --git a/pendulum/__init__.py b/pendulum/__init__.py index bb1e0ca7..b19f87d5 100644 --- a/pendulum/__init__.py +++ b/pendulum/__init__.py @@ -251,7 +251,10 @@ def yesterday(tz="local"): # type: (Union[str, _Timezone]) -> DateTime def from_format( - string, fmt, tz=UTC, locale=None, # noqa + string, + fmt, + tz=UTC, + locale=None, # noqa ): # type: (str, str, Union[str, _Timezone], Optional[str]) -> DateTime """ Creates a DateTime instance from a specific format. diff --git a/pendulum/duration.py b/pendulum/duration.py index 45df13da..9b352bd3 100644 --- a/pendulum/duration.py +++ b/pendulum/duration.py @@ -414,6 +414,29 @@ def __divmod__(self, other): return NotImplemented + def isoformat(self) -> str: + """Represent this duration as a ISO-8601-compliant string.""" + periods = [ + ("Y", self.years), + ("M", self.months), + ("D", self.remaining_days), + ] + period = "P" + for sym, val in periods: + period += "{val}{sym}".format(val=val, sym=sym) + times = [ + ("H", self.hours), + ("M", self.minutes), + ("S", self.remaining_seconds), + ] + time = "T" + for sym, val in times: + time += "{val}{sym}".format(val=val, sym=sym) + if self.microseconds: + time = time[:-1] + time += ".{ms:06}S".format(ms=self.microseconds) + return period + time + Duration.min = Duration(days=-999999999) Duration.max = Duration( diff --git a/tests/datetime/test_from_format.py b/tests/datetime/test_from_format.py index 0949332c..398b68da 100644 --- a/tests/datetime/test_from_format.py +++ b/tests/datetime/test_from_format.py @@ -39,7 +39,8 @@ def test_from_format_with_timezone(): def test_from_format_with_square_bracket_in_timezone(): with pytest.raises(ValueError, match="^String does not match format"): pendulum.from_format( - "1975-05-21 22:32:11 Eu[rope/London", "YYYY-MM-DD HH:mm:ss z", + "1975-05-21 22:32:11 Eu[rope/London", + "YYYY-MM-DD HH:mm:ss z", ) diff --git a/tests/duration/test_isoformat.py b/tests/duration/test_isoformat.py new file mode 100644 index 00000000..2d280d15 --- /dev/null +++ b/tests/duration/test_isoformat.py @@ -0,0 +1,32 @@ +import pytest + +from pendulum import Duration +from pendulum import parse + + +@pytest.mark.parametrize( + "dur, expected_iso", + [ + ( + Duration( + years=1, + months=3, + days=6, + minutes=50, + seconds=3, + milliseconds=10, + microseconds=10, + ), + "P1Y3M6DT0H50M3.010010S", + ), + (Duration(days=4, hours=12, minutes=30, seconds=5), "P0Y0M4DT12H30M5S"), + (Duration(days=4, hours=12, minutes=30, seconds=5), "P0Y0M4DT12H30M5S"), + (Duration(microseconds=10), "P0Y0M0DT0H0M0.000010S"), + (Duration(milliseconds=1), "P0Y0M0DT0H0M0.001000S"), + (Duration(minutes=1), "P0Y0M0DT0H1M0S"), + ], +) +def test_isoformat(dur, expected_iso): + fmt = dur.isoformat() + assert fmt == expected_iso + assert parse(fmt) == dur