As part of a new Jira process I am developing, I need to ensure that the date stored in a certain custom field is in the future. I was using the “compare date and time” validator for this, checking to see that the value of the custom field was greater than or equal to that of the issue Created field. This was working well while I was using it on a standard workflow transition, but it bothered me that the user needed to create an issue and then immediately transition it in order for the validation to work. My users are a technical lot, but even they are likely to forget the second step of the process.
You can use validators on the Create Issue transition (if you can find it – go to the initial step of your workflow and look for “incoming transitions”), but the technique I was using doesn’t work, as the Created field isn’t updated until after the validator is processed and I was thus comparing with Null. I have Misc Workflow Extensions, Jira Suite Utilities and Jira Toolkit installed, but none provided a validator that would compare a date field with anything other than another date field. Luckily I also have the Jira Scripting Suite installed, and I thought this would be a trivial use case. Grab the custom field data using a jython one-liner and compare with the system date.
It wasn’t so simple. Jira returns date custom field values as java.sql.Timestamp objects, which are nontrivial to compare with jython date objects. I then tried using java.util.Calendar instead of the native jython datetime library, thinking that a java.util.Date would be more easily compared with a java.sql.Timestamp because they implement the same interface. This is technically true, but I didn’t want to compare instantaneous times, only dates, and there is no native method for extracting a date-only component (as a Julian date, say) from a java.util.Date object. One might expect to be able to obtain the raw timestamp data and discard the fractional day components (say, by performing an integer division), but this falls foul of time zones.
Jira treats dates as timestamps with zeroed time-of-day components, but the database backend stores this as milliseconds since the Epoch, and this is what is returned in the Timestamp object. A bare date is thus stored as the timestamp of the immediately previous local midnight. In the case of Ireland in summertime, this will cause an off-by-one error in the algorithm if one naïvely performs an integer division by the number of milliseconds in a day. To find the correct number of local days since the Epoch, one must add the current timezone offset to both timestamps before discarding fractional days. The resulting algorithm is thus far from the desired one-liner:
from com.atlassian.jira import ComponentManager from java.util import Calendar cfm = ComponentManager.getInstance().getCustomFieldManager() leavingDate = issue.getCustomFieldValue(cfm.getCustomFieldObject('customfield_10072')) today = Calendar.getInstance() tzoffset = today.getTimeZone().getOffset(today.getTimeInMillis()) leavingDateEpoch = (leavingDate.getTime()+tzoffset)//86400000 todayEpoch = (today.getTimeInMillis()+tzoffset)//86400000 if leavingDateEpoch < todayEpoch : result = False description = "Leaving date cannot be in the past" invalid_fields['customfield_10072'] = u"Leaving date cannot be in the past"
It works, but it’s damn ugly. I particularly love the double reference to the today object in line 7. How the Java standard library manages to be so verbose and still so lacking in basic functionality is beyond me. They actually seem to be going in the wrong direction – I found this wonderful piece of documentation on my travels:
int Date.getTimezoneOffset() Deprecated. As of JDK version 1.1, replaced by -(Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
Says it all, really.