A user reported a very strange issue - when a document with a date field is saved, it changes the value one day to the past. With every save. But only for some dates, not all. It turned out to be a mystery that goes deep into XPages and Notes/Java APIs.
I've posted a sample on OpenNTF Discord and Serdar tried it on his server - no issue. But he uses the GMT zone and I have CET (Windows set to UTC+1 - Amsterdam, Berlin... to be precise).
To cut it short, the issue is caused by daylight saving interpretation between Notes and Java. The date fields (because XPages have no notion of real date-only fields) are stored with 00:00 time component and for some dates the conversion back to Java Date resulted in 23:00 on the previous day. XPages that get the date component as String for the input field, which is then saved back as a previous day during document save.
The app is full of date fields and I couldn't add custom logic to every save operation, so I tried to fix it at XPages converter level. The problem actually happens before that as the Date object in the converter is already wrong, but if I could intercept had massage the value, the rest of the application can remain unaffected.
API test
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import lotus.domino.AgentBase;
import lotus.domino.AgentContext;
import lotus.domino.DateTime;
import lotus.domino.Session;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
SimpleDateFormat sdf2y = new SimpleDateFormat("dd.MM.yy");
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
Calendar cal = new GregorianCalendar(2024, 7, 21);
for (int i = 0; i < 50000; i++) {
// create a new date and let Notes decide about the DST
DateTime dt = session.createDateTime(sdf.format(cal.getTime()) + " 00:00:00");
Date date = dt.toJavaDate();
String notesDate = dt.toString().split(" ")[0];
String javaDate = sdf.format(date).split(" ")[0];
String javaDate2y = sdf2y.format(date).split(" ")[0];
if (!notesDate.equals(javaDate) && !notesDate.equals(javaDate2y)) {
System.out.println("Notes: " + dt);
System.out.println("Java: " + date);
}
cal.add(Calendar.DAY_OF_MONTH, -1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
On my machine it started to complain about dates starting 29.10.1995. Then the periods were different - sometimes just one month, later whole DST period.
15.02.2024 10:13:07 Agent Manager: Agent printing: Notes: 29.10.95 00:00:00 CEDT
15.02.2024 10:13:07 Agent Manager: Agent printing: Java: Sat Oct 28 23:00:00 CET 1995
15.02.2024 10:13:07 Agent Manager: Agent printing: Notes: 28.10.95 00:00:00 CEDT
15.02.2024 10:13:07 Agent Manager: Agent printing: Java: Fri Oct 27 23:00:00 CET 1995
15.02.2024 10:13:07 Agent Manager: Agent printing: Notes: 27.10.95 00:00:00 CEDT
15.02.2024 10:13:07 Agent Manager: Agent printing: Java: Thu Oct 26 23:00:00 CET 1995
So the problem is bigger that just a few problematic dates.
Fix routine
private static Date fixDate(Session s, Date origDate) {
Date res = origDate;
try {
DateTime dateTime = s.createDateTime(origDate);
Calendar c = Calendar.getInstance();
c.setTime(origDate);
DateTime calDateTime = s.createDateTime(c);
if (!StringUtil.equals(calDateTime.getDateOnly(), dateTime.getDateOnly())) {
int diff = dateTime.timeDifference(calDateTime);
c.add(Calendar.SECOND, -diff);
res = c.getTime();
}
} catch (NotesException e) {
e.printStackTrace();
}
return res;
}
Taking this to XPages
<xp:this.afterPageLoad>
<![CDATA[#{javascript:getComponent(compositeData.inputId).setConverter(
new com.pradny.domino.converter.CustomDateConverter()
);}]]>
</xp:this.afterPageLoad>
The converter itself just extends the standard one and uses a pattern defined in the app. It's designed just for this single scenario, so it can't be used for other formats, but it's fine as it's applied only to components in custom controls that use this format.
package comm.pradny.domino.converter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.convert.DateTimeConverter;
import com.ibm.xsp.extlib.util.ExtLibUtil;
import icrc.openLog.OpenLogItem;
import icrc.welcome.Settings;
import lotus.domino.DateTime;
import lotus.domino.NotesException;
import lotus.domino.Session;
public class CustomDateConverter extends DateTimeConverter {
private static final SimpleDateFormat formatter = new java.text.SimpleDateFormat(Settings.DATE_PATTERN);
public CustomDateConverter() {
super();
setPattern(Settings.DATE_PATTERN);
}
private Date fixDate(Session s, Date origDate) {
Date res = origDate;
try {
DateTime dateTime = s.createDateTime(origDate);
Calendar c = Calendar.getInstance();
c.setTime(origDate);
DateTime calDateTime = s.createDateTime(c);
if (!StringUtil.equals(calDateTime.getDateOnly(), dateTime.getDateOnly())) {
int diff = dateTime.timeDifference(calDateTime);
c.add(Calendar.SECOND, -diff);
res = c.getTime();
}
} catch (NotesException e) {
OpenLogItem.logError(e);
}
return res;
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value instanceof Date) {
Date origDate = fixDate(ExtLibUtil.getCurrentSession(), (Date) value);
String s = formatter.format(origDate);
return s;
} else {
throw new IllegalArgumentException("Value is not a java.util.Date");
}
}
}
Comments
Post a Comment