diff --git a/src/org/openstreetmap/josm/tools/date/DateUtils.java b/src/org/openstreetmap/josm/tools/date/DateUtils.java
index 9cd7cc82e..09ba101a7 100644
--- a/src/org/openstreetmap/josm/tools/date/DateUtils.java
+++ b/src/org/openstreetmap/josm/tools/date/DateUtils.java
@@ -14,12 +14,8 @@
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeFactory;
-
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
-import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.UncheckedParseException;
 
 /**
@@ -43,18 +39,6 @@
      */
     public static final BooleanProperty PROP_ISO_DATES = new BooleanProperty("iso.dates", false);
 
-    private static final DatatypeFactory XML_DATE;
-
-    static {
-        DatatypeFactory fact = null;
-        try {
-            fact = DatatypeFactory.newInstance();
-        } catch (DatatypeConfigurationException e) {
-            Logging.error(e);
-        }
-        XML_DATE = fact;
-    }
-
     /**
      * Constructs a new {@code DateUtils}.
      */
@@ -69,7 +53,7 @@ private DateUtils() {
      * @throws UncheckedParseException if the date does not match any of the supported date formats
      * @throws DateTimeException if the value of any field is out of range, or if the day-of-month is invalid for the month-year
      */
-    public static synchronized Date fromString(String str) {
+    public static Date fromString(String str) {
         return new Date(tsFromString(str));
     }
 
@@ -80,9 +64,16 @@ public static synchronized Date fromString(String str) {
      * @throws UncheckedParseException if the date does not match any of the supported date formats
      * @throws DateTimeException if the value of any field is out of range, or if the day-of-month is invalid for the month-year
      */
-    public static synchronized long tsFromString(String str) {
+    public static long tsFromString(String str) {
         // "2007-07-25T09:26:24{Z|{+|-}01[:00]}"
-        if (checkLayout(str, "xxxx-xx-xxTxx:xx:xxZ") ||
+        if (checkLayout(str, "xxxx-xx-xx")) {
+            final ZonedDateTime local = ZonedDateTime.of(
+                    parsePart4(str, 0),
+                    parsePart2(str, 5),
+                    parsePart2(str, 8),
+                    0,0,0, 0, ZoneOffset.UTC);
+            return local.toInstant().toEpochMilli();
+        } else if (checkLayout(str, "xxxx-xx-xxTxx:xx:xxZ") ||
                 checkLayout(str, "xxxx-xx-xxTxx:xx:xx") ||
                 checkLayout(str, "xxxx:xx:xx xx:xx:xx") ||
                 checkLayout(str, "xxxx-xx-xx xx:xx:xxZ") ||
@@ -133,12 +124,7 @@ public static synchronized long tsFromString(String str) {
             if (d != null)
                 return d.getTime();
         }
-
-        try {
-            return XML_DATE.newXMLGregorianCalendar(str).toGregorianCalendar().getTimeInMillis();
-        } catch (IllegalArgumentException ex) {
-            throw new UncheckedParseException("The date string (" + str + ") could not be parsed.", ex);
-        }
+        throw new UncheckedParseException("The date string (" + str + ") could not be parsed.");
     }
 
     /**
@@ -157,7 +143,7 @@ public static String fromTimestamp(long timestamp) {
      * @return The formatted date
      * @since 14434
      */
-    public static synchronized String fromTimestampInMillis(long timestamp) {
+    public static String fromTimestampInMillis(long timestamp) {
         final ZonedDateTime temporal = Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.UTC);
         return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(temporal);
     }
@@ -167,7 +153,7 @@ public static synchronized String fromTimestampInMillis(long timestamp) {
      * @param timestamp number of seconds since the epoch
      * @return The formatted date
      */
-    public static synchronized String fromTimestamp(int timestamp) {
+    public static String fromTimestamp(int timestamp) {
         return fromTimestamp(Integer.toUnsignedLong(timestamp));
     }
 
@@ -176,7 +162,7 @@ public static synchronized String fromTimestamp(int timestamp) {
      * @param date The date to format
      * @return The formatted date
      */
-    public static synchronized String fromDate(Date date) {
+    public static String fromDate(Date date) {
         final ZonedDateTime temporal = date.toInstant().atZone(ZoneOffset.UTC);
         return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(temporal);
     }
diff --git a/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java b/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java
index 3e9efe675..d6bcc949c 100644
--- a/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java
+++ b/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java
@@ -9,8 +9,11 @@
 
 import java.text.DateFormat;
 import java.util.Date;
+import java.util.Random;
 import java.util.TimeZone;
+import java.util.concurrent.ForkJoinPool;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
@@ -191,10 +194,20 @@ public void testTsFromString() {
         assertEquals(1459695600123L + 5 * 3600 * 1000, DateUtils.tsFromString("2016-04-03T15:00:00.123-05:00"));
 
         // Local time
-        TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));
+        setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
         assertEquals(1459688400000L, DateUtils.tsFromString("03-APR-16 15:00:00"));
     }
 
+    @Test
+    @Ignore("slow, for thread safety testing")
+    public void testTsFromString800k() throws Exception {
+        new ForkJoinPool(64).submit(() -> new Random()
+                .longs(800_000)
+                .parallel()
+                .forEach(ignore -> testTsFromString())).get();
+        // 13.992s → 2.213s
+    }
+
     /**
      * Unit test of {@link DateUtils#tsFromString} method.
      */
