I ran into interesting issue with the following requirement:
Test if a process had run in the same day, if not run the process. The dates are stored as DataTimeOffset.
My original approach was to:
Convert both values to UTC, because these dates could have been created in different time zones and have different offsets.
View the Date value of each value. This is done after converting to UTC because the Date method ignores the offset.
Most scenarios this worked but I came across one case that the logic would fail. If one of the values had a time that was close to the previous/next day so that the when converting to UTC it would change the date. If the other value didn't have a time that also converted to the previous/next day then the date comparison failed.
So I ended up with the following logic to include that scenario:
public static bool SameDate(DateTimeOffset first, DateTimeOffset second)
{
bool returnValue = false;
DateTime firstAdjusted = first.ToUniversalTime().Date;
DateTime secondAdjusted = second.ToUniversalTime().Date;
// If date is now a day ahead after conversion, than add/deduct a day to other date if that date hasn't advanced
if (first.Date < firstAdjusted.Date && second.Date == secondAdjusted.Date)
secondAdjusted = secondAdjusted.Date.AddDays(1);
if (first.Date > firstAdjusted.Date && second.Date == secondAdjusted.Date)
secondAdjusted = secondAdjusted.Date.AddDays(-1);
if (second.Date < secondAdjusted.Date && first.Date == firstAdjusted.Date)
firstAdjusted = firstAdjusted.Date.AddDays(1);
if (second.Date > secondAdjusted.Date && first.Date == firstAdjusted.Date)
firstAdjusted = firstAdjusted.Date.AddDays(-1);
if (DateTime.Compare(firstAdjusted, secondAdjusted) == 0)
returnValue = true;
return returnValue;
}
Here is the Unit Tests that were failing that now pass:
[TestMethod()]
public void SameDateTest()
{
DateTimeOffset current = DateTimeOffset.Now;
DateTimeOffset first = current;
DateTimeOffset second = current;
// 23 hours later, next day, with negative offset (EST) -- First rolls over
first = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0));
second = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
// 23 hours earlier, next day, with postive offset -- First rollovers
first = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0));
second = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
// 23 hours later, next day, with negative offset (EST) -- Second rolls over
first = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0));
second = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
// 23 hours earlier, next day, with postive offset -- Second rolls over
first = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0));
second = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0));
Assert.IsFalse(Common.SameDate(first, second));
}
My gut feeling is that there is a cleaner approach than to increment/decrement based on the other value. Is there a better approach?
The primary criteria:
Adjust the both dates to have the same offset.
Return true only if both first and second dates occur in the same calendar day, not within 24 hours.
Adjust the one of the dates for the difference in both dates:
public static bool SameDate(DateTimeOffset first, DateTimeOffset second)
{
bool returnValue = false;
DateTime firstAdjusted = first.ToUniversalTime().Date;
DateTime secondAdjusted = second.ToUniversalTime().Date;
// calculate the total diference between the dates
int diff = first.Date.CompareTo(firstAdjusted) - second.Date.CompareTo(secondAdjusted);
// the firstAdjusted date is corected for the difference in BOTH dates.
firstAdjusted = firstAdjusted.AddDays(diff);
if (DateTime.Compare(firstAdjusted, secondAdjusted) == 0)
returnValue = true;
return returnValue;
}
In this function I am asuming that the offset will never be more than 24 hours. IE the difference between a date and it's adjusted date will not be two or more days. If this is not the case, then you can use time span comparison.
The general methodology you describe (convert to common time-zone then compare date portion) is reasonable. The problem here is actually one of deciding on the frame of reference. You have arbitrarily chosen UTC as your frame of reference. At first gloss it doesn't matter so long as they are compared in the same time zone, but as you have found this can put them on either side of a day boundary.
I think you need to refine your specification. Ask yourself which of the following you are trying to determine.
Whether the values occur on the same calendar day for a specified time zone.
Whether the values are no more than 12 hours apart (+/- 12hrs is a 24hr period).
Whether the values are no more than 24 hours apart.
It might also be something else. The definition as implemented (but rejected by you) is "Whether the values occur on the same UTC calendar day".
First of all, you need to clear up some confusion what the program should do exactly. For two general timestamps in two general time zones (two DateTimeOffset instances without specific limitations), there is no such concept as “the calendar day”. Each time zone has its own calendar day. For instance, we could have two instances of DateTimeOffset, named first and second, and they have different offsets. Let’s visualize the time axis, and mark the specific time instants to which the DateTimeOffset instances refer with * and the calendar day in the respective time zone (i.e. the interval between 0:00 and 23:59 in the specific timezone) with |__|. It could look like this:
first: ........|___________________*__|.......
second: ...|______*_______________|............
When in the timezone of first, the second event happened during the same calendar day (between 2–3 am). When in the timezone of second, the first event happened during the following calendar day (between 1–2 am).
So it is obvious the question needs clarification and probably a bit of scope limitation. Are those really generic timezones, or are they timezones of the same place, differing potentially only in the daylight saving time? In that case, why don’t you just ignore the timezone? E.g. it does not matter that on November 2nd 2014, between 00:10 and 23:50, the UTC offset has changed (EDT->ET) and the two instants are separated by more than 24 hrs of time: new DateTimeOffset(2014, 11, 02, 00, 10, 00, new TimeSpan(-4, 0, 0)).Date == new DateTimeOffset(2014, 11, 02, 23, 50, 00, new TimeSpan(-5, 0, 0)).Date. Basically, this is what martijn tries to do, but in a very complicated way. When you would try just
public static bool SameDateSimple(DateTimeOffset first, DateTimeOffset second)
{
return first.Date == second.Date;
}
it would work for all your abovementioned unit tests. And, also, this is what most humans would call “the same calendar day” when it is guaranteed the two instances refer to times at a single place.
Or, if you are really comparing two “random” timezones, you have to choose your reference timezone. It could be UTC as you tried initially. Or, it might be more logical from a human standpoint to use the first timezone as the reference (you could also choose the second one, it would give different results, but both variants are “equally good”):
public static bool SameDateGeneral(DateTimeOffset first, DateTimeOffset second)
{
DateTime secondAdjusted = second.ToOffset(first.Offset).Date;
return first.Date == secondAdjusted.Date;
}
This does not work for some of the abovementioned tests, but is more general in the sense it works “correctly” (in some sense) for two random timezones: If you try first = new DateTimeOffset(2014, 1, 2, 0, 30, 0, new TimeSpan(5, 0, 0)), second = new DateTimeOffset(2014, 1, 1, 23, 30, 0, new TimeSpan(4, 0, 0)), the simple SameDateSimple returns false (as does martijn’s), even though these two instances refer to the exact same moment in time (both are 2014-01-01 19:30:00Z). SameDateGeneral returns true here correctly.
First off, you have an error in your UnitTest.
[TestMethod()]
public void SameDateTest()
{
DateTimeOffset current = DateTimeOffset.Now;
DateTimeOffset first = current;
DateTimeOffset second = current;
// 23 hours later, next day, with negative offset (EST) -- First rolls over
first = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
// 23 hours earlier, next day, with positive offset -- First rollovers
first = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsFalse( DateTimeComparison.Program.SameDate( first, second ) );
// 23 hours later, next day, with negative offset (EST) -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
// 23 hours earlier, next day, with positive offset -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsFalse( DateTimeComparison.Program.SameDate( first, second ) );
}
This is the corrected test. Your first test should return "True", as should your third posted tests. Those DateTimeOffsets being compared are on the same UTC date. Only test case two and four should return "False", as those DateTimeOffsets are in fact on 2 different dates.
Second, you can simplify your SameDate() function to this:
public static bool SameDate( DateTimeOffset first, DateTimeOffset second )
{
bool returnValue = false;
DateTime firstAdjusted = first.UtcDateTime;
DateTime secondAdjusted = second.UtcDateTime;
if( firstAdjusted.Date == secondAdjusted.Date )
returnValue = true;
return returnValue;
}
As all you are interested in is if first.Date and second.Date are actually on the same UTC date, this will get the job done without an extra cast/conversion to UTC.
Third, you can test your test cases using this complete program:
using System;
namespace DateTimeComparison
{
public class Program
{
static void Main( string[] args )
{
DateTimeOffset current = DateTimeOffset.Now;
DateTimeOffset first = current;
DateTimeOffset second = current;
// 23 hours later, next day, with negative offset (EST) -- First rolls over
first = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
// --Comment is wrong -- 23 hours earlier, next day, with positive offset -- First rollovers
first = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
// 23 hours later, next day, with negative offset (EST) -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 18, 0, 0, new TimeSpan( -5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 19, 0, 0, new TimeSpan( -5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
// --Comment is wrong -- 23 hours earlier, next day, with positive offset -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
if( false == SameDate( first, second ) ) {
Console.WriteLine( "Different day values!" );
} else {
Console.WriteLine( "Same day value!" );
}
}
public static bool SameDate( DateTimeOffset first, DateTimeOffset second )
{
bool returnValue = false;
DateTime firstAdjusted = first.UtcDateTime;
DateTime secondAdjusted = second.UtcDateTime;
if( firstAdjusted.Date == secondAdjusted.Date )
returnValue = true;
return returnValue;
}
}
}
Set a break point wherever you like and run this short program in the debugger. This will show you that test case 2 and test case 4 are in fact more than 2 days apart by UTC time and therefore should expect false. Furthermore, it will show test case 1 and test case 3 are on the same UTC date and should expect true from a properly functioning SameDate().
If you want your second and fourth test cases to be 23 hours apart on the same date, then for test case two, you should use:
// 23 hours earlier, next day, with positive offset -- First rollovers
first = new DateTimeOffset( 2014, 1, 2, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 1, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
And for test case four, you should use:
// 23 hours earlier, next day, with positive offset -- Second rolls over
first = new DateTimeOffset( 2014, 1, 2, 5, 0, 0, new TimeSpan( 5, 0, 0 ) );
second = new DateTimeOffset( 2014, 1, 3, 4, 0, 0, new TimeSpan( 5, 0, 0 ) );
Assert.IsTrue( DateTimeComparison.Program.SameDate( first, second ) );
What about this function:
public static bool SameDate(DateTimeOffset first, DateTimeOffset second)
{
return Math.Abs((first - second).TotalDays) < 1;
}
You can substract two dates (DateTimeOffset is smart and knows the timezone) and it will give you a range, a timespan. Then you can check if this range is +- 1 day.
This question is somewhat extension of a question asked previously c# using marshalling for packet parsing by me.
I have to parse a variable size packet although header size is fixed but data packets inside it can be of different size and may be of more than 1 type are present in same packet.
For example the packet has following fields in its header :
1) username(12 bytes)
2 password(12 bytes)
3) id_number(4 bytes)
4) may be 1 or combination of other data packets of variable size(size can be 12, 16 or 512 bytes)
5) crc(2 bytes)
Now data packets can be following
a) data packet type 1
1) size(2 bytes)
2) name(12 bytes)
3) id_number(2 bytes)
b) data packet type 2
1) size(2 bytes)
2) data(24 bytes)
3) id_number(1 byte).
So there can be either type1 or type2. It is also possible for both type to be present. My question is how can I use marshalling to parse these packets or anyone can suggest some other way.
One more thing I want to add is that 1st and 3rd field of data packets will always be the data packet size(2 bytes) and data packet id number(1 byte) respectively. The 2nd field of data packets can be anything and of variable size(2, 3, 13, 18, 515).
As an alternative, you may use LINQ (assuming that ASCII encoding is being used):
var packet = new byte[]{
97, 108, 101, 120, 0, 0, 0, 0, 0, 0, 0, 0, // username
112, 97, 115, 115, 119, 111, 114, 100, 0, 0, 0, 0, //password
49, 50, 51, 0, // id_number
0, 53, 0, 0, 1, // 1st data packet
0, 54, 1, 2, 5, 2, // 2nd data packet
49, 0 // crc
};
var username = Encoding.ASCII.GetString(packet.Take(12).ToArray());
var password = Encoding.ASCII.GetString(packet.Skip(12).Take(12).ToArray());
var idNumber = Encoding.ASCII.GetString(packet.Skip(24).Take(4).ToArray());
var data = packet.Skip(28).Take(packet.Length - 30).ToArray();
var crc = Encoding.ASCII.GetString(packet.Skip(packet.Length - 2).ToArray());
var nextDataPackedPos = 0;
var nextDataPackedPos = 0;
var dataPackets = data
.TakeWhile(b => nextDataPackedPos < data.Length)
.Zip(data.Skip(nextDataPackedPos), (a, b) =>
{
var size = Int32.Parse(
Encoding.ASCII
.GetString(data.Skip(nextDataPackedPos).Take(2).ToArray())
.Trim('\0')
);
var result = data.Skip(nextDataPackedPos).Take(size).ToArray();
nextDataPackedPos += size;
return result;
}).ToList();
The code first separates the data section from the packet bytes. Then it reads the size of each packet and based on it, it creates an equaly sized array containing the bytes of the data packet. It hen advances to the beginning of the next packet until the end of the array is reached.
I should implement a MAC-CBC generation method in C# with some information about the cryptography algorithm. Here's what I have:
I should use DES.
The key is byte[] {11, 11, 11, 11, 11, 11, 11, 11}
The data (16 bytes) should be encrypted in 8-byte parts. First 8 bytes is encrypted using Instance Vector = new byte[8] (8 bytes with 0 value). (CBC?)
that last 8 bytes of the encrypted value should be converted to Hex string. this is the result I should send.
With this information, I have implemented the following method:
public static string Encrypt(byte[] data)
{
var IV = new byte[8];
var key = new byte[] { 11, 11, 11, 11, 11, 11, 11, 11 };
var result = new byte[16];
// Create DES and encrypt.
var des = DES.Create();
des.Key = key;
des.IV = IV;
des.Padding = PaddingMode.None;
des.Mode = CipherMode.CBC;
ICryptoTransform cryptoTransform = des.CreateEncryptor(key, IV);
cryptoTransform.TransformBlock(data, 0, 16, result, 0);
// Get the last eight bytes of the encrypted data.
var lastEightBytes = new byte[8];
Array.Copy(result, 8, lastEightBytes, 0, 8);
// Convert to hex.
var hexResult = string.Empty;
foreach (byte ascii in lastEightBytes)
{
int n = (int)ascii;
hexResult += n.ToString("X").PadLeft(2, '0');
}
return hexResult;
}
The sample raw data they have provided me is: input=byte[] {0, 6, 4, 1, 6, 4, 1, 7, E, E, F, F, F, F, B, B) which should return the output of value: A7CBFB3C730B059C. This means the last eight bytes of encrypted data should be: byte[] {167, 203, 251, 60, 115, 11, 05, 156}.
But unfortunately using the above method, I get: 32D91200D0007632. meaning my encrypted data is not correct. (the last eight byte of my method's generated encrypted value is byte[] {50, 207, 18, 0, 208, 0, 118, 50}).
Is there any way that I can find out what I should do to get to A7CB...? Am I doing something wrong?
CBC-MAC requires a zero Initialisation Vector. Much better to specify the IV explicitly:
var IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
You say your key is byte[] { 11, 11, 11, 11, 11, 11, 11, 11 } are those bytes in hex or in base 10? You might want to try:
var key = new byte[] { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 };
and see if that works better.
The Mono project has a generic MAC-CBC implementation that should work on any SymmetricAlgorithm - even if it's used, internally, only to implement MACTripleDES.
You can find the MIT.X11 licensed source code here. Use it as-is or compare it to your own code.