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;
}
}


Hey Dude,
So you have the source for this?
Handy plugin, thanks, I’ll try and return the favour to the community.
Jamie
That was a typo, I meant to write “do you have the source”.
Jamie
Sure do… I’ll re-edit the post and make the source availble.
Thanks,
James
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
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
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)
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
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
Wonderful plugin!
Solves the old current-date-moving-target-testability problem!
Thank you!
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
Hi,
Great plugin.
We have come across a problem: the sysdate does not get intetrpreted (e.g. substituted by the date string).
Example:
!| Generic Fixture | ${sel} |
| user types | !sysdate(dd/MM/yyyy) | into field | sSomeField |
we are not sure if it is related to the Generic Fixture plugin we are using or not.
The only occasion where we found it to work is in !Comment tables.
Any help would be greatly appreciated.
Thanks in advance,
Aristotelis.
I am using the following command line:
java -cp fitnesse.jar fitnesse.FitNesse -jar SysdatePlugin.jar -p 8080 -e 0
and see the following:
FitNesse (v20090112) Started…
port: 8080
root page: fitnesse.wiki.FileSystemPage at ./FitNesseRoot
logger: none
authenticator: fitnesse.authentication.PromiscuousAuthenticator
html page factory: fitnesse.html.HtmlPageFactory
page version expiration set to 0 days.
Custom wiki widgets loaded:
fitnesse.wikitext.widgets.SystemDateWikiWidget
which leads me to believe the plugin is installed properly.
When I try to use the plugin in the page either in a markup variable such as:
!define currentdate {!sysdate}
or in a column fixture such as:
!|StringFixture|
|field|field?|
|!sysdate|>>what|
I do not see the date.
However when I simply put
!sysdate
at the top of the wiki page, I see the 01112010.
Can you help me out? I really would like to use this plugin and understand plugin’s in general.
Thanks,
Faith
hi jamescarr,
Plug in very good.Thank you for providing such plugins. I want datetime format as “Wednesday, July 6, 2011 8:54:42 AM EDT”. I identified the date format as “EEEE,MMMM dd,yyyy HH:mm:ss z” But i don’t have any such sort of plug-in to support the above datetime format in fitnesse. Can you please help me. Is it possible to get the above format form the existing date format. I don’t think so but doubt about it.
Thanks in Advance James
Regards
gita
Unable to download this plugin.. has some 404 issue..