How to test a Date against Cron Expression - c#

I need to test if a certain date matches a cron expression using Quartz.Net. I'm trying to get the below code to work, but fail.
// The data I'm testing is Dec 4, 2018 which is a Tuesday.
var date = new DateTimeOffset(2018, 12, 4, 0, 0, 0, TimeSpan.FromHours(0));
// Expression must define every monday, time is irrelevant
var expression = new CronExpression("0 0 0 ? * MON *");
// This returns 2018-12-09. But the next Monday after my date is 2018-12-10 !!
var next = expression.GetNextValidTimeAfter(date);
If you look at my comments, Quartz returns 2018-12-09 as the next Monday. But that is a Sunday...I don't understand why, perhaps misunderstanding the time-element - which I don't need at all.
Btw here's my expression from CronMaker:
So I guess the expression is ok.
Any hints?

Found out why. The CronExpression was missing the TimeZone.
So for example:
var date = new DateTimeOffset(2018, 12, 4, 0, 0, 0, TimeSpan.FromHours(0));
var expression = new CronExpression("0 0 0 ? * MON *") { TimeZone = TimeZoneInfo.Utc };
var next = expression.GetNextValidTimeAfter(date);
Will return correct for me now.

Related

TimeSpan calculation is different between TimeOnly and DateTime

I'm having a hard time understanding why this is happening or even if this should be happening. If I calculate the TimeSpan between two DateTime objects (same date, different times) and compare it to the same calculation using two TimeOnly objects I get different results.
var start = new DateTime(1, 1, 1, 14, 0, 0);
var end = new DateTime(1, 1, 1, 10, 0, 0);
Console.WriteLine(end - start); // Prints -4 hours
However...
var start = new TimeOnly(14, 0, 0);
var end = new TimeOnly(10, 0, 0);
Console.WriteLine(end - start); // Prints 20 hours???
Isn't the span between starting at 2pm and ending at 10am always a span of -4 hours? Interestingly enough if I take the second one and do Console.WriteLine(end.ToTimeSpan() - start.ToTimeSpan()); I end up with -4 hours.
This feels like an error on TimeOnly's part but I don't know. Here is a fiddle I did comparing results between NodaTime, System.DateTime, converting System.TimeOnly to TimeSpan, and System.TimeOnly.

Is there a discontinuity over year end in DateTime

I was hoping to write down a countdown timer for a New Year's party tomorrow, but I seem to have encountered an issue with subtracting two DateTimes from eachother.
My app seems to work fine when dealing with dates in January 2015 (the first method), but it gives strange results when I cross the 2014 / 2015 year end boundary (the second one).
Any ideas?
private static readonly string MyDateFormat = "{0:" + string.Join("", (-1).ToString("x").Skip(1)) + "}";
public void disKoud()
{
var later = new DateTime(2015-01-01);
var now = new DateTime(2015-01-31);
var diff = (later - now);
Debug.WriteLine(MyDateFormat, diff); // 0000030
}
public void sonderNHoed()
{
var later = new DateTime(2015-01-01);
var now = new DateTime(2014-12-31);
var diff = (later - now);
Debug.WriteLine(MyDateFormat, diff); // 0000042 ??? WTF
}
You're not constructing the dates you think you are. These lines:
var later = new DateTime(2015-01-01);
var now = new DateTime(2014-12-31);
are equivalent to:
var later = new DateTime(2013);
var now = new DateTime(1972);
After all, (2015 - 1) - 1 is 2013, and (2014 - 12) - 31 is 1972. That's just simple arithmetic.
Those values are both within the first second of January 1st 0001 AD... because you've passed a number of ticks.
You meant:
var later = new DateTime(2015, 01, 01);
var now = new DateTime(2014, 12, 31);
... which is passing the year, month and day as separate values into the DateTime constructor.
One thing that can help here is to use your debugger. If you mouse over the values, you will clearly see that they are actually in the year 0001. That's surely not what you were expecting!

Convert 12-hour time format to 24 hour integer?

In my app I have a drop-down box of strings that shows possible hours in 12-hour time for the user to select. The possible values are:
9am
10am
11am
12pm
1pm
2pm
3pm
4pm
5pm
What code will convert one of these strings to a 24 hour integer? For example, 10am should be converted to 10 and 4pm should be converted to 16.
You can use DateTime.Parse(...) to get a DateTime value, and then reference the .Hour property for a result;
int h = DateTime.Parse("10am").Hour; // == 10
int h2 = DateTime.Parse("10pm").Hour; // == 22
DateTime.Parse is pretty liberal in what it allows, but obviously makes some assumptions internally. For example, in the above code, DateTime.Parse("10am") returns 10am on the current date in the current timezone (I think...). So, be aware of the context in which you use the API.
If you have a dropdown, why not set the values to be the integer values you desire:
<asp:DropDownList runat="server" ID="hours">
<asp:ListItem Value="9">9am</asp:ListItem>
<asp:ListItem Value="10">10am</asp:ListItem>
<!-- etc. -->
<asp:ListItem Value="17">5pm</asp:ListItem>
</asp:DropDownList>
Considering the times are continuous, you can simplify the logic:
var firstHourStr = box.Items[0].ToString();
var firstHour = int.Parse(firstHourStr.Replace("am", "").Replace("pm", ""));
if (firstHourStr.Contains("pm"))
{
firstHour += 12;
}
var selectedHour = firstHour + box.SelectedIndex;
If the hours are static, and you know the first hour, you could have a const and simplify the process by much with var selectedHour = FIRST_HOUR + box.SelectedIndex.
Also, I assumed valid formats as shown in the question.
Final note: You'll need to handle the 12pm case which causes problems, due to the nature of the hour 12 being a single second after "am".
You could use DateTime.Parse, but that would not play nicely with internationalization.
int hour = DateTime.Parse(stringValue).Hour;
Instead, just use DateTime objects in the ComboBox and format them using FormatString:
// In Constructor:
cbHours.Items.Add(new DateTime(2000, 1, 1, 8, 0, 0));
cbHours.Items.Add(new DateTime(2000, 1, 1, 10, 0, 0));
cbHours.Items.Add(new DateTime(2000, 1, 1, 13, 0, 0));
cbHours.FormatString = "h tt";
// In event handler
if (cbHours.SelectedIndex >= 0)
{
int hour = ((DateTime)cbHours.SelectedItem).Hour
// do things with the hour
}

