Index: trunk/src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java	(revision 15976)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/ConditionalKeys.java	(revision 15978)
@@ -9,4 +9,5 @@
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 import java.util.regex.Matcher;
@@ -17,5 +18,4 @@
 import org.openstreetmap.josm.data.validation.Test;
 import org.openstreetmap.josm.data.validation.TestError;
-import org.openstreetmap.josm.tools.LanguageInfo;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.SubclassFilteredCollection;
@@ -205,5 +205,5 @@
                     if (condition.matches(".*[0-9]:[0-9]{2}.*")) {
                         final List<OpeningHourTest.OpeningHoursTestError> errors = openingHourTest.checkOpeningHourSyntax(
-                                "", condition, OpeningHourTest.CheckMode.TIME_RANGE, true, LanguageInfo.getJOSMLocaleCode());
+                                "", condition, true, Locale.getDefault());
                         if (!errors.isEmpty()) {
                             return errors.get(0).getMessage();
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java	(revision 15976)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java	(revision 15978)
@@ -4,15 +4,14 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.io.StringReader;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
 
-import javax.script.Invocable;
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
-import javax.swing.JOptionPane;
-
+import ch.poole.openinghoursparser.OpeningHoursParser;
+import ch.poole.openinghoursparser.ParseException;
+import ch.poole.openinghoursparser.Rule;
+import ch.poole.openinghoursparser.Util;
 import org.openstreetmap.josm.command.ChangePropertyCommand;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -20,24 +19,13 @@
 import org.openstreetmap.josm.data.validation.Test.TagTest;
 import org.openstreetmap.josm.data.validation.TestError;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.CachedFile;
-import org.openstreetmap.josm.tools.LanguageInfo;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
 
 /**
  * Tests the correct usage of the opening hour syntax of the tags
  * {@code opening_hours}, {@code collection_times}, {@code service_times} according to
- * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a>.
+ * <a href="https://github.com/simonpoole/OpeningHoursParser">OpeningHoursParser</a>.
  *
- * @since 6370
+ * @since 6370 (using opening_hours.js), 15978 (using OpeningHoursParser)
  */
 public class OpeningHourTest extends TagTest {
-
-    /**
-     * Javascript engine
-     */
-    public static final ScriptEngine ENGINE = Utils.getJavaScriptEngine();
 
     /**
@@ -47,38 +35,4 @@
         super(tr("Opening hours syntax"),
                 tr("This test checks the correct usage of the opening hours syntax."));
-    }
-
-    @Override
-    public void initialize() throws Exception {
-        super.initialize();
-        if (ENGINE != null) {
-            try (CachedFile cf = new CachedFile("resource://data/validator/opening_hours.js");
-                 Reader reader = cf.getContentReader()) {
-                ENGINE.eval("var console={};console.debug=print;console.log=print;console.warn=print;console.error=print;");
-                ENGINE.eval(reader);
-                // fake country/state to not get errors on holidays
-                ENGINE.eval("var nominatimJSON = {address: {country_code: 'xa'}};");
-                ENGINE.eval(
-                        "var oh = function (value, tag_key, mode, locale) {" +
-                        " try {" +
-                        "    var conf = {tag_key: tag_key, locale: locale, additional_rule_separator: false};" +
-                        "    if (mode > -1) {" +
-                        "      conf.mode = mode;" +
-                        "    }" +
-                        "    var r = new opening_hours(value, nominatimJSON, conf);" +
-                        "    r.getErrors = function() {return [];};" +
-                        "    return r;" +
-                        "  } catch (err) {" +
-                        "    return {" +
-                        "      prettifyValue: function() {return null;}," +
-                        "      getWarnings: function() {return [];}," +
-                        "      getErrors: function() {return [err.toString()]}" +
-                        "    };" +
-                        "  }" +
-                        "};");
-            }
-        } else {
-            Logging.warn("Unable to initialize OpeningHourTest because no JavaScript engine has been found");
-        }
     }
 
@@ -104,36 +58,4 @@
 
     /**
-     * Parses the opening hour syntax of the {@code value} given according to
-     * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns an object on which
-     * methods can be called to extract information.
-     * @param value the opening hour value to be checked
-     * @param tagKey the OSM key (should be "opening_hours", "collection_times" or "service_times")
-     * @param mode whether to validate {@code value} as a time range, or points in time, or both. Can be null
-     * @param locale the locale code used for localizing messages
-     * @return The value returned by the underlying method. Usually a {@code jdk.nashorn.api.scripting.ScriptObjectMirror}
-     * @throws ScriptException if an error occurs during invocation of the underlying method
-     * @throws NoSuchMethodException if underlying method with given name or matching argument types cannot be found
-     * @since 13147
-     */
-    public Object parse(String value, String tagKey, CheckMode mode, String locale) throws ScriptException, NoSuchMethodException {
-        return ((Invocable) ENGINE).invokeFunction("oh", value, tagKey, mode != null ? mode.code : -1, locale);
-    }
-
-    @SuppressWarnings("unchecked")
-    protected List<Object> getList(Object obj) throws ScriptException, NoSuchMethodException {
-        if (obj == null || "".equals(obj)) {
-            return Arrays.asList();
-        } else if (obj instanceof String) {
-            final Object[] strings = ((String) obj).split("\\\\n");
-            return Arrays.asList(strings);
-        } else if (obj instanceof List) {
-            return (List<Object>) obj;
-        } else {
-            // recursively call getList() with argument converted to newline-separated string
-            return getList(((Invocable) ENGINE).invokeMethod(obj, "join", "\\n"));
-        }
-    }
-
-    /**
      * An error concerning invalid syntax for an "opening_hours"-like tag.
      */
@@ -144,5 +66,5 @@
 
         /**
-         * Constructs a new {@code OpeningHoursTestError} with a known pretiffied value.
+         * Constructs a new {@code OpeningHoursTestError} with a known prettified value.
          * @param message The error message
          * @param severity The error severity
@@ -203,7 +125,6 @@
 
     /**
-     * Checks for a correct usage of the opening hour syntax of the {@code value} given according to
-     * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns a list containing
-     * validation errors or an empty list. Null values result in an empty list.
+     * Checks for a correct usage of the opening hour syntax of the {@code value} given,
+     * and returns a list containing validation errors or an empty list. Null values result in an empty list.
      * @param key the OSM key (should be "opening_hours", "collection_times" or "service_times"). Used in error message
      * @param value the opening hour value to be checked.
@@ -211,110 +132,40 @@
      */
     public List<OpeningHoursTestError> checkOpeningHourSyntax(final String key, final String value) {
-        return checkOpeningHourSyntax(key, value, null, false, LanguageInfo.getJOSMLocaleCode());
+        return checkOpeningHourSyntax(key, value, false, Locale.getDefault());
     }
 
     /**
-     * Checks for a correct usage of the opening hour syntax of the {@code value} given according to
-     * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns a list containing
-     * validation errors or an empty list. Null values result in an empty list.
+     * Checks for a correct usage of the opening hour syntax of the {@code value} given,
+     * and returns a list containing validation errors or an empty list. Null values result in an empty list.
      * @param key the OSM key (should be "opening_hours", "collection_times" or "service_times").
      * @param value the opening hour value to be checked.
-     * @param mode whether to validate {@code value} as a time range, or points in time, or both. Can be null
      * @param ignoreOtherSeverity whether to ignore errors with {@link Severity#OTHER}.
      * @param locale the locale code used for localizing messages
      * @return a list of {@link TestError} or an empty list
      */
-    public List<OpeningHoursTestError> checkOpeningHourSyntax(final String key, final String value, CheckMode mode,
-            boolean ignoreOtherSeverity, String locale) {
-        if (ENGINE == null || value == null || value.isEmpty()) {
+    public List<OpeningHoursTestError> checkOpeningHourSyntax(final String key, final String value, boolean ignoreOtherSeverity, Locale locale) {
+        if (value == null || value.isEmpty()) {
             return Collections.emptyList();
         }
-        final List<OpeningHoursTestError> errors = new ArrayList<>();
+
+        ch.poole.openinghoursparser.I18n.setLocale(locale);
+        String prettifiedValue = null;
         try {
-            final Object r = parse(value, key, mode, locale);
-            String prettifiedValue = null;
-            try {
-                prettifiedValue = getOpeningHoursPrettifiedValues(r);
-            } catch (ScriptException | NoSuchMethodException e) {
-                Logging.warn(e);
+            final List<Rule> rules = new OpeningHoursParser(new StringReader(value)).rules(false);
+            prettifiedValue = Util.rulesToOpeningHoursString(rules);
+            if (!Objects.equals(value, prettifiedValue)) {
+                // parse again in strict mode for detailed message
+                new OpeningHoursParser(new StringReader(value)).rules(true);
             }
-            for (final Object i : getOpeningHoursErrors(r)) {
-                errors.add(new OpeningHoursTestError(getErrorMessage(key, i), Severity.ERROR, prettifiedValue));
-            }
-            for (final Object i : getOpeningHoursWarnings(r)) {
-                errors.add(new OpeningHoursTestError(getErrorMessage(key, i), Severity.WARNING, prettifiedValue));
-            }
-            if (!ignoreOtherSeverity && errors.isEmpty() && prettifiedValue != null && !value.equals(prettifiedValue)) {
-                errors.add(new OpeningHoursTestError(tr("opening_hours value can be prettified"), Severity.OTHER, prettifiedValue));
-            }
-        } catch (ScriptException | NoSuchMethodException ex) {
-            Logging.error(ex);
-            GuiHelper.runInEDT(() -> new Notification(Utils.getRootCause(ex).getMessage()).setIcon(JOptionPane.ERROR_MESSAGE).show());
+        } catch (ParseException e) {
+            return Collections.singletonList(new OpeningHoursTestError(e.getMessage(), Severity.WARNING, prettifiedValue));
         }
-        return errors;
-    }
 
-    /**
-     * Returns the prettified value returned by the opening hours parser.
-     * @param r result of {@link #parse}
-     * @return the prettified value returned by the opening hours parser
-     * @throws NoSuchMethodException if method "prettifyValue" or matching argument types cannot be found
-     * @throws ScriptException if an error occurs during invocation of the JavaScript method
-     * @since 13296
-     */
-    public final String getOpeningHoursPrettifiedValues(Object r) throws NoSuchMethodException, ScriptException {
-        return (String) ((Invocable) ENGINE).invokeMethod(r, "prettifyValue");
-    }
-
-    /**
-     * Returns the list of errors returned by the opening hours parser.
-     * @param r result of {@link #parse}
-     * @return the list of errors returned by the opening hours parser
-     * @throws NoSuchMethodException if method "getErrors" or matching argument types cannot be found
-     * @throws ScriptException if an error occurs during invocation of the JavaScript method
-     * @since 13296
-     */
-    public final List<Object> getOpeningHoursErrors(Object r) throws NoSuchMethodException, ScriptException {
-        return getList(((Invocable) ENGINE).invokeMethod(r, "getErrors"));
-    }
-
-    /**
-     * Returns the list of warnings returned by the opening hours parser.
-     * @param r result of {@link #parse}
-     * @return the list of warnings returned by the opening hours parser
-     * @throws NoSuchMethodException if method "getWarnings" or matching argument types cannot be found
-     * @throws ScriptException if an error occurs during invocation of the JavaScript method
-     * @since 13296
-     */
-    public final List<Object> getOpeningHoursWarnings(Object r) throws NoSuchMethodException, ScriptException {
-        return getList(((Invocable) ENGINE).invokeMethod(r, "getWarnings"));
-    }
-
-    /**
-     * Translates and shortens the error/warning message.
-     * @param o error/warning message returned by {@link #getOpeningHoursErrors} or {@link #getOpeningHoursWarnings}
-     * @return translated/shortened error/warning message
-     * @since 13298
-     */
-    public static String getErrorMessage(Object o) {
-        return o.toString().trim()
-        .replace("Unexpected token:", tr("Unexpected token:"))
-        .replace("Unexpected token (school holiday parser):", tr("Unexpected token (school holiday parser):"))
-        .replace("Unexpected token in number range:", tr("Unexpected token in number range:"))
-        .replace("Unexpected token in week range:", tr("Unexpected token in week range:"))
-        .replace("Unexpected token in weekday range:", tr("Unexpected token in weekday range:"))
-        .replace("Unexpected token in month range:", tr("Unexpected token in month range:"))
-        .replace("Unexpected token in year range:", tr("Unexpected token in year range:"))
-        .replace("This means that the syntax is not valid at that point or it is currently not supported.", tr("Invalid/unsupported syntax."));
-    }
-
-    /**
-     * Translates and shortens the error/warning message.
-     * @param key OSM key
-     * @param o error/warning message returned by {@link #getOpeningHoursErrors} or {@link #getOpeningHoursWarnings}
-     * @return translated/shortened error/warning message
-     */
-    static String getErrorMessage(String key, Object o) {
-        return key + " - " + getErrorMessage(o);
+        if (ignoreOtherSeverity || Objects.equals(value, prettifiedValue)) {
+            return Collections.emptyList();
+        } else {
+            return Collections.singletonList(
+                    new OpeningHoursTestError(tr("{0} value can be prettified", key), Severity.OTHER, prettifiedValue));
+        }
     }
 
