import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.Options; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.Date; public class MomentJsDateFormatHelper implements Helper { // RAJ - This file must be in the root classpath (I stick it under src/main/resources and maven puts it where it needs to go) // RAJ - I am using v1.7.2, as the latest version doesn't set a global "moment" constructor correctly under Rhino // RAJ - You can find v1.7.2 here: https://raw.githubusercontent.com/moment/moment/1.7.2/moment.js @NotNull private static final String MOMENT_JS_FILE = "moment.js"; @NotNull private final ScriptEngine m_momentJsScriptEngine = initializeScriptEngine( MOMENT_JS_FILE ); @Override @NotNull public CharSequence apply ( @NotNull Date date, @NotNull Options options ) throws IOException { String format = options.param(0, options.hash("format", null)); return formatDate( date, format ); } /* @NotNull private synchronized String formatDate ( @NotNull Date date, @Nullable String format ) { try { Object moment = ( (Invocable)m_momentJsScriptEngine ).invokeFunction( "moment", date.getTime() ); if( format == null ) return ( (Invocable)m_momentJsScriptEngine ).invokeMethod( moment, "calendar" ).toString(); String[] formatArgs = new String[] { format }; // RAJ - It is probably cleaner to use Invocable instead of eval but this line always throws NPE under Java6 return ( (Invocable)m_momentJsScriptEngine ).invokeMethod( moment, "format", formatArgs ).toString(); } catch( ScriptException e ) { throw new RuntimeException( "Failed to format date", e ); } catch( NoSuchMethodException e ) { throw new RuntimeException( "Failed to format date", e ); } } */ @NotNull private CharSequence formatDate ( @NotNull Date date, @Nullable String format ) { String javascript = getMomentJsFormattingJavascript( date, format ); return evalJavascript( javascript ); } @NotNull private String getMomentJsFormattingJavascript ( @NotNull Date date, @Nullable String format ) { if( format == null ) return "new moment(" + date.getTime() + ").calendar();"; format = format.replace( "'", "\\'" ); return "new moment(" + date.getTime() + ").format('" + format + "');"; } @NotNull private static ScriptEngine initializeScriptEngine ( @NotNull String momentJsFile) { InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( momentJsFile ); Reader reader = new InputStreamReader( is ); ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); try { engine.eval( reader ); // RAJ - Since I'm only concerned about my own locale, I don't worry about it but you may want to load // any locale data you care about here... return engine; } catch( ScriptException e ) { throw new RuntimeException( e ); } } // RAJ - I have no idea how thread safe the Rhino code is, so I'm synchronizing this method just to be safe @NotNull private synchronized String evalJavascript ( @NotNull String script ) { try { return m_momentJsScriptEngine.eval( script ).toString(); } catch( ScriptException e ) { throw new RuntimeException( e ); } } }