c# - Substracting two times returns negative value

I'm having problems when I'm trying to substract hr2 with hr1 in a specific situation, for example, when hr1 = 13:00 and hr2 = 15:00, ok, the result is 02:00.
But when the values are: hr1 = 22:00 and hr2 = 02:00, the result is 20:00.
The result should be 04:00.
TimeSpan ts1 = hr1.Subtract(hr2).Duration();
TextBox1.Text = ts1.ToString();
How can I solve this problem?
I understand what you want, but how you currently try to achieve it makes no sense. 22 hours minus 20 hours is 2 hours, which is correct.
You probably want this:
new DateTime(1, 1, 2, 2, 0, 0) - new DateTime(1, 1, 1, 22, 0, 0)
You don't want to subtract TimeSpan's, you want to subtract dates (fake dates in this case).
Invoking Duration() will always result in a positive TimeSpan. The problem is coming from the fact that you are discarding days in your calculation. 22:00-02:00 is 20:00. I believe you are expecting it to be 04:00 because 02:00 represents "tomorrow." If that is what you want, you will need to calculate 22:00-(02:00+24:00) which will give you -04:00, which will become 04:00 when you invoke Duration().
You are trying to subtract two "spans", or durations, of time--not fixed points in time. What your code is currently saying is, I want to subtract two hours from twenty hours (which is indeed twenty hours). Instead, you need to use DateTimes. The hard part is going to be deciding the date for your timespans. I would rework the code to use DateTimes and preserve the "moments" in time that you are actually attempting to calculate.
Edit: Converting from a TimeSpan to a DateTime can cause you to lose information that affects the outcome of the result:
var ts1 = new DateTime (1, 1, 1, hr1.Hours, hr1.Minutes, hr1.Seconds, hr1.Milliseconds) -
new DateTime (1, 1, 1, hr2.Hours, hr2.Minutes, hr2.Seconds, hr2.Milliseconds);
is different than:
var ts1 = new DateTime (1, 1, 1, hr1.Hours, hr1.Minutes, hr1.Seconds, hr1.Milliseconds) -
new DateTime (1, 1, 2, hr2.Hours, hr2.Minutes, hr2.Seconds, hr2.Milliseconds);
or:
var ts1 = new DateTime (1, 1, 2, hr1.Hours, hr1.Minutes, hr1.Seconds, hr1.Milliseconds) -
new DateTime (1, 1, 1, hr2.Hours, hr2.Minutes, hr2.Seconds, hr2.Milliseconds);
Which is why you need to maintain the "point in time" with a DateTime.

Comparing dates, looking for the next instance of a specific time

Based on my unit-test I'm trying to calculate when the next instance of 0400 hrs are and return this;
public void when_given_a_date_i_should_return_next_time_of_day_that_equals_04_hrs()
{
var dateTimeNow = new DateTime(2012, 6, 11, 14, 22, 0);
var dateTimeExpected = new DateTime(2012, 6, 12, 4, 0, 0);
Assert.AreEqual(dateTimeExpected, t.CalculateIncremental(dateTimeNow));
}
The point of CalculateIncremental is to publish a message with nservicebus every 24 hrs, at exactly 04:00 am using RequestUtcTimeout. This again to trigger some functionality.
Something like this:
private static DateTime GetNext4AM(DateTime input)
{
var result = new DateTime(input.Year, input.Month, input.Day, 4, 0, 0);
if (result > input)
{
return result;
}
else
{
return result.AddDays(1);
}
}
Should return the next occurrence of 4 AM, which can be on the same day (if the input is earlier than 04:00) or the next. However:
The point [..] is to publish a message with nservicebus every 24 hrs, at exactly 04:00 am using RequestUtcTimeout. This again to trigger some functionality.
You should use a scheduler for that, like Quartz.NET or simply the Windows Task Scheduler.
Check the hour of dateTimeNow, if it's less than 4, return its date and 4AM as the hour. If it's 4 or more, return the date of the next day, and 4AM as the hour. Decide what you want to do with 4:00:00 - either this day or the next one.
var now = new DateTime();
var dateTimeExpected = new DateTime(now.GetFullYear(),
now.GetMonth(), now.GetDay(), 4, 0, 0);
if (dateTimeExpected.CompareTo(now) > 0)
dateTimeExpected = dateTimeExpected.AddDays(1);

Categories

Resources