diff --git a/docs/formatting.md b/docs/formatting.md
index 9b8372d7..00fc9dfb 100644
--- a/docs/formatting.md
+++ b/docs/formatting.md
@@ -160,10 +160,16 @@ Format specifiers can be used for advanced formatting by using the options provi
{foo:Ro/()/} |
F()() Bar |
+
+ D<format>/ |
+ Parse a string value to a datetime object according to <format> |
+ {updated:D%b %d %Y %I:%M %p/} |
+ 2010-01-01 00:00:00 |
+
-All special format specifiers (`?`, `L`, `J`, `R`) 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`) 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 f5d961ac..c2b4d99e 100644
--- a/gallery_dl/formatter.py
+++ b/gallery_dl/formatter.py
@@ -274,6 +274,8 @@ def build_format_func(format_spec):
return _parse_join(format_spec)
if fmt == "R":
return _parse_replace(format_spec)
+ if fmt == "D":
+ return _parse_datetime(format_spec)
return _default_format(format_spec)
return format
@@ -319,6 +321,16 @@ def _parse_replace(format_spec):
return replace
+def _parse_datetime(format_spec):
+ dt_format, _, format_spec = format_spec.partition("/")
+ dt_format = dt_format[1:]
+ fmt = build_format_func(format_spec)
+
+ def dt(obj):
+ return fmt(text.parse_datetime(obj, dt_format))
+ return dt
+
+
def _default_format(format_spec):
def wrap(obj):
return format(obj, format_spec)
diff --git a/test/test_formatter.py b/test/test_formatter.py
index 088b45ba..8464b1b6 100644
--- a/test/test_formatter.py
+++ b/test/test_formatter.py
@@ -29,6 +29,7 @@ class TestFormatter(unittest.TestCase):
"u": "'< / >'",
"t": 1262304000,
"dt": datetime.datetime(2010, 1, 1),
+ "ds": "2010-01-01T01:00:00+0100",
"name": "Name",
"title1": "Title",
"title2": "",
@@ -162,6 +163,11 @@ class TestFormatter(unittest.TestCase):
self._run_test("{a!l:Rl//}" , "heo word")
self._run_test("{name:Rame/othing/}", "Nothing")
+ def test_datetime(self):
+ self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z}", "2010-01-01 00:00:00")
+ self._run_test("{ds:D%Y}", "2010-01-01T01:00:00+0100")
+ self._run_test("{l:D%Y}", "None")
+
def test_chain_special(self):
# multiple replacements
self._run_test("{a:Rh/C/RE/e/RL/l/}", "Cello wOrld")
@@ -174,6 +180,9 @@ class TestFormatter(unittest.TestCase):
self._run_test("{d[a]:?>/L1/too long/}", "")
self._run_test("{d[c]:?>/L5/too long/}", "")
+ # parse and format datetime
+ self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z/%Y%m%d}", "20100101")
+
def test_globals_env(self):
os.environ["FORMATTER_TEST"] = value = self.kwdict["a"]
@@ -259,7 +268,7 @@ def noarg():
sys.path.pop(0)
self.assertEqual(fmt1.format_map(self.kwdict), "'Title' by Name")
- self.assertEqual(fmt2.format_map(self.kwdict), "65")
+ self.assertEqual(fmt2.format_map(self.kwdict), "89")
with self.assertRaises(TypeError):
self.assertEqual(fmt3.format_map(self.kwdict), "")