Skip to content

Instantly share code, notes, and snippets.

@rage-shadowman
Last active August 29, 2015 14:12
Show Gist options
  • Save rage-shadowman/550f7686b98c9ff6fcd4 to your computer and use it in GitHub Desktop.
Save rage-shadowman/550f7686b98c9ff6fcd4 to your computer and use it in GitHub Desktop.
MomentJsDateFormatHelper that I use with jknack's handlebars.java project.
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<Date>
{
// 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.<String>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 );
}
}
}
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.context.MapValueResolver;
import junit.framework.Assert;
import org.apache.commons.lang3.StringEscapeUtils;
import org.junit.Test;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
public class MomentJsDateFormatHelperTest
{
@Test
public void testMomentJsDateFormatting ()
throws IOException, ParseException
{
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss a" );
Date date = sdf.parse( "2014-10-01 12:32:23 PM" );
Template template = compileTemplate( "date is {{dateFormat date format='MMMM Do, YYYY [at] hh:mm a [and] ss [seconds]'}}" );
Context context = createContext( Collections.singletonMap( "date", date ) );
Assert.assertEquals( "date is October 1st, 2014 at 12:32 pm and 23 seconds", template.apply( context ) );
}
@Test
// RAJ - internally we evaluate the javascript inline using single quotes to surround the format
public void testFormatIncludingQuotes1 ()
throws IOException, ParseException
{
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss a" );
Date date = sdf.parse( "2014-10-01 12:32:23 PM" );
Template template = compileTemplate( "date is {{dateFormat date format='MMMM Do, [\\']YY [\"yay\"]'}}" );
Context context = createContext( Collections.singletonMap( "date", date ) );
// RAJ - By default, Handlebars escapes text for HTML and we have quotes in our generated output
String htmlEscapedText = template.apply( context );
String unescapedText = StringEscapeUtils.unescapeHtml4( htmlEscapedText );
Assert.assertEquals( "date is October 1st, '14 \"yay\"", unescapedText );
}
@Test
// RAJ - internally we evaluate the javascript inline using single quotes to surround the format
public void testFormatIncludingQuotes2 ()
throws IOException, ParseException
{
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss a" );
Date date = sdf.parse( "2014-10-01 12:32:23 PM" );
Template template = compileTemplate( "date is {{dateFormat date format=\"MMMM Do, [']YY [\\\"yay\\\"]\"}}" );
Context context = createContext( Collections.singletonMap( "date", date ) );
// RAJ - By default, Handlebars escapes text for HTML and we have quotes in our generated output
String htmlEscapedText = template.apply( context );
String unescapedText = StringEscapeUtils.unescapeHtml4( htmlEscapedText );
Assert.assertEquals( "date is October 1st, '14 \"yay\"", unescapedText );
}
@Test
public void testFormatAndEmptyFormatString ()
throws IOException, ParseException
{
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss a" );
Date date = sdf.parse( "2014-10-01 12:32:23 PM" );
Template template = compileTemplate( "date is {{dateFormat date}}" );
Context context = createContext( Collections.singletonMap( "date", date ) );
// RAJ - I have no idea how this should behave under non-US locales, so I'm not sure how useful this assertion is
// RAJ - We should probably just check to make sure that something was generated here, rather than the specific format
// RAJ - Then again, all of the MMMM stuff above is also locale (language) specific...
Assert.assertEquals( "date is 10/01/2014", template.apply( context ) );
}
private Context createContext ( Object data )
{
return Context.newBuilder( data )
.resolver( MapValueResolver.INSTANCE )
.build();
}
private Template compileTemplate ( String templateText )
throws IOException
{
Handlebars handlebars = new Handlebars();
handlebars.registerHelper( "dateFormat", new MomentJsDateFormatHelper() );
return handlebars.compileInline( templateText );
}
}
@rage-shadowman
Copy link
Author

I based this gist off of another gist from UnquietCode that you can find here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment