Formatting dates with ordinal numbers

D

David Jackson

Hello,

Is there anything in the framework which will format a date to show the
ordinal representation of the day value e.g.

28th June 2007
1st August 2007

instead of

28 June 2007
01 August 2007

I realise that it would be simple enough to write a function for this, but I
wondered if there was anything built-in so as not to reinvent the wheel.

Thanks,

DJ
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,

David Jackson said:
Hello,

Is there anything in the framework which will format a date to show the
ordinal representation of the day value e.g.

28th June 2007
1st August 2007

instead of

28 June 2007
01 August 2007

I dont know for sure, but look into DateTime.ToString() and more
especifically DateTimeFormatInfo provider.
 
D

David Jackson

"Ignacio Machin ( .NET/ C# MVP )" <machin TA laceupsolutions.com> wrote in
message
Hi Ignacio,

Thanks for the reply.
I dont know for sure, but look into DateTime.ToString() and more
especifically DateTimeFormatInfo provider.

Those were the first two places I looked but couldn't find anything.

DJ
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,

David Jackson said:
"Ignacio Machin ( .NET/ C# MVP )" <machin TA laceupsolutions.com> wrote in
message
Hi Ignacio,

Thanks for the reply.


Those were the first two places I looked but couldn't find anything.


If not there I doubht you will find it somewhere else :(

Implement it and then share it with the community :)
 
D

David Jackson

Implement it and then share it with the community :)

That seems fair :)

OK, now this is my first attempt at showing any code in public, so I would
really appreciate any and all criticism. I'm still very much a newbie
compared to most who post in here.

I'm also ashamed to say that I don't really speak any language other than
English, but luckily my French wife helped me with the French formatting. I
guess the main problem here is that this function would need to be extended
to support all of the various cultures in the Framework... Anyway, be
kind...

string EnglishDate = OrdinalDate(DateTime.Now, "MMM", "yyyy", new
CultureInfo("en-GB");
string FrenchDate = OrdinalDate(DateTime.Now, "MMM", "yyyy", new
CultureInfo("fr-FR");

static string OrdinalDate(DateTime DateToFormat, string MonthFormat, string
YearFormat, CultureInfo CI)
{
StringBuilder SB = new StringBuilder();

SB.Append(DateToFormat.Day.ToString("d"));
switch (CI.TwoLetterISOLanguageName)
{
case "en":
{
switch (DateToFormat.Day)
{
case 1:
case 21:
case 31:
{
SB.Append("st");
break;
}
case 2:
case 22:
{
SB.Append("nd");
break;
}
case 3:
case 23:
{
SB.Append("rd");
break;
}
default:
{
SB.Append("th");
break;
}
}
break;
}
case "fr":
{
switch (DateToFormat.Day)
{
case 1:
{
SB.Append("er");
break;
}
default:
{
SB.Append("e");
break;
}
}
break;
}
}
SB.Append(" ");

SB.Append(DateToFormat.ToString(MonthFormat, CI.DateTimeFormat));
SB.Append(" ");
SB.Append(DateToFormat.ToString(YearFormat, CI.DateTimeFormat));

return SB.ToString();
}
 
P

Peter Duniho

OK, now this is my first attempt at showing any code in public, so I
would
really appreciate any and all criticism. I'm still very much a newbie
compared to most who post in here.

Because of your desire to make it extensible, I would use the Dictionary<>
class to handle the actual look-up (which is mostly what your function
does).

For example (you will note that, counting initialization, this code is
longer than the code you posted, but as new languages are added, two
things are true: 1) this code stays the same length, as code based on your
approach increases proportionally to the data being added, and 2) once
this code is shown to be correct, adding new data is simple and less
error-prone):

(Warning: I haven't compiled any of this code. There may well be some
fundamental syntax errors, like you need to cast something I forgot to
cast. However, I am confident that the basic design is fine, so don't let
any little thing like compiler errors dissuade you. :) )

Dictionary<string, Dictionary<int, string>> _dict = new Dictionary<string,
Dictionary<int, string>>();

static string OrdinalDate(DateTime DateToFormat, string MonthFormat, string
YearFormat, CultureInfo CI)
{
StringBuilder SB = new StringBuilder();
Dictionary<int, string> dictLanguage;

SB.Append(DateToFormat.Day.ToString("d"));

try
{
string strPostfix;

dictLanguage = _dict[CI.TwoLetterISOLanguageName].Value;

try
{
strPostfix = dictLanguage[DateToFormat.Day].Value;
}
catch (KeyNotFoundException)
{
// The dictionary must always include key 0, which is the
default
// to use if the actual date number isn't found.
strPostfix = dictLanguage[0].Value;
}

SB.Append(strPostfix);
}
catch (KeyNotFoundException exc)
{
throw new NotSupportedException("Unknown language in CultureInfo
CI", exc);
}

SB.Append(" ");

SB.Append(DateToFormat.ToString(MonthFormat, CI.DateTimeFormat));
SB.Append(" ");
SB.Append(DateToFormat.ToString(YearFormat, CI.DateTimeFormat));

return SB.ToString();
}

The idea being that the _dict field would be initialized to contain
dictionaries for each language you support, each dictionary containing a
default string to append (using index 0) and then specific strings for
specific values. You could of course write explicit code to initialize
the Dictionary<> instances, but in keeping with the data-driven model, I
would do something like this:

void InitOrdinalDate()
{
object[] objsDictInit = new object[] {
new object[]
{ "en",
new object[] { "th", 0 },
new object[] { "st", 1, 21, 31 },
new object[] { "nd", 2, 22 },
new object[] { "rd", 3, 23 }
},
new object []
{ "fr",
new object[] { "e", 0 },
new object[] { "er", 1 }
} };

Dictionary<string, Dictionary<int, string>> dictLang = new
Dictionary<string, Dictionary<int, string>>();

foreach (object[] arrayLang in objsDictInit)
{
string strLanguage = arrayLang[0];
Dictionary<int, string> dictPostfix = new Dictionary<int,
string>();

for (int i = 1; i < arrayLang.Length; i++)
{
object[] arrayPostfix = arrayLang;
string strPostfix = arrayPostfix[0];

for (int j = 1; j < arrayPostfix.Length; j++)
{
dictPostfix.Add(arrayPostfix[j], strPostfix);
}

}

dictLang.Add(strLanguage, dictPostfix);
}

_dict = dictLang;
}

You'd run this code once, and then the OrdinalDate() method would simply
reuse the results each time you call that method. Note also that the
awkward "arrays of arrays of arrays" design can easily be replaced with
something that just reads an XML file or string. IMHO, that would
actually be a better approach than what I posted, but I wanted to get the
illustration out without complicating things further. While I think XML
would be more maintainable, the above has the advantage that is uses only
the basic built-in stuff.

Hope that helps, rather than confuses things further. :)

Pete
 
D

David Jackson

Hi Peter,

Thanks for the reply.
Because of your desire to make it extensible, I would use the Dictionary<>
class to handle the actual look-up (which is mostly what your function
does).
OK.

For example (you will note that, counting initialization, this code is
longer than the code you posted, but as new languages are added, two
things are true: 1) this code stays the same length, as code based on your
approach increases proportionally to the data being added, and 2) once
this code is shown to be correct, adding new data is simple and less
error-prone):

Yes, I can see that.
Dictionary<string, Dictionary<int, string>> _dict = new Dictionary<string,
Dictionary<int, string>>();

FxCop tells me that it doesn't recommend nested generics - are they really
OK?
Hope that helps, rather than confuses things further. :)

I found it very useful. Thanks again.

DJ
 
M

Mark Rae

FxCop tells me that it doesn't recommend nested generics - are they really
OK?

FxCop's recommendations really should taken very much with a pinch of
salt...

Peter's code looks fine to me, and I would have no qualms about using
"nested generics"...

In fact, until I saw your post, the concept of a "nested generic" never
crossed my mind...
 
P

Peter Duniho

[...]
FxCop tells me that it doesn't recommend nested generics - are they
really OK?

I don't know anything about FxCop. Sounds a little fussy to me. :)

I have no idea why it might suggest that nested generics should be
avoided. That's like saying one should avoid putting references to any
class within any other class. But, it should go without saying, since I
wrote the code I at least thought it was okay at the time I wrote it. :)

Pete
 
M

Mark Wilden

Rather than designing and implementing this from scratch, why not look at
one of the Perl or Ruby library functions that already accomplish this, and
convert it to C#?

///ark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top