Fitnesse Date Plugin
A long time ago at work I ran into a small problem during some of our tests with fitnesse. We had a few fitnesse tests that tested time based rules, stuff like a 30 day rule that basically goes something like “if the file was received longer than 60 days ago, do not process it.” This was slightly problematic though as we had to update tests from time to time if we were going to hard code dates in our tests, so I decided to take it upon myself to create a wiki plugin to generate the date time dynamically.
Anyhow, I posted it on the fitnesse mailing list on yahoogroups, but I have neglected to take the time to write up instructions on it. So, without furth ado, here it is… the fitnesse sysdate plugin how to (or you can download it now).
Installation
Installation is rather simple… simply place the SysdatePlugin.jar plugin somewhere in your class path and add it to your plugins.properties file (if you don’t already have one, create it in the same directory you run fitnesse from).
Below is what you need to add to your plugins.properties file:
WikiWidgets=fitnesse.wikitext.widgets.SystemDateWikiWidget
Start fitnesse and you’re ready to go! You should see the following at the console once fitnesse starts up.
Custom wiki widgets loaded:
fitnesse.wikitext.widgets.SystemDateWikiWidget
Usage
Using the plugin is quite simple. To start using it right away, simply type:
!sysdate
This will print the current date in MMddYYYY format. If you want to use a different format, you have the option of passing the format in as a parameter… basically it will accept any format string that SimpleDateFormat takes, so refer to the api documentation for SimpleDateFormat for further information.
For example, if I wanted to print the date for today as something like 11/11/2006 21:15:23, I’d use the following:
!sysdate(MM/dd/yyyy hh:mm:ss)
Of course, it’s not usefull without features to generate a date in the future, or in the past. For example, say I want to have a test that alsways tests a boundry condition, say against a date that is always EXACTLY 30 days in the past.
The following are examples of various formats to use. Simply adding a +number or -number to the sysdate will make n days in the past or future.
!sysdate(MM/dd/yyyy hh:mm:ss)-30 !sysdate-30 !sysdate+30 !sysdate(MM/dd/yyyy hh:mm:ss)+5
That’s about it. If there are any features anyone like to have, please feel free to let me know. You can download the plugin here.
The Source
Here is the accompanying unit test:
package fitnesse.wikitext.widgets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import fitnesse.wiki.*;
import fitnesse.wikitext.WidgetBuilder;
public class SystemDateWikiWidgetTest extends WidgetTest
{
private WikiPage root;
private PageCrawler crawler;
private WikiPage page;
private WidgetRoot widgetRoot;
public void setUp() throws Exception
{
root = InMemoryPage.makeRoot("root");
crawler = root.getPageCrawler();
page = crawler.addPage(root, PathParser.parse("MyPage"));
widgetRoot = new WidgetRoot("", page);
}
public void testMatches() throws Exception
{
assertMatches("!sysdate");
assertMatches("!sysdate(mmmddyyyy)");
assertMatches("!sysdate(MM-dd-yy)");
assertMatches("!sysdate(MM-dd-yy)-1");
assertMatches("!sysdate(MM-dd-yy)-22");
assertMatches("!sysdate(MM-dd-yy)-300");
assertMatches("!sysdate-30");
}
protected String getRegexp()
{
return SystemDateWikiWidget.REGEXP;
}
/**
* Allow adding of sysdate special variable
*
* @author jamescarr
* @throws Exception
*/
public void testSysdateVariable() throws Exception{
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!sysdate\n");
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate");
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat format = new SimpleDateFormat("MMddyyyy");
System.out.println(format.format(date));
assertEquals(format.format(date), w.render());
}
public void testSysdateVariableWithFormat() throws Exception{
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!sysdate(MMM-dd-yyyy)\n");
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate(MMM-dd-yyyy)");
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat format = new SimpleDateFormat("MMM-dd-yyyy");
System.out.println(format.format(date));
assertEquals(format.format(date), w.render());
}
public void testSysdateVariableWithBadFormat() throws Exception{
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!define var {!sysdate(shithead)}\n");
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate(shithead)");
// default
SimpleDateFormat format = new SimpleDateFormat("MMddyyyy");
Date date = new Date(System.currentTimeMillis());
assertEquals( format.format(date), w.render());
}
public void testMultipleSysDateCalls() throws Exception{
String dates = "!sysdate(MMMddyyyy)\n!sysdate\n!sysdate(MM-dd-yyyy)\n!sysdate(yyyy-MMM-dd)\n!sysdate(MM/dd/yyyy)";
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);
// expect only the first date
SimpleDateFormat format = new SimpleDateFormat("MMMddyyyy");
Date date = new Date(System.currentTimeMillis());
assertEquals(format.format(date), w.render());
}
public void testSubtractZeroDays() throws Exception{
String dates = "!sysdate(MM/dd/yyyy)-0";
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);
// expect only the first date
SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
Calendar cal = Calendar.getInstance();
Date date = new Date(System.currentTimeMillis());
String dateString = format.format(date);
assertEquals(dateString, w.render());
}
public void testSubtractTwentyDays() throws Exception{
String dates = "!sysdate(MM/dd/yyyy)-20";
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);
// expect only the first date
SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
Calendar cal = Calendar.getInstance();
Date date = new Date(System.currentTimeMillis());
cal.setTime(date);
cal.add(Calendar.DATE, -20);
date = cal.getTime();
String dateString = format.format(date);
assertEquals(dateString, w.render());
}
public void testAddTwentyDays() throws Exception{
String dates = "!sysdate(MM/dd/yyyy)+20";
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);
// expect only the first date
SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
Calendar cal = Calendar.getInstance();
Date date = new Date(System.currentTimeMillis());
cal.setTime(date);
cal.add(Calendar.DATE, 20);
date = cal.getTime();
String dateString = format.format(date);
assertEquals(dateString, w.render());
}
public void testAddThreeHundredDays() throws Exception{
String dates = "!sysdate(MM/dd/yyyy)+300";
WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");
WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);
// expect only the first date
SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
Calendar cal = Calendar.getInstance();
Date date = new Date(System.currentTimeMillis());
cal.setTime(date);
cal.add(Calendar.DATE, 300);
date = cal.getTime();
String dateString = format.format(date);
assertEquals(dateString, w.render());
}
}
And here is the source for the date widget class:
package fitnesse.wikitext.widgets;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import fitnesse.wikitext.WikiWidget;
public class SystemDateWikiWidget extends WikiWidget {
private static final String SUBTRACT = "-";
private static final String ADD = "+";
private static final String DATE_FORMAT_REGEX = "\\(\\w+.*\\)";
public static final String REGEXP = "!sysdate(\\(\\w+.*?\\))?(-\\d+|\\+\\d+)?";
public static final Pattern pattern = Pattern.compile(REGEXP);
private static final Pattern dateMethodPattern = Pattern.compile(DATE_FORMAT_REGEX);
private static final Pattern subtractionPattern = Pattern.compile("(-\\d+|\\+\\d+)$");
private String originalText = "";
private String token = null;
public SystemDateWikiWidget(ParentWidget parent, String text) {
super(parent);
originalText = text;
Matcher match = pattern.matcher(text);
if (match.find()) {
token = match.group(0);
}
}
private String getCurrentDate(String token) {
SimpleDateFormat formatter = getDateFormatter(token);
Date date = getTheDate(token);
return formatter.format(date);
}
private Date getTheDate(String token) {
Matcher match = subtractionPattern.matcher(token);
Date date = new Date(System.currentTimeMillis());
int days = 0;
if (match.find()) {
String result = match.group(0);
if (0 == result.indexOf(SUBTRACT)) {
days = getIntegerValue(result, SUBTRACT);
}
else if (0 == result.indexOf(ADD)) {
days = getIntegerValue(result, ADD);
}
date = getDateDaysFromDate(date, days);
}
return date;
}
private Date getDateDaysFromDate(Date date, int days) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, days);
return cal.getTime();
}
private SimpleDateFormat getDateFormatter(String token) {
SimpleDateFormat formatter = new SimpleDateFormat("MMddyyyy");
Matcher match = dateMethodPattern.matcher(token);
if (match.find()) {
String format = match.group(0);
format = removeParenthesis(format);
try {
formatter = new SimpleDateFormat(format);
}
catch (IllegalArgumentException e) {
}
}
return formatter;
}
private int getIntegerValue(String result, String modifier) {
result = result.replaceAll("\\" + modifier, "");
return (SUBTRACT.equals(modifier)) ? -Integer.parseInt(result) : Integer.parseInt(result);
}
private String removeParenthesis(String format) {
return format.replaceAll("\\(", "").replaceAll("\\)", "");
}
public String render() throws Exception {
return (token != null) ? getCurrentDate(token) : originalText;
}
}
If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!








