I'm trying to call something every 3 months (quarterly) in Quartz.NET (using both stable and latest version 2 which is beta with same results).
I create cron trigger with 0 30 8 3 */3 ? * to be called every 3 months at 8.30am on third of the month it occurs.
So technically since its 2 of September today I would expect it to trigger tomorrow. However it next run time shows as being next month. Why is that so?
Updated: As per answers I got I created following method - could be useful for someone:
public static string CalculateMonthsWithInterval(int startMonth, int interval)
{
var months = new List<string>();
var monthNames = new [] {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
var monthSelector = startMonth % interval;
for (var i = 0; i < 12; i++)
{
if (i % interval == monthSelector)
{
months.Add(monthNames[i]);
}
}
return string.Join(",", months.ToArray());
}
Ps: I didn't use indexes for months because for some reason it wasn't working well with my Quartz (v2 BETA). Also its easier to read in DB level.
Example call - Every 3 months based on startDate:
var cronMonths = CronUtils.CalculateMonthsWithInterval((startDate.Month - 1), 3);
Well I think that's because the scheduler will verify which month can be divided by 3, since all month in Quartz are based 0 (according to: http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/tutorial-lesson-06), the month that will be scheduled will be january, april, july and october.
0 mod 3 = 0 -> JAN
1 mod 3 = 1 -> FEB
...
8 mod 3 = 2 -> SEP
9 mod 3 = 0 -> OCT
The Quartz scheduler will analyse your cron expression and keep only those where their modulus 3 equals to 0.
If you want it to be 1 month before that (march, june, september and october) you will have to set it to:
0 30 8 3 MAR,JUN,SEP,DEC ? *
A good page to create cron expressions: http://www.cronmaker.com/
Cron format:
0 0 12 1 1/3 ? *
Executes every:
1. Saturday, April 1, 2017 12:00 PM
2. Saturday, July 1, 2017 12:00 PM
3. Sunday, October 1, 2017 12:00 PM
4. Monday, January 1, 2018 12:00 PM
5. Sunday, April 1, 2018 12:00 PM
Related
Hello so I have this condition:
if (DateTime.Now.Subtract(dateTimePicker_Doc_BirthDate.Value).Days/(365)<18)
{
this.errorProvider1.SetError(this.dateTimePicker_Doc_BirthDate, "Atleast 18 years old");
valid = false;
}
And I want to make another that contains graduation of this person and set restriction that at the time when he graduat must be atleast 18 years older.
I have tried this: but it does not work
if (DateTime.Now.Subtract(dateTimePicker_Doc_Graduation.Value).Days / (365) < 18 + (18))
{
this.errorProvider1.SetError(this.dateTimePicker_Doc_Graduation, "Atleast 18");
valid = false;
}
The main problem here is leap year. When a person born in 29 Feb 2004 will be 18 year old? There are two possible answers:
https://www.buzzfeed.com/lanesainty/leap-year-birthday-teenager-court-ruling
28 Feb 2022 (New Zealand for driving license; see xdtTransform's comment)
1 Mar 2022 (Australia)
In a simple case, we can just add 18 year to the birthDate and see if 18th birth day is before or after today. For 29 February we may well have to add 1 day as well:
DateTime birthDate = dateTimePicker_Doc_BirthDate.Value.Date;
DateTime today = DateTime.Today;
// 0 for 28 Feb 2022; 1 for 1 Mar 2022
int leapPolicy = 1;
if (birthDate.AddYears(18) <= today || // 18th birthday before today or today
birthDate.Day == 29 &&
birthDate.Day == 2 &&
birthDate.AddYears(18).AddDays(leapPolicy) == today) {
// At least 18 years old
}
Please, note, that year is not 365 but 365.2425 days (Gregorian calendar) that's why you can't put it as ...Days / 365...
I am wrapping NodaTime for use in our software, and to be future-proof I thought I'd set up IWeekYearRule according to Bcl DateTimeFormat, like this:
var culture = CultureInfo.CurrentCulture;
var weekRule = WeekYearRules.FromCalendarWeekRule(
culture.DateTimeFormat.CalendarWeekRule,
culture.DateTimeFormat.FirstDayOfWeek);
Here in Norway we're following the following settings:
CalendarWeekRule.FirstFourDayWeek
DayOfWeek.Monday
However, much to my surprise, when I checked the week number of the following two dates, kalender.no disagrees:
2018-12-31: week 53 of 2018 (kalender.no says week 1 [implied: of 2019])
2019-01-01: week 1 of 2019 (kalender.no agrees here)
However, if I use WeekYearRules.Iso, they both return "week 1 of 2019", which is the correct one.
I have a bunch of questions but let's focus on the most immediate one:
How can I write the code to "future-proof" this part of the library, so that when we start rolling out the software into more parts of the world, it will calculate week numbers correctly out of the box, according to the local culture?
Or is this just not possible, because the culture information doesn't contain this?
Here's a LINQPad example that demonstrates NodaTime behavior (sorry for not including a [mcve] to begin with):
void Main()
{
"-------------- ISO --------------".Dump();
Test(WeekYearRules.Iso);
"-------------- BCL --------------".Dump();
Test(WeekYearRules.FromCalendarWeekRule(CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday));
}
public static void Test(IWeekYearRule rule)
{
var date = new LocalDate(2018, 12, 31);
$"{date}, {rule.GetWeekOfWeekYear(date)} in {rule.GetWeekYear(date)}".Dump();
date = new LocalDate(2019, 1, 1);
$"{date}, {rule.GetWeekOfWeekYear(date)} in {rule.GetWeekYear(date)}".Dump();
}
Output:
-------------- ISO --------------
mandag 31. desember 2018, 1 in 2019
tirsdag 1. januar 2019, 1 in 2019
-------------- BCL --------------
mandag 31. desember 2018, 53 in 2018
tirsdag 1. januar 2019, 1 in 2019
(and just to be clear, the ISO output is the correct one for Norway)
This is NodaTime 2.4.4, Windows 10, .NET 4.7.2.
And here's a LINQPad example without using NodaTime:
void Main()
{
var culture = CultureInfo.GetCultureInfo("nb-NO");
var calendarWeekRule = culture.DateTimeFormat.CalendarWeekRule.Dump();
var firstDayOfWeek = culture.DateTimeFormat.FirstDayOfWeek.Dump();
var date = new DateTime(2018, 12, 31);
$"{date.DayOfWeek} {date}, week {culture.Calendar.GetWeekOfYear(date, calendarWeekRule, firstDayOfWeek)}".Dump();
date = new DateTime(2019, 1, 1);
$"{date.DayOfWeek} {date}, week {culture.Calendar.GetWeekOfYear(date, calendarWeekRule, firstDayOfWeek)}".Dump();
}
Output:
FirstFourDayWeek
Monday
Monday 31.12.2018 00.00.00, week 53
Tuesday 01.01.2019 00.00.00, week 1
The Monday there is in the same week as week 1 of 2019, and is counted as week 1 in 2019 here in Norway, so the first of the two dates should really have been:
Monday 31.12.2018 00.00.00, week 1
I have defined the following trigger for a job:
var postbagJobTrigger = (ICronTrigger)TriggerBuilder.Create()
.WithIdentity("cronJOBBER", "Campaigns")
.WithCronSchedule("0 13 9 1/5 * ? *")
.StartAt(new DateTime(2014, 12, 3))
.EndAt(new DateTime(2015, 3, 3))
.WithPriority(1)
.Build();
What I expect is that the first time of my trigger should be DateTime(2014, 12, 3). But when I see the firing schedule of this job, the first time is DateTime(2014, 12, 6). And when I add the StartNow() condition, the first time will be the time that meets the schedule starting now which is DateTime(2014, 10, 31). How can I tell the trigger to have its first run at my StartAt() time?
It seems ok to me.
The problem is in your CRON expression. This is the result for December:
8 Monday, December 1, 2014 9:13 AM
9 Saturday, December 6, 2014 9:13 AM
10 Thursday, December 11, 2014 9:13 AM
11 Tuesday, December 16, 2014 9:13 AM
12 Sunday, December 21, 2014 9:13 AM
13 Friday, December 26, 2014 9:13 AM
14 Wednesday, December 31, 2014 9:13 AM
You instructed Quartz.Net to start counting from December the 3rd.
You can check your expression with CronMaker.
It's better not to use DateTime. This is the definition for StartAt:
//
// Summary:
// Set the time the Trigger should start at - the trigger may or may not fire
// at this time - depending upon the schedule configured for the Trigger. However
// the Trigger will NOT fire before this time, regardless of the Trigger's schedule.
//
// Parameters:
// startTimeUtc:
// the start time for the Trigger.
//
// Returns:
// the updated TriggerBuilder
public TriggerBuilder StartAt(DateTimeOffset startTimeUtc);
You should use a DateTimeOffset. This article might help you.
UPDATE:
Quartz.Net uses your local TimeZone unless you specify a different one.
If you want to check what TimeZone your trigger is using:
((CronTriggerImpl)trigger).TimeZone
A better approach is to use DateBuilder:
DateTimeOffset dtNow = DateBuilder.DateOf(10, 32, 30, 28, 10, 2014);
which uses your local timezone.
If you want to achieve what you're asking you can do something like this:
ITrigger trigger = TriggerBuilder
.Create()
.WithIdentity("trigger1", "myGroup")
.StartAt(DateBuilder.DateOf(0, 0, 0, 3, 12, 2014))
.EndAt(DateBuilder.DateOf(0, 0, 0, 3, 3, 2015))
//.WithCronSchedule("0 0 0 1/5 * ? *")
.WithCalendarIntervalSchedule(f=>f.WithIntervalInDays(5))
.WithPriority(1)
.Build();
or
ITrigger trigger = TriggerBuilder
.Create()
.WithIdentity("trigger1", "myGroup")
.StartAt(DateBuilder.DateOf(0, 0, 0, 3, 12, 2014))
.EndAt(DateBuilder.DateOf(0, 0, 0, 3, 3, 2015))
// .WithCronSchedule("0 0 12 1/5 * ? *")
.WithSimpleSchedule(f=>f.WithIntervalInHours(24 * 5).RepeatForever())
.WithPriority(1)
.Build();
If you want to check your schedules call GetNextXFireTimes. This is the function :
private void GetNextXFireTimes(ITrigger trigger, int counts)
{
var dt = trigger.GetNextFireTimeUtc();
for (int i = 0; i < (counts-1); i++)
{
if (dt == null)
{
break;
}
Console.WriteLine(dt.Value.ToLocalTime());
dt = trigger.GetFireTimeAfter(dt);
}
}
This is the way Cron Expressions work:
* * * * *
- - - - -
| | | | |
| | | | +----- day of week (0 - 6) (Sunday=0)
| | | +------- month (1 - 12)
| | +--------- day of month (1 - 31)
| +----------- hour (0 - 23)
+------------- min (0 - 59)
I have a form that accept input of 'Month' and 'Year', I am writing here to ask any idea on how will I get all 'working weeks'?
I mean 'Working weeks' as Monday - Friday
So basically I need week1 to week4 or if available including week5.
For example if I input January 2013:
week1 = January 1 to January 4
week2 = January 7 to January 11
week3 = January 14 to January 18
week4 = January 21 to January 25
week5 = January 28 to January 31
How can I achieve that? Thanks for any help! Any suggestions or ideas will be greatly appreciated. Thanks mates! :)
You could use this Linq query:
int month = 1;
int year = 2013;
var cal = System.Globalization.CultureInfo.CurrentCulture.Calendar;
IEnumerable<int> daysInMonth = Enumerable.Range(1, cal.GetDaysInMonth(year, month));
List<Tuple<int, DateTime, DateTime>> listOfWorkWeeks = daysInMonth
.Select(day => new DateTime(year, month, day))
.GroupBy(d => cal.GetWeekOfYear(d, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday))
.Select(g => Tuple.Create(g.Key, g.First(), g.Last(d => d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)))
.ToList();
// Item1 = week in year, Item2 = first day, Item3 = last working day
int weekNum = 1;
foreach (var weekGroup in listOfWorkWeeks)
{
Console.WriteLine("Week{0} = {1} {2} to {1} {3}"
, weekNum++
, System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month)
, weekGroup.Item2.Day
, weekGroup.Item3.Day);
}
output for January:
Week1 = January 1 to January 4
Week2 = January 7 to January 11
Week3 = January 14 to January 18
Week4 = January 21 to January 25
Week5 = January 28 to January 31
and for February:
Week1 = February 1 to February 1
Week2 = February 4 to February 8
Week3 = February 11 to February 15
Week4 = February 18 to February 22
Week5 = February 25 to February 28
Find the first Monday of the month and year.
int year = 2013;
int month = 1;
DateTime testDate = new DateTime(year,month,1);
while ( testDate.DayOfWeek != DayOfWeek.Monday )
{
testDate = testDate.AddDays(1);
}
Then iterate over each week until you reach a year that isn't 2013.
// Should have first monday now.
// Loop until the month changes
while ( testDate.Month == month)
{
var monday = testDate;
var friday = testDate.AddDays(4);
// You now have both dates. Do whatever you need to.
// here.
// Increment test date by a week
testDate = testDate.AddDays(7)
}
In pseudo code:
Start from the first day of the month
While (day != Monday) take next date (+1 day)
See this SO post (Jon Skeet answer!) for checking if a day is a Monday.
Add 4 days to find the end of the working week
If it's in the same month, than you have a working week for your answer.
Add 7 days to find the next Monday
If the new Monday is still within the same month: repeat 3. and 4.
EDIT The above finds all complete working weeks.
To find the other weeks as well:
If the first day of the month is already a working day, find the next Friday. -> 1st working week
If the last friday is in the next month, finish the last working week at the last day of the month. -> last working week
i needed to get the correspondent trimester (periods of 3 months, being the first trimester Jan, Feb and Mar) of a given date. Using the c# System.DateTime struct I didn't manage to find a method for what I was looking for. So I solved like this:
DateTime sDeathDate = DateTime.Parse("18/09/1970");
int iDeathTrimester = Convert.ToInt32(Math.Round(Convert.ToDouble(sDeathDate.Month) / 3 + 0.25));
If someone knows a easier way to do this, please answer.
André
Assuming Jan - Mar is trimester 1, Apr - Jun is trimester 2, Jul - Sep is Trimester 3 and Oct - Dec is trimester 4, then you can use
int trimester = (sDeathDate.Month - 1) / 3 + 1
This is the same as a quarter, did you mean something different?
Math.Ceiling Returns the smallest integer greater than or equal to the specified number.
DateTime sDeathDate = DateTime.Parse("18/11/1970");
int trimester = (int)Math.Ceiling(sDeathDate.Month/3.0);
Note that the code use 3.0 to perform a floating point division and not an integer division
The Time Period Library for .NET includes the class Quarter (period of 3 months):
// ----------------------------------------------------------------------
public void QuarterOfDateSample()
{
DateTime moment = new DateTime( 1970, 9, 15 );
Console.WriteLine( "Quarter : {0}", new Quarter( moment ) );
// > Quarter : Q3 1970; 01.07.1970- 30.09.1970| 91.23:59
Console.WriteLine( "Quarter : {0}", new Quarter( 2006, YearQuarter.First ) );
// > Quarter : Q1 2006; 01.01.2006 - 31.03.2006 | 89.23:59
} // QuarterOfDateSample
If you use Math.Round (Double, MidpointRounding) you can do the round better without adding 0,25
See you