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")