Ways to deal with Daylight Savings time with Quartz Cron Trigger - cron

I have a quartz cron trigger that looks like so:
<bean id="batchProcessCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="batchProcessJobDetail" />
<property name="cronExpression" value="0 30 2 * * ?" />
</bean>
How should I solve this, if I have several configurations that happen within the 2-3am period? Is there an accepted best practice?
Relevant link: http://www.quartz-scheduler.org/docs/faq.html#FAQ-daylightSavings
Basically it says "Deal with it." But my question is how!

I solved it using a separate trigger that only fires (an hour early) on the beginning date of DST for the configurations that happen between 2am and 3am Eastern.
Seems kludgey, but it works...

We are using the following solution. For this you will also need the joda time library.
public class MyCronExpression extends CronExpression
{
CronExpression _orgCronExpression;
public MyCronExpression(String cronExpression) throws ParseException
{
super(cronExpression);
setTimeZone(TimeZone.getTimeZone("UTC"));
_orgCronExpression = new CronExpression(cronExpression);
}
#Override
public Date getTimeAfter(Date date)
{
Date date1 = super.getTimeAfter(new Date(date.getTime()-date.getTimezoneOffset()*60*1000));
if (TimeZone.getDefault().inDaylightTime( date1 ) && !TimeZone.getDefault().inDaylightTime( date ))
{
DateTimeZone dtz = DateTimeZone.getDefault();
Date dstEnd = new Date(dtz.nextTransition(date.getTime()));
int dstEndHour = dstEnd.getHours();
int dstDuration = (dtz.getOffset(date1.getTime()) - dtz.getStandardOffset(date1.getTime()))/(60*60*1000);
int hour = date1.getHours()+date1.getTimezoneOffset()/60;
if (hour < dstEndHour && hour >= dstEndHour-dstDuration)
return dstEnd;
else
return _orgCronExpression.getTimeAfter(date);
}
else
return _orgCronExpression.getTimeAfter(date);
}
}
The class is used as follows:
CronTriggerImpl trigger = new CronTriggerImpl();
trigger.setCronExpression(new MyCronExpression("0 15 2 * * ?"));
Here some sample trigger times:
Tue Mar 25 02:15:00 CET 2014
Wed Mar 26 02:15:00 CET 2014
Thu Mar 27 02:15:00 CET 2014
Fri Mar 28 02:15:00 CET 2014
Sat Mar 29 02:15:00 CET 2014
**Sun Mar 30 03:00:00 CEST 2014**
Mon Mar 31 02:15:00 CEST 2014
Tue Apr 01 02:15:00 CEST 2014
Wed Apr 02 02:15:00 CEST 2014
Please post if you find any bugs/issues with this solution.

I took Ron's very interesting answer and improved the getTimeAfter Method, In order to adjust it to server GMT running and possible differences when scheduling 'Once a year' cron expressions.
#Override
public Date getTimeAfter(Date date) {
Date nextDate = super.getTimeAfter(date);
if(nextDate == null){
return null;
}
DateTime date1 = new DateTime(nextDate);
if (getTimeZone().inDaylightTime(date1.toDate()) && !getTimeZone().inDaylightTime(date)) {
DateTimeZone dtz = DateTimeZone.forTimeZone(getTimeZone());
DateTime dstEndDateTime = new DateTime(new Date(dtz.nextTransition(date.getTime())));
int dstEndHour = dstEndDateTime.getHourOfDay();
int dstDuration = (dtz.getOffset(date1.getMillis()) - dtz.getStandardOffset(date1.getMillis())) / (60 * 60 * 1000);
int hour = date1.getHourOfDay();
// Verifies if the scheduled hour is within a phantom hour (dissapears upon DST change)
if (hour < dstEndHour && hour >= dstEndHour-dstDuration){
// Verify if the date is a skip, otherwise it is a date in the future (like threads that run once a year)
if(dstEndDateTime.getDayOfYear() == date1.minusDays(1).getDayOfYear()){
return dstEndDateTime.toDate();
}else{
return nextDate;
}
}else{
return nextDate;
}
} else{
return nextDate;
}
}
Please note my server runs in GMT mode, therefore I do not use some of the offset conversions present in Ron's answer.
Also I discovered a Quartz bug, in which if you use the following configuration, it will fail because it is not capable of processing the cron expression correctly:
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
String cron = "0 15 2 8 3 ? 2015";
FailsafeCronExpression cronExpression = new FailsafeCronExpression(cron);
cronExpression.setTimeZone(DateTimeZone.forID("America/Vancouver"));
DateTime nextDate = new DateTime(cronExpression.getTimeAfter(sdf.parse("12/11/2014 10:15:00")));
This actually seems to happen because DST change takes place during 9th of March 2am for Vancouver and seems the Quartz internal implementation of the super.getTimeAfter(date) method will always send null.
I hope this information is useful.

I'm aware this question is quite old, but it still seems valid. I believe i've found a way to solve this problem, i'l leave it here in case someone else sumbles upon it and finds it handy
With spring 5.3 comes improved scheduling, rewritten using java.time API. It also supports quartz-specific extensions to cron expressions.
Example computation code:
public Instant calculateNextExecution(String cronExpression, Instant lastExecutionInstant, ZoneId executionZoneId) {
LocalDateTime lastExecutionDateTimeInExecutionZone lastExecutionInstant.atZone(executionZoneId)
.toLocalDateTime();
LocalDateTime nextExecutionDateInExecutionZone = CronExpression.parse(cronExpression).next(lastExecutionDateTimeInExecutionZone);
// skipped checking and handling nonexistant next execution
ZoneOffsetTransition transition = executionZoneId.getRules().getTransition(nextExecutionDateInExecutionZone);
if (transition == null) {
// next execution didn't occur during time transition
return nextExecutionDateInExecutionZone.atZone(executionZoneId)
.toInstant();
} else {
// next execution occured during time transition, one might check if transition was a gap or overlap and do sth with it
return doSthWithIt(transition, nextExecutionDateInExecutionZone);
}
}
Relevant spring class is org.springframework.scheduling.support.CronExpression.
Detailed description https://spring.io/blog/2020/11/10/new-in-spring-5-3-improved-cron-expressions
#update: Spring scheduler doesn't support years in cron expressions :( so it might not work in you scenario

Related

Execute a function every midnight in a timezone (CST) different than server timezone (UTC)

I have a time zone (timerTimeZone): For e.g. "America/Chicago".
let timerTimeZone = "America/Chicago"
Our server local time is in UTC.
I want to execute a function every night at 12.00 AM in the time zone which is stored in the timerTimeZone variable.
Let's say the program is running at 6.00 PM UTC/1.00 PM CST. So the first execution should be after 11 hours (12.00 AM CST) and next subsequent executions every 24 hours.
I have been trying to use moment and moment-timezone but not able to find any way.
I would suggest using the excellent Cron module.
This allows you to schedule tasks according to a cron expression, and also lets you specify an IANA timezone to use.
I'm also logging here the next 5 dates the job will run, both in the specified timezone and UTC.
const CronJob = require("cron").CronJob;
// Run at midnight every night
const cronExpression = "00 00 * * *";
const timeZone = "America/Chicago";
const cronJob = new CronJob(
cronExpression,
cronFunction,
null,
true,
timeZone
);
function cronFunction() {
console.log("cronFunction: Running....");
/* Do whatever you wish here... */
}
// Get the next N dates the job will fire on...
const nextDates = cronJob.nextDates(5);
console.log(`Next times (${timeZone}) the job will run on:`, nextDates.map(d => d.tz(timeZone).format("YYYY-MM-DD HH:mm")));
console.log("Next times (UTC) the job will run on:", nextDates.map(d => d.utc().format("YYYY-MM-DD HH:mm")));

Momentjs get time in current location

I'm trying to generate a momentjs object of a certain timestamp in the current day of a specified location. For example:
const timeNow = moment().tz('Africa/Cairo')
const startTime = moment('10:00 am', 'HH:mm a')
const endTime = moment('2:30 pm', 'HH:mm a')
Printing the above 3 variables outputs this:
Fri, 12:31 am
Thu, 10:00 am
Thu, 02:30 pm
Where the first result is in fact the current time in Cairo, However the other two results are the day before. How can I change it so that they return the current day?
You can simply do:
moment.tz('Africa/Cairo') // <= Moment Object
One small info: whenever you'll get to see some javascript date in a browser that will be shown in your system's time-zone. As javascript Date is UTC, browsers will show accordingly. Use moment.format() to get string values.

How to convert Azure date into friendly date?

In azure I am using an api, and I get back this in the json response.
Date(1533024552000)
Does anyone know how to convert that into a regular date like July 2 2018 for example?
Thanks
You can use UnixDateTimeConverter class. Converts a DateTime object to and from JSON. DateTime is represented as the total number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
public class AzureResponse
{
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime Date;
}
static void Main(string[] args)
{
AzureResponse input = new AzureResponse() { Date = new DateTime(2018,7,31,10,09,12)};
string output = JsonConvert.SerializeObject(input);
// "{\"Date\":1533031752}"
AzureResponse readBack = JsonConvert.DeserializeObject<AzureResponse>(output);
// Date = {31.07.2018 10:09:12}
}
Epoch, also known as Unix timestamps, is the number of seconds (not milliseconds!) that have elapsed since January 1, 1970 at 00:00:00 GMT (1970-01-01 00:00:00 GMT). https://www.freeformatter.com/epoch-timestamp-to-date-converter.html

Format the time from moment js

When I am trying to receive mail from gmail, I get time in this format (Mon, 12 Jun 2017 10:29:07 +0530). I want to calculate time minus current time. How to do so?
var testDate = moment("Mon, 12 Jun 2017 10:29:07 +0530");
//Relative to time in human readble format
testDate.fromNow(); //3 days ago
You can simply call the moment constructor with the given Date format. moment.js is smart enough to parse it for you. To get the difference you can convert it into unix based time format and subtract it.
const givenTime = moment("Mon, 12 Jun 2017 10:29:07 +0530").unix()
const currentTime = moment().unix()
//Difference in milliseconds
const diff = givenTime - currentTime

TimeCategory add minutes using vars

How do i add minutes to currentDate.
I might add more than 1440 minutes..
def AddMinutes = 1445
currentDate = new Date();
println currentDate
use( TimeCategory ) {
NewCurrentDate = currentDate + AddMinutes.minutes // fails
NewCurrentDate = currentDate + 1445.minutes // works
}
println currentDate
Tue Feb 23 15:09:13 CET 2016
Wed Feb 24 15:14:13 CET 2016
Works for me... Can't see your problem apart from you're not printing out the newCurrentDate (PS: Lower case letters for variable names, otherwise Groovy can get confused, and think you're on about classes -- but that's not the issue here)
import groovy.time.*
def addMinutes = 1445
currentDate = new Date()
use( TimeCategory ) {
newCurrentDate = currentDate + addMinutes.minutes
}
println currentDate //Tue Feb 23 14:45:52 GMT 2016
println newCurrentDate //Wed Feb 24 14:50:52 GMT 2016
I found out that I needed to make sure that the addMinutes was an int. So I added this before in my script:
addMinutes = addMinutes.toInteger()
and now it works.

Resources