Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tmarwen/c35b0b3c0dc9920a145f to your computer and use it in GitHub Desktop.
Save tmarwen/c35b0b3c0dc9920a145f to your computer and use it in GitHub Desktop.
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
@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 testTemplateWithMomentJsDateFormatting ()
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 testTemplateWithFormatIncludingQuotes1 ()
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 testTemplateWithFormatIncludingQuotes2 ()
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 testTemplateWithFormatAndEmptyFormatString ()
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 );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment