I am having trouble with DOS date formats. I need to convert:
From: 29th June of 2011 To: 16093
I know 16093 is the correct result, but how can I get this result?
I can convert the DOS date integer value to a recognized DateTime
but don't know how to reverse the process. This is how I do the conversion from DOS date to DateTime
:
var number = 16093;
var year = (number >> 9) + 1980;
var month = (number & 0x01e0) >> 5;
var day = number & 0x1F;
var date = new DateTime((int)year, (int)month, (int)day);
This works. Now I need to reverse it.
Let me teach you how to fish, and in the process fix your problem.
What do we have?
A DateTime
.NET object that represents a date/time combination.
DateTime dateTime = new DateTime(2011, 6, 29);
What do we want?
A DOS date/time according to the specification, but only with the 16-bit date component. The time component is ignored.
What do we need?
The day of the month, month number, and year since 1980. Let's get them as unsigned integers and ensure they are in the proper range.
uint day = (uint)dateTime.Day; // Between 1 and 31
uint month = (uint)dateTime.Month; // Between 1 and 12
uint years = (uint)(dateTime.Year - 1980); // From 1980
Note that there are 7 bits for the year, so it can represent years from 1980 up to 2107. Anything outside of that range is invalid. Since years
is unsigned, it can't represent negative values. But a value like 1970 (minus 1980) would then be 4294967286, so that it also out of range.
if (years > 127)
throw new ArgumentOutOfRangeException("Cannot represent the year.");
How do we combine it?
We need to shift all values to the appropriate spot in the resulting integer.
Start with an all-zero integer. We only need the lower 16 bits but for convenience we'll use an uint
here.
uint dosDateTime = 0;
The specification lists the bit indices of where each value starts. But since we're ignoring the time part (the 16 least significant bits), we need to subtract 16 from all those values.
Days start at bit 16. We shift it to the left by 16 - 16 positions (i.e. no shifting), and we can OR the bits to the result.
dosDateTime |= day << (16 - 16);
Months start at bit 21 (minus 16), and years at bit 25.
dosDateTime |= minutes << (21 - 16);
dosDateTime |= years << (25 - 16);
Then turn it into an unsigned 16-bit integer. It won't overflow.
ushort result = unchecked((ushort)dosDateTime);
How do we put it together?
Putting it all together as an extension method:
public static class DateTimeExtensions
{
public static ushort ToDosDateTime(this DateTime dateTime)
{
uint day = (uint)dateTime.Day; // Between 1 and 31
uint month = (uint)dateTime.Month; // Between 1 and 12
uint years = (uint)(dateTime.Year - 1980); // From 1980
if (years > 127)
throw new ArgumentOutOfRangeException("Cannot represent the year.");
uint dosDateTime = 0;
dosDateTime |= day << (16 - 16);
dosDateTime |= month << (21 - 16);
dosDateTime |= years << (25 - 16);
return unchecked((ushort)dosDateTime);
}
}
Of course you can write it more concise, but this shows clearly what's going on. And the compiler will optimize many of the constants.
Testing it
You should write some unit tests to see that your code works correctly. But this quick test verifies the basic idea:
static void Main(string[] args)
{
DateTime dateTime = new DateTime(2011, 6, 29);
ushort dosDateTime = dateTime.ToDosDateTime();
Console.WriteLine(dosDateTime); // Prints: 16093
Console.ReadLine();
}