-2

So I think there is a simple answer to this problem. Essentially I am getting a decimal storage of a DateTime from a database that is in essence '9062017'. I was wanting to create a decimal extension method to account for decimals and parsing and do variable formats for an extensible DateTime. I was playing around with the logic, yet it keeps not working. I was looking up a few threads on SO like this: Parse datetime in multiple formats. But it's not explaining what I think would just work and does not.

static void Main(string[] args)
{
  DateTime d = new DateTime(2017, 9, 6);
  //Cool what I would expect to show the day position with two digits and the month uses one if needed else it will go to two if needed.
  Console.WriteLine(d.ToString("Mddyyyy"));

  //I can parse out an int and see it is exactly as above.
  int i = Int32.Parse(d.ToString("Mddyyyy"));
  Console.WriteLine(i.ToString());

  DateTime dt;
  string[] formats = { "Mddyyyy", "MMddyyyy" };
  //Keeps default min value of DateTime and ignores parsing regardless of formats done in the first arg changes, the seconds formats to apply, or cultural changes.
  DateTime.TryParseExact(i.ToString(), formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);

  Console.WriteLine(dt);


  //Console.WriteLine(i);
  Console.ReadLine();
}
djangojazz
  • 14,131
  • 10
  • 56
  • 94
  • 1
    Firstly, I'd **very strongly** recommend against storing a DateTime like that - *particuarly* in month/day/year format, which isn't even sortable. If you really, really want to do it, I'd get rid of the string parts entirely - do *all* the work directly in integers. – Jon Skeet Sep 06 '17 at 17:02
  • @JonSkeet I agree. Unfortunately it is a third party ultimately I don't maintain their code base I am just getting it and trying to better form it. – djangojazz Sep 06 '17 at 17:04
  • You may have packed decimal date. See following : https://www.ibm.com/support/knowledgecenter/en/SS6SGM_3.1.0/com.ibm.aix.cbl.doc/PGandLR/concepts/cpari09.htm – jdweng Sep 06 '17 at 17:05
  • What database are you storing to? Does it have a native date/time format? The kind of date/time logic that you're doing, is something that we try to never deal with, since ADO.NET usually handles it for us... – code4life Sep 06 '17 at 17:05
  • 2
    Then as I say, I'd completely remove the string aspects and do it all with arithmetic. Basically, `value = year + (day * 10000) + (month * 10000000)`. Reverse the operation to go from integer to date. – Jon Skeet Sep 06 '17 at 17:05
  • 1
    @jdweng: I'm not sure how you expect that article to help... – Jon Skeet Sep 06 '17 at 17:06
  • Just an explanation of what packed decimal format is. Regular parsing will not work. – jdweng Sep 06 '17 at 17:08
  • Given that the decimal is of predictable length and follows a consistent convention, the (ch)easiest way to do this might be to cast to a string, and pluck out the parts you need, to formulate a new `DateTime(yyyy, mm, dd)` out of it. – code4life Sep 06 '17 at 17:08
  • 1
    @jdweng: That article isn't even about a date representation. There's no indication that the OP is using "packed decimal" or anything similar - they're just trying to convert a date to an integer and back. – Jon Skeet Sep 06 '17 at 17:12
  • @Jon Skeet Yeah I was thinking about something like that and have not gone down that road yet. Would you be opposed to adding the leading zero that was stripped out? EG: Instead of "9062017" that comes from the integer to string I would just do: $"0{i.toString()}". Ultimately the code is just a playground for an extension method I will create once I get the business logic I want. – djangojazz Sep 06 '17 at 17:12
  • @code4life: The OP is already trying to do this via a string representation, but I don't see why that would be better than simple arithmetic. – Jon Skeet Sep 06 '17 at 17:13
  • 1
    @djangojazz: Yes, I would be opposed to that, because it would fail for larger values, and it's still doing completely pointless (and error-prone, as you've seen) string manipulation. – Jon Skeet Sep 06 '17 at 17:13
  • Why do you want to create a "generic extension method" if all you need is a specific / non-generic one to address this? – stephen.vakil Sep 06 '17 at 17:16
  • @stephen.vakil Reuse. Maybe generic only in the sense that the 'format' incoming will be variable but really it is always a DateTime return. So I will amend what I wrote. – djangojazz Sep 06 '17 at 17:17
  • @JonSkeet: it's this line that I'm talking about: `DateTime.TryParseExact(i.ToString(), formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);` – code4life Sep 06 '17 at 17:18
  • @code4life: I still don't see what you're suggesting, or why the OP would want to use strings at all. – Jon Skeet Sep 06 '17 at 17:20
  • It is somewhat obvious what "9062017" should mean. But what is "1112017"? Could be Jan 11, could be Nov 1st. If this is taken care of by using 8 digit strings when there is a risk of ambiguity then you need PadLeft(8, '0') to make the string parseable. – Hans Passant Sep 06 '17 at 17:32
  • @HansPassant I agree, but I think that is why in tandom you offer the decimal a manner in which to format itself. If I tell it "Mddyyyy" I would expect it to understand how to do decode versus just making assumptions on what each character was. It sounds like from what others have listed this is going to be a mess to create to make it more dynamic and I thought it was something simple I was just missing in the syntax. – djangojazz Sep 06 '17 at 17:41
  • As a side note, you keep using "decimal" to mean "integer" - given the C# context, that's a bit misleading, given `System.Decimal`... – Jon Skeet Sep 06 '17 at 17:43
  • Using PadLeft is simple. Not that much point in coming up with reasons to not use it, it is *required* to make ParseExact work. And don't forget to double-check that the programmer that wrote this junk got it right, you'll need to send that email asap when his code needs to be fixed. – Hans Passant Sep 06 '17 at 17:51
  • @HansPassant: My reason not to use it would be that ParseExact isn't required to start with :) – Jon Skeet Sep 06 '17 at 18:08

