diff --git a/docs/formatting.md b/docs/formatting.md index b9c4355e..f7212079 100644 --- a/docs/formatting.md +++ b/docs/formatting.md @@ -173,10 +173,16 @@ Format specifiers can be used for advanced formatting by using the options provi {updated:D%b %d %Y %I:%M %p/} 2010-01-01 00:00:00 + + O<offset>/ + Apply <offset> to a datetime object, either as ±HH:MM or local for local UTC offset + {date:O-06:30/} + 2009-12-31 17:30:00 + -All special format specifiers (`?`, `L`, `J`, `R`, `D`) can be chained and combined with one another, but must always come before any standard format specifiers: +All special format specifiers (`?`, `L`, `J`, `R`, `D`, `O`) can be chained and combined with one another, but must always come before any standard format specifiers: For example `{foo:?//RF/B/Ro/e/> 10}` -> `   Bee Bar` - `?//` - Tests if `foo` has a value diff --git a/gallery_dl/formatter.py b/gallery_dl/formatter.py index d9c307e5..04ec1c4a 100644 --- a/gallery_dl/formatter.py +++ b/gallery_dl/formatter.py @@ -10,6 +10,7 @@ import os import json +import time import string import _string import datetime @@ -323,6 +324,27 @@ def _parse_datetime(format_spec, default): return dt +def _parse_offset(format_spec, default): + offset, _, format_spec = format_spec.partition("/") + offset = offset[1:] + fmt = _build_format_func(format_spec, default) + + if not offset or offset == "local": + is_dst = time.daylight and time.localtime().tm_isdst > 0 + offset = -(time.altzone if is_dst else time.timezone) + else: + hours, _, minutes = offset.partition(":") + offset = 3600 * int(hours) + if minutes: + offset += 60 * (int(minutes) if offset > 0 else -int(minutes)) + + offset = datetime.timedelta(seconds=offset) + + def off(obj): + return fmt(obj + offset) + return off + + def _default_format(format_spec, default): def wrap(obj): return format(obj, format_spec) @@ -367,5 +389,6 @@ _FORMAT_SPECIFIERS = { "D": _parse_datetime, "L": _parse_maxlen, "J": _parse_join, + "O": _parse_offset, "R": _parse_replace, } diff --git a/test/test_formatter.py b/test/test_formatter.py index f2ce310c..b86f0bdb 100644 --- a/test/test_formatter.py +++ b/test/test_formatter.py @@ -9,6 +9,7 @@ import os import sys +import time import unittest import datetime import tempfile @@ -192,6 +193,31 @@ class TestFormatter(unittest.TestCase): self._run_test("{ds:D%Y}", "2010-01-01T01:00:00+0100") self._run_test("{l:D%Y}", "None") + def test_offset(self): + self._run_test("{dt:O 01:00}", "2010-01-01 01:00:00") + self._run_test("{dt:O+02:00}", "2010-01-01 02:00:00") + self._run_test("{dt:O-03:45}", "2009-12-31 20:15:00") + + self._run_test("{dt:O12}", "2010-01-01 12:00:00") + self._run_test("{dt:O-24}", "2009-12-31 00:00:00") + + self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z/O1}", "2010-01-01 01:00:00") + self._run_test("{t!d:O2}", "2010-01-01 02:00:00") + + orig_daylight = time.daylight + orig_timezone = time.timezone + orig_altzone = time.altzone + try: + time.daylight = False + time.timezone = -3600 + self._run_test("{dt:O}", "2010-01-01 01:00:00") + time.timezone = 7200 + self._run_test("{dt:Olocal}", "2009-12-31 22:00:00") + finally: + time.daylight = orig_daylight + time.timezone = orig_timezone + time.altzone = orig_altzone + def test_chain_special(self): # multiple replacements self._run_test("{a:Rh/C/RE/e/RL/l/}", "Cello wOrld")