January 26th, 2007 at 5:35 am
Hey Dude,
So you have the source for this?
Handy plugin, thanks, I’ll try and return the favour to the community.
Jamie
January 26th, 2007 at 5:36 am
That was a typo, I meant to write “do you have the source”.
Jamie
January 26th, 2007 at 9:53 am
Sure do… I’ll re-edit the post and make the source availble.
Thanks,
James
February 1st, 2007 at 5:46 am
Cheers mate.
That is handy.
You know, I was trawling the user group. We have done so much that I reckon, together, we could knock a cookbook out in weeks. I would happiliy contribute chapters on linking Fit with the development process, etc. You could write the plugins section. We are crying out for another book, on advance topics.
J
June 22nd, 2007 at 7:40 am
I am having a few issues with the installation. The .jar is in the ClassPath, I have created the plugins.properties file and added the entry as specified above, but I get the following error when I start the server:
Exception in thread “main” java.lang.NoClassDefFoundError: fitnesse/wikitext/WikiWidget
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$000(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at fitnesse.ComponentFactory.loadWikiWidgetPlugins(ComponentFactory.java
:126)
at fitnesse.FitNesse.loadContext(FitNesse.java:54)
at fitnesse.FitNesse.main(FitNesse.java:26)
Any ideas?
Thanks
January 17th, 2008 at 6:01 am
I got the same exception as peter. Did you know how to solve this?
Exception in thread “main” java.lang.ClassNotFoundException: fitnesse.wikitext.w
idgets.DateExpressionWidget
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:164)
at fitnesse.ComponentFactory.loadWikiWidgetPlugins(Unknown Source)
at fitnesse.FitNesse.loadContext(Unknown Source)
at fitnesse.FitNesse.main(Unknown Source)
January 17th, 2008 at 1:51 pm
Hi Dava and Peter,
The problem your having results from the plugin not being on your classpath… you must add the jar to your classpath when you run fitnesse. This can be done by adding the jar to the -cp argument in the fitnesse.bat file.
Hope that helps!
James
June 20th, 2008 at 8:07 am
Hi,
is it possible to store the sysdate in a variable or use it within a cell of a fixture table?
If I type in !sysdate at the beginning of a line everything works.
Then I tried:
!define dateVar {!sysdate}
result:
variable defined: dateVar=!sysdate
(I expected: variable defined: dateVar=06202008)
then I wanted to use !sysdate within a fixture:
!|GuiAction|
|action|value|result?|
|click|BTN_TYPE_DISPO_MAIN|!sysdate|
and sysdate was not replaced by the date..
Do I miss something ?
Thank you in advance
Christine
July 9th, 2008 at 9:40 pm
Wonderful plugin!
Solves the old current-date-moving-target-testability problem!
Thank you!
August 7th, 2008 at 2:15 am
Christine,
I faced the same issue, because as it is stated on FitNesse.MarkupVariables:
“The text in a variable is never interpreted as wiki markup. It is always raw literal text.”
Andras