Index: /trunk/src/org/openstreetmap/josm/io/OverpassDownloadReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/OverpassDownloadReader.java	(revision 12713)
+++ /trunk/src/org/openstreetmap/josm/io/OverpassDownloadReader.java	(revision 12714)
@@ -7,5 +7,10 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.Period;
+import java.time.ZoneOffset;
 import java.util.EnumMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -21,8 +26,11 @@
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.DataSource;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.BBox;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.NameFinder.SearchResult;
 import org.openstreetmap.josm.tools.HttpClient;
 import org.openstreetmap.josm.tools.Logging;
@@ -146,5 +154,7 @@
             return super.getRequestForBbox(lon1, lat1, lon2, lat2);
         else {
-            final String query = this.overpassQuery.replace("{{bbox}}", lat1 + "," + lon1 + "," + lat2 + "," + lon2);
+            final String query = this.overpassQuery
+                    .replace("{{bbox}}", bbox(lon1, lat1, lon2, lat2))
+                    .replace("{{center}}", center(lon1, lat1, lon2, lat2));
             final String expandedOverpassQuery = expandExtendedQueries(query);
             return "interpreter" + DATA_PREFIX + Utils.encodeUrl(expandedOverpassQuery);
@@ -160,15 +170,27 @@
     static String expandExtendedQueries(String query) {
         final StringBuffer sb = new StringBuffer();
-        final Matcher matcher = Pattern.compile("\\{\\{(geocodeArea):([^}]+)\\}\\}").matcher(query);
+        final Matcher matcher = Pattern.compile("\\{\\{(date|geocodeArea|geocodeBbox|geocodeCoords|geocodeId):([^}]+)\\}\\}").matcher(query);
         while (matcher.find()) {
             try {
                 switch (matcher.group(1)) {
+                    case "date":
+                        matcher.appendReplacement(sb, date(matcher.group(2), LocalDateTime.now()));
+                        break;
                     case "geocodeArea":
                         matcher.appendReplacement(sb, geocodeArea(matcher.group(2)));
                         break;
+                    case "geocodeBbox":
+                        matcher.appendReplacement(sb, geocodeBbox(matcher.group(2)));
+                        break;
+                    case "geocodeCoords":
+                        matcher.appendReplacement(sb, geocodeCoords(matcher.group(2)));
+                        break;
+                    case "geocodeId":
+                        matcher.appendReplacement(sb, geocodeId(matcher.group(2)));
+                        break;
                     default:
                         Logging.warn("Unsupported syntax: " + matcher.group(1));
                 }
-            } catch (UncheckedParseException ex) {
+            } catch (UncheckedParseException | IOException | NoSuchElementException | IndexOutOfBoundsException ex) {
                 final String msg = tr("Failed to evaluate {0}", matcher.group());
                 Logging.log(Logging.LEVEL_WARN, msg, ex);
@@ -180,5 +202,66 @@
     }
 
-    private static String geocodeArea(String area) {
+    static String bbox(double lon1, double lat1, double lon2, double lat2) {
+        return lat1 + "," + lon1 + "," + lat2 + "," + lon2;
+    }
+
+    static String center(double lon1, double lat1, double lon2, double lat2) {
+        LatLon c = new BBox(lon1, lat1, lon2, lat2).getCenter();
+        return c.lat()+ "," + c.lon();
+    }
+
+    static String date(String humanDuration, LocalDateTime from) {
+        // Convert to ISO 8601. Replace months by X temporarily to avoid conflict with minutes
+        String duration = humanDuration.toLowerCase(Locale.ENGLISH).replace(" ", "")
+                .replaceAll("years?", "Y").replaceAll("months?", "X").replaceAll("weeks?", "W")
+                .replaceAll("days?", "D").replaceAll("hours?", "H").replaceAll("minutes?", "M").replaceAll("seconds?", "S");
+        Matcher matcher = Pattern.compile(
+                "((?:[0-9]+Y)?(?:[0-9]+X)?(?:[0-9]+W)?)"+
+                "((?:[0-9]+D)?)" +
+                "((?:[0-9]+H)?(?:[0-9]+M)?(?:[0-9]+(?:[.,][0-9]{0,9})?S)?)?").matcher(duration);
+        boolean javaPer = false;
+        boolean javaDur = false;
+        if (matcher.matches()) {
+            javaPer = matcher.group(1) != null && !matcher.group(1).isEmpty();
+            javaDur = matcher.group(3) != null && !matcher.group(3).isEmpty();
+            duration = 'P' + matcher.group(1).replace('X', 'M') + matcher.group(2);
+            if (javaDur) {
+                duration += 'T' + matcher.group(3);
+            }
+        }
+
+        // Duration is now a full ISO 8601 duration string. Unfortunately Java does not allow to parse it entirely.
+        // We must split the "period" (years, months, weeks, days) from the "duration" (days, hours, minutes, seconds).
+        Period p = null;
+        Duration d = null;
+        int idx = duration.indexOf('T');
+        if (javaPer) {
+            p = Period.parse(javaDur ? duration.substring(0, idx) : duration);
+        }
+        if (javaDur) {
+            d = Duration.parse(javaPer ? 'P' + duration.substring(idx, duration.length()) : duration);
+        } else if (!javaPer) {
+            d = Duration.parse(duration);
+        }
+
+        // Now that period and duration are known, compute the correct date/time
+        LocalDateTime dt = from;
+        if (p != null) {
+            dt = dt.minus(p);
+        }
+        if (d != null) {
+            dt = dt.minus(d);
+        }
+
+        // Returns the date/time formatted in ISO 8601
+        return dt.toInstant(ZoneOffset.UTC).toString();
+    }
+
+    private static SearchResult searchName(String area) throws IOException {
+        return NameFinder.queryNominatim(area).stream().filter(
+                x -> !OsmPrimitiveType.NODE.equals(x.getOsmId().getType())).iterator().next();
+    }
+
+    static String geocodeArea(String area) throws IOException {
         // Offsets defined in https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#By_element_id
         final EnumMap<OsmPrimitiveType, Long> idOffset = new EnumMap<>(OsmPrimitiveType.class);
@@ -186,11 +269,21 @@
         idOffset.put(OsmPrimitiveType.WAY, 2_400_000_000L);
         idOffset.put(OsmPrimitiveType.RELATION, 3_600_000_000L);
-        try {
-            final PrimitiveId osmId = NameFinder.queryNominatim(area).stream().filter(
-                    x -> !OsmPrimitiveType.NODE.equals(x.getOsmId().getType())).iterator().next().getOsmId();
-            return String.format("area(%d)", osmId.getUniqueId() + idOffset.get(osmId.getType()));
-        } catch (IOException | NoSuchElementException | IndexOutOfBoundsException ex) {
-            throw new UncheckedParseException(ex);
-        }
+        final PrimitiveId osmId = searchName(area).getOsmId();
+        return String.format("area(%d)", osmId.getUniqueId() + idOffset.get(osmId.getType()));
+    }
+
+    static String geocodeBbox(String area) throws IOException {
+        Bounds bounds = searchName(area).getBounds();
+        return bounds.getMinLat() + "," + bounds.getMinLon() + "," + bounds.getMaxLat() + "," + bounds.getMaxLon();
+    }
+
+    static String geocodeCoords(String area) throws IOException {
+        SearchResult result = searchName(area);
+        return result.getLat() + "," + result.getLon();
+    }
+
+    static String geocodeId(String area) throws IOException {
+        PrimitiveId osmId = searchName(area).getOsmId();
+        return String.format("%s(%d)", osmId.getType().getAPIName(), osmId.getUniqueId());
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java	(revision 12713)
+++ /trunk/test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java	(revision 12714)
@@ -7,6 +7,8 @@
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.time.LocalDateTime;
 import java.util.regex.Matcher;
 
@@ -20,4 +22,5 @@
 import org.openstreetmap.josm.tools.OverpassTurboQueryWizard;
 import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.tools.date.DateUtils;
 
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
@@ -87,4 +90,40 @@
 
     /**
+     * Tests evaluating the extended query feature {@code date}.
+     */
+    @Test
+    public void testDate() {
+        LocalDateTime from = LocalDateTime.of(2017, 7, 14, 2, 40);
+        assertEquals("2016-07-14T02:40:00Z", OverpassDownloadReader.date("1 year", from));
+        assertEquals("2007-07-14T02:40:00Z", OverpassDownloadReader.date("10years", from));
+        assertEquals("2017-06-14T02:40:00Z", OverpassDownloadReader.date("1 month", from));
+        assertEquals("2016-09-14T02:40:00Z", OverpassDownloadReader.date("10months", from));
+        assertEquals("2017-07-07T02:40:00Z", OverpassDownloadReader.date("1 week", from));
+        assertEquals("2017-05-05T02:40:00Z", OverpassDownloadReader.date("10weeks", from));
+        assertEquals("2017-07-13T02:40:00Z", OverpassDownloadReader.date("1 day", from));
+        assertEquals("2017-07-04T02:40:00Z", OverpassDownloadReader.date("10days", from));
+        assertEquals("2017-07-14T01:40:00Z", OverpassDownloadReader.date("1 hour", from));
+        assertEquals("2017-07-13T16:40:00Z", OverpassDownloadReader.date("10hours", from));
+        assertEquals("2017-07-14T02:39:00Z", OverpassDownloadReader.date("1 minute", from));
+        assertEquals("2017-07-14T02:30:00Z", OverpassDownloadReader.date("10minutes", from));
+        assertEquals("2017-07-14T02:39:59Z", OverpassDownloadReader.date("1 second", from));
+        assertEquals("2017-07-14T02:39:50Z", OverpassDownloadReader.date("10seconds", from));
+
+        assertEquals("2016-07-13T02:40:00Z", OverpassDownloadReader.date("1 year 1 day", from));
+        assertEquals("2016-07-14T02:38:20Z", OverpassDownloadReader.date("1 year 100 seconds", from));
+        assertEquals("2017-07-13T02:38:20Z", OverpassDownloadReader.date("1 day  100 seconds", from));
+    }
+
+    /**
+     * Tests evaluating the extended query feature {@code date} through {@code newer:} operator.
+     */
+    @Test
+    public void testDateNewer() {
+        final String query = getExpandedQuery("type:node and newer:3minutes");
+        String statement = query.substring(query.indexOf("node(newer:\"") + 12, query.lastIndexOf("\");"));
+        assertNotNull(DateUtils.fromString(statement));
+    }
+
+    /**
      * Tests evaluating the extended query feature {@code geocodeArea}.
      */