2 Answers2

2

I think you should avoid the string conversion entirely. It's pointless and error prone. It's much simpler just to do the maths:

public static DateTime Int32ToDateTime(int value)
{
    int year = value % 10000;
    int day = (value / 10000) % 100;
    int month = value / 1000000;
    // Specify whatever kind is appropriate; it's unclear from the question.
    return new DateTime(year, month, day);
}

public static int DateTimeToInt32(DateTime date) =>
    date.Year + (date.Day * 10000) + (date.Month * 1000000);

I wouldn't use an extension method for this. It's not appropriate for all integers. You should be really, really clear when you're doing this. If you have multiple numeric formats, you could have an interface an multiple implementations:

public interface IDateTimeInt32Converter
{
    DateTime Int32ToDateTime(int value);
    int Int32ToDateTime(DateTime date);
}

public class YearMonthDayConverter : IDateTimeInt32Converter
{
    // etc
}

// Ditto for MonthDayYearConverter and DayMonthYearConverter
djangojazz
  • 14,131
  • 10
  • 56
  • 94
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I agree with your manner in which you are doing this. However I want to be able to do multiple formats as I have dealt with third parties that do this "Mddyyyy", "Mdyy", and the more welcomed "yyyyMMdd". I was still playing with it but was thinking of finding the text and position in a string for doing the remainder division you are doing. I just want those remainder positions to change based on where it finds text for year based on length of format. – djangojazz Sep 06 '17 at 17:48
  • 1
    @djangojazz: "Mdyy" is simply a broken format, due to ambiguity. You've got no chance with that. I find it strange that you have multiple third parties taking the same bad approach (storing a date as an integer) but in different ways. If that's *really, really* the case, then you could always create an interface with three different implementations which perform the arithmetic three different ways. I'd still take that over string manipulation when neither the source nor the target of the conversion is really a string. – Jon Skeet Sep 06 '17 at 17:50
  • ' I'd still take that over string manipulation when neither the source nor the target of the conversion is really a string'. You just really piqued my curiosity when you changed your answer to use an interface with multiple implementations and avoid an extension methods. I am just a lazy dev at heart and like the laziness of doing (some int).GetADate("Mddyyyy"); It really is about me just not wanting to type out an extensive code base but if down the road it will burn me then I can see your point. – djangojazz Sep 06 '17 at 17:55
  • @djangojazz: This would be about 50 lines of code, tops. Hardly an "extensive code base", but much clearer than the extension method, IMO. – Jon Skeet Sep 06 '17 at 17:56
  • depends on how lazy I am that day, but I have already typed a ton on this so it was ironic in the first place I thought this would be an easy line or two change in the DateTime.TryParseExact or similar. Thanks for the help. – djangojazz Sep 06 '17 at 17:57
0

I think you should veer away from TryParseExact since it's going to be a trip down the garden path, in your case (as in, expect to write a lot more code than you've done so far).

A simpler, possibly easier approach, is to simply parse out the string representation of the decimal value.

Here's a sample to illustrate what I mean:

var d = 9062017.0;
var s = d.ToString();

if (s.IndexOf(".") > -1)
    s = s.Substring(0, s.IndexOf("."));

string mm = "", dd = "", yy = "";

// single-digit month?
if (s.Length == 7)
{
    mm = s.Substring(0, 1);
    dd = s.Substring(1, 2);
    yy = s.Substring(3);    
}

if (s.Length == 8)
{
    mm = s.Substring(0, 2);
    dd = s.Substring(3, 2);
    yy = s.Substring(4);
}

// rather than parse directly to DateTime, just parse
// the individual date portions as ints...
var year = Convert.ToInt32(yy);
var month = Convert.ToInt32(mm);
var day = Convert.ToInt32(dd);

// transform into the date...
var dt = new DateTime(year, month, day);
code4life
  • 15,655
  • 7
  • 50
  • 82
  • You are assuming absolute positioning in your example. If I was to go this route I would have to make a ton of code to account for each specific format verbatim. My question ultimately has this part: ' I was wanting to create a decimal extension method to account for decimals and parsing and do variable formats for an extensible DateTime'. The variable formats would break immediately on your code. I was wanting to do an extensible extension method like 9162017.GetDateTime("mddyyyy") or 201765.GetDateTime("yyyymd"). etc. – djangojazz Sep 06 '17 at 17:34
  • @djangojazz: so basically, you will have more formats than "mmddyyyy" and "mddyyyy"? Could you explain that a bit more please? I was assuming, based on your example, that the sequence is Month-Day-Year, of which you can only have 2 permutations. Let me know what I'm missing! – code4life Sep 06 '17 at 17:37
  • I just did explain with pseudo code at the end. I thought this would be built more into .NET in the GAC and was surprised it did not account for it. – djangojazz Sep 06 '17 at 17:40
  • "A simpler, possibly easier approach, is to simply parse out the string representation of the decimal value." Why? Why use a string representation *at all*? It's really simple arithmetic - see my answer for an example. Going via string conversions is complicated and error-prone. – Jon Skeet Sep 06 '17 at 17:44