This is a follow-on to this question. I am building a RESTful server (that is in C# but the core engine is Java (via IKVM)). We are writing clients in C# Java, etc.
So for the specific case of the C# client, if they want a ZonedDateTime on the server side, I need to pass a string similar to "2018-10-21T08:11:55-06:00[America/Denver]". For this case I think I should have them pass me a (DateTime, TimeZoneInfo) and from that build the string.
To do this I need to get the Java "America/Denver" from the setting for TimeZoneInfo. Is there any way to get this conversion.
As I understand it, there's no ISO or other widely used setting for uniquely identifying time zones.
"America/Denver" is the IANA name for that time zone. The Time Zone Database (tzdb) contains these names and their rules (which can change over time). NodaTime uses tzdb data to perform its logic.
You can use the TimeZoneConverter package to convert a TimeZoneInfo to a tzdb time zone ID.
You've asked a few questions related to this and I'd like to piece some of that together for you here.
NodaTime All The Way Down
If it's feasible to do so, you should have the consumer pass you a ZonedDateTime. It's a single value with all the information needed by the Java-based core engine, which is exactly what you were asking for (here). Using a single value that has been validated in the domain (as opposed to a custom container for the constituent parts) defers error-proned activities to the consumer, who would be better suited than you to resolve them and must do before calling your client. Then you don't have to be responsible for any errors or bugs related to something that shouldn't concern you.
Provided that you have a ZonedDateTime instance, all you need now is a custom pattern that will give you a string in the format the Java side expects.
ZonedDateTimePattern customPattern = ZonedDateTimePattern.Create(
"uuuu'-'MM'-'dd'T'HH':'mm':'sso<Z-HH':'mm>'['z']'",
CultureInfo.InvariantCulture,
mapping => mapping.LocalDateTime.InZoneLeniently(mapping.Zone),
DateTimeZoneProviders.Tzdb,
default);
Based on your previous questions, it looks like you need a literal "Z" for UTC instead of "+00:00". The "Z-HH':'mm" subpattern does that. Take a look at the Offset patterns documentation if you need something different.
Now you can use customPattern to create the string you need to send.
string formatted = customPattern.Format(zonedDateTime);
The same pattern can be used to parse such a string back to ZonedDateTime, if necessary.
Using NodaTime Internally Only
If you can't expect the consumer to work with NodaTime types, that's okay. Receiving DateTimeOffset and TimeZoneInfo could also work. You can convert them to a ZonedDateTime within your client without much ceremony.
// Given: DateTimeOffset dateTimeOffset, TimeZoneInfo timeZoneInfo
DateTimeZone dateTimeZone = DateTimeZoneProviders.Tzdb[TZConvert.WindowsToIana(timeZoneInfo.Id)];
ZonedDateTime zonedDateTime = OffsetDateTime.FromDateTimeOffset(dateTimeOffset).InZone(dateTimeZone);
The potential problem with this is that the collective input hasn't been validated in the domain. Matt Johnson-Pint pointed out in his answer that an offset could be passed which is incorrect for the provided time zone. Be prepared to add validation or a try/catch so you can tell the consumer what they did wrong in very clear language.
Accepting Ambiguous Times
You could accept a DateTime but you would be forced to make assumptions about ambiguous times that may not be acceptable to you and/or the consumer. Then you become responsible for logic that has nothing to do with your API's functionality.
I'll include it here for completeness but this is not an endorsement of any kind.
// Given: DateTime dateTime, TimeZoneInfo timeZoneInfo
DateTimeZone dateTimeZone = DateTimeZoneProviders.Tzdb[TZConvert.WindowsToIana(timeZoneInfo.Id)];
ZonedDateTime zonedDateTime = LocalDateTime.FromDateTime(dateTime).InZoneLeniently(dateTimeZone);
InZoneLeniently is the big red flag there. It will "just do it" when an ambiguous time is encountered, and it might not be right. Remember, "just" is a 4-letter word.
Related
I've been struggling with this for a bit so I'll punt to SO.
I have an app where I need to parse strings into datetimes. The strings look like this:
"201503131557"
(they will always be this format "yyyyMMddHHmm") and always be in Central Standard Time (or Central Daylight Time depending on the date)even though they don't specify it.
When I parse them I understand that it parses them into local time. I'm running on Azure PaaS and don't want to change it to run in CST just to support this operation.
How do i write code that works both locally and in Azure PaaS that will correctly parse these dates into DateTimes setting their timezone to CST?
Here is a quick unit test I wrote to prove Matt Johnson's answer.
[TestMethod]
public void DateTests()
{
var unSpecDt = DateTime.ParseExact("201503131557", "yyyyMMddHHmm", CultureInfo.InvariantCulture);
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var utcDt = TimeZoneInfo.ConvertTimeToUtc(unSpecDt, tz);
var offset = tz.GetUtcOffset(unSpecDt);
var dto =new DateTimeOffset(unSpecDt, offset);
var cstDt = dto.DateTime;
Assert.IsTrue(cstDt.Hour - utcDt.Hour == offset.Hours);
}
To parse the string, since you have only the date and time, and you know the specific format the strings will be in, do this:
DateTime dt = DateTime.ParseExact("201503131557", "yyyyMMddHHmm", CultureInfo.InvariantCulture);
The resulting value will have its Kind property set to DateTimeKind.Unspecified (not local time, as you thought). That is to be expected, as you provided no information regarding how this timestamp is related to UTC or local time.
You said the value represents time in Central Standard Time. You'll need a TimeZoneInfo object that understands that time zone.
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
Note that this identifier represents the Central Time observed in the USA, including both CST or CDT depending on which is in effect for the given value (despite the word "Standard" in its name). Also note that it valid on Windows operating systems only. If you wanted to use .NET Core on some other OS, then you'd need to pass the IANA identifier "America/Chicago" instead (or use my TimeZoneConverter library to use either identifier on any platform).
The next step is to figure out what you want to do with this value. You might do a few different things with it:
If you want to convert it to the equivalent UTC value, represented as a DateTime, then you can do this:
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(dt, tz);
If you want a DateTimeOffset representation which holds the input you gave and the offset from UTC as it related to US Central Time, then you can do this:
TimeSpan offset = tz.GetUtcOffset(dt);
DateTimeOffset dto = new DateTimeOffset(dt, offset);
Keep in mind that it's possible for the input time is invalid or ambiguous if it falls near a DST transition. The GetUtcOffset method will return the standard offset in such cases. If you want different behavior, you have more code to write (out of scope for this post).
There are other things you might do, all of which are provided by the TimeZoneInfo class.
Note that "Azure PaaS" might refer to a few different things, and while there is a setting called WEBSITE_TIME_ZONE in Azure App Service - I don't recommend you lean on it. Consider it a last resort to be used only when you can't control the code. In most cases, it is better off writing your code to never depend on the time zone setting of the system it runs on. That means never calling DateTime.Now, or TimeZoneInfo.Local, DateTime.ToLocalTime, or even DateTime.ToUniversalTime (since it converts from the local time zone), etc. Instead, rely upon methods that work explicitly with either UTC or a specific time zone or offset. Then you will never need to care about where your app is hosted.
Lastly, understand that neither the DateTime or DateTimeOffset types have any capability of understanding that a value is tied to a specific time zone. For that, you'd need to either write your own class, or look to the Noda Time library whose ZonedDateTime class provides such functionality.
You can use:
DateTime.ParseExact(...)
public static DateTime ParseExact (string s, string format, IFormatProvider provider);
This datetime do not have any metadata about timezone. You can convert to UTC and then you will be sure that you are able to convert to all timezones.
Use the DateTime.TryParseExact(...) method with DateTimeStyles.RoundtripKind to avoid conversion.
DateTime dt;
DateTime.TryParseExact("201503131557", "yyyyMMddHHmm", null, System.Globalization.DateTimeStyles.RoundtripKind, out dt);
If you need to convert you will need to go to UTC then use the conversion methods of TimeZoneInfo with TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time").
This is a follow on from a question answered yesterday..
Convert 12hr Time String to DateTime object
Those times in the xml feed are EST (who does that?) but our timezone is BST.
so 10:30PM is 02:30AM UTC or 03:30AM BST
However, TryParseExact yields 10:30PM in local time (as to be expected given that there is not timezone information)
So the question; how can I parse that time as 02:30AM UTC rather than 10:30PM BST?
However, TryParseExact yields 10:30PM in local time
No, it doesn't. Not unless you tell it to. By default, and unless there's any indication of the offset in the pattern, the parse methods will return DateTime values with a Kind of Unspecified - which is entirely appropriate as no information has been specified. If you just convert it to a string, it will assume it's actually a local time, but that's not what the value itself says. You need to understand the three kinds of DateTime - it's a broken model IMO, but that's what we've got in the BCL.
You can pass that to the appropriate TimeZoneInfo to apply a specific time zone and get an appropriate DateTimeOffset, although it's then up to you to remember the actual time zone involved. (An offset isn't the same as a time zone.)
Alternatively, you could use my Noda Time project, which differentiates between the different logical types rather more clearly. You'd parse as a LocalTime, then decide which LocalDate to join that with in order to produce LocalDateTime, which you could then convert to a ZonedDateTime using the "America/Los_Angeles" time zone (or the Windows equivalent; the choice is yours). In performing that conversion, you'd specify what you'd want to happen if the given local time was invalid or ambiguous due to daylight saving transitions.
In our C# project we have the need for representing a date without a time.
I know of the existence of the DateTime, however, it incorporates a time of day as well.
I want to make explicit that certain variables and method-arguments are date-based.
Hence I can't use the DateTime.Date property
What are the standard approaches to this problem?
Why is there no Date class in C#?
Does anyone have a nice implementation using a struct and maybe some extensionmethods on DateTime and maybe implementing some operators such as == and <, > ?
Allow me to add an update to this classic question:
DateOnly (and TimeOnly) types have been added to .NET 6, starting with Preview 4. See my other answer here.
Jon Skeet's Noda Time library is now quite mature, and has a date-only type called LocalDate. (Local in this case just means local to someone, not necessarily local to the computer where the code is running.)
I've studied this problem significantly, so I'll also share several reasons for the necessity of these types:
There is a logical discrepancy between a date-only, and a date-at-midnight value.
Not every local day has a midnight in every time zone. Example: Brazil's spring-forward daylight saving time transition moves the clock from 11:59:59 to 01:00:00.
A date-time always refers to a specific time within the day, while a date-only may refer to the beginning of the day, the end of the day, or the entire range of the day.
Attaching a time to a date can lead to the date changing as the value is passed from one environment to another, if time zones are not watched very carefully. This commonly occurs in JavaScript (whose Date object is really a date+time), but can easily happen in .NET also, or in the serialization as data is passed between JavaScript and .NET.
Serializing a DateTime with XML or JSON (and others) will always include the time, even if it's not important. This is very confusing, especially considering things like birth dates and anniversaries, where the time is irrelevant.
Architecturally, DateTime is a DDD value-object, but it violates the Single Responsibly Principle in several ways:
It is designed as a date+time type, but often is used as date-only (ignoring the time), or time-of-day-only (ignoring the date). (TimeSpan is also often used for time-of-day, but that's another topic.)
The DateTimeKind value attached to the .Kind property splits the single type into three, The Unspecified kind is really the original intent of the structure, and should be used that way. The Utc kind aligns the value specifically with UTC, and the Local kind aligns the value with the environment's local time zone.
The problem with having a separate flag for kind is that every time you consume a DateTime, you are supposed to check .Kind to decide what behavior to take. The framework methods all do this, but others often forget. This is truly a SRP violation, as the type now has two different reasons to change (the value, and the kind).
The two of these lead to API usages that compile, but are often nonsensical, or have strange edge cases caused by side effects. Consider:
// nonsensical, caused by mixing types
DateTime dt = DateTime.Today - TimeSpan.FromHours(3); // when on today??
// strange edge cases, caused by impact of Kind
var london = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var paris = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
var dt = new DateTime(2016, 3, 27, 2, 0, 0); // unspecified kind
var delta = paris.GetUtcOffset(dt) - london.GetUtcOffset(dt); // side effect!
Console.WriteLine(delta.TotalHours); // 0, when should be 1 !!!
In summary, while a DateTime can be used for a date-only, it should only do so when when every place that uses it is very careful to ignore the time, and is also very careful not to try to convert to and from UTC or other time zones.
I suspect there is no dedicate pure Date class because you already have DateTime which can handle it. Having Date would lead to duplication and confusion.
If you want the standard approach look at the DateTime.Date property which gives just the date portion of a DateTime with the time value set to 12:00:00 midnight (00:00:00).
I've emailed refsrcfeedback#microsoft.com and that's their answer
Marcos, this is not a good place to ask questions like these. Try http://stackoverflow.com
Short answer is that you need a model to represent a point in time, and DateTime does that, it’s the most useful scenario in practice. The fact that humans use two concepts (date and time) to mark points in time is arbitrary and not useful to separate.
Only decouple where it is warranted, don’t do things just for the sake of doing things blindly. Think of it this way: what problem do you have that is solved by splitting DateTime into Date and Time? And what problems will you get that you don’t have now? Hint: if you look at DateTime usages across the .NET framework: http://referencesource.microsoft.com/#mscorlib/system/datetime.cs#df6b1eba7461813b#references
You will see that most are being returned from a method. If we didn’t have a single concept like DateTime, you would have to use out parameters or Tuples to return a pair of Date and Time.
HTH,
Kirill Osenkov
In my email I'd questioned if it was because DateTime uses TimeZoneInfo to get the time of the machine - in Now propriety. So I'd say it's because "the business rules" are "too coupled", they confimed that to me.
I created a simple Date struct for times when you need a simple date without worrying about time portion, timezones, local vs. utc, etc.
https://github.com/claycephus/csharp-date
System.DateOnly and System.TimeOnly types were recently added to .NET 6, and are available in the daily builds.
They were included with the .NET 6 Preview 4 release.
See https://github.com/dotnet/runtime/issues/49036
They are in the .NET source code here:
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs
I've blogged about them here.
If you need to run date comparisons then use
yourdatetime.Date;
If you are displaying to the screen use
yourdatetime.ToShortDateString();
Allow me to speculate: Maybe it is because until SQL Server 2008 there was no Date datatype in SQL so it would be hard so store it in SQL server?? And it is after all a Microsoft Product?
Who knows why it's that way. There are lots of bad design decisions in the .NET framework. However, I think this is a pretty minor one. You can always ignore the time part, so even if some code does decide to have a DateTime refer to more than just the date, the code that cares should only ever look at the date part. Alternatively, you could create a new type that represents just a date and use functions in DateTime to do the heavy lifting (calculations).
Why? We can only speculate and it doesn't do much to help solve engineering problems. A good guess is that DateTime contains all the functionality that such a struct would have.
If it really matters to you, just wrap DateTime in your own immutable struct that only exposes the date (or look at the DateTime.Date property).
In addition to Robert's answer you also have the DateTime.ToShortDateString method. Also, if you really wanted a Date object you could always use the Adapter pattern and wrap the DateTime object exposing only what you want (i.e. month, day, year).
There is always the DateTime.Date property which cuts off the time part of the DateTime. Maybe you can encapsulate or wrap DateTime in your own Date type.
And for the question why, well, I guess you'll have to ask Anders Heljsberg.
Yeah, also System.DateTime is sealed. I've seen some folks play games with this by creating a custom class just to get the string value of the time as mentioned by earlier posts, stuff like:
class CustomDate
{
public DateTime Date { get; set; }
public bool IsTimeOnly { get; private set; }
public CustomDate(bool isTimeOnly)
{
this.IsTimeOnly = isTimeOnly;
}
public string GetValue()
{
if (IsTimeOnly)
{
return Date.ToShortTimeString();
}
else
{
return Date.ToString();
}
}
}
This is maybe unnecessary, since you could easily just extract GetShortTimeString from a plain old DateTime type without a new class
Because in order to know the date, you have to know the system time (in ticks), which includes the time - so why throw away that information?
DateTime has a Date property if you don't care at all about the time.
If you use the Date or Today properties to get only the date portion from the DateTime object.
DateTime today = DateTime.Today;
DateTime yesterday = DateTime.Now.AddDays(-1).Date;
Then you will get the date component only with the time component set to midnight.
once again I have to create a date module, and once again i live the horror of perfecting it, is it me or are date and time the filthiest animals in programming profession, its the beast lurking behind the door that I wish I never have to deal with :(
does anyone know of a great source I can learn from that deals with dates in the following aspects:
user enters datetime and time zone
system translates to universal time and saves in data source
system retrieves universal time converted to local time chosen by developer (not by server or client location which may not be the right zone to display)
system should consider daylight time saving differences
cannot rely on "DateTime" parsing as it parses bohemiangly with respect to local server time
must give ability to developer to deal in both shapes: datetime and string objects
i looked at blogengine.net to see how they deal with but its too nieve, they save the time difference in hours in the settings datasource, it is absoluteley inaccurate... any sources to help?
i already went far in creating the necessary methods that use CultureInfo, TimeZoneInfo, DateTimeOffset ... yet when i put it to the test, it failed! appreciate the help
EDIT:
After squeezing some more, i narrowed it down to this:
public string PrettyDate(DateTime s, string format)
{
// this one parses to local then returns according to timezone as a string
s = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(s, "AUS Eastern Standard Time");
CultureInfo Culture = CultureInfo.CreateSpecificCulture("en-au");
return s.ToString(format , Culture);
}
problem is, I know the passed date is UTC time because im using
DateTimeOffset.Parse(s, _dtfi).UtcDateTime;
// where dtfi has "yyyy-MM-ddTHH:mmzzz" as its FullDateTimePattern
when i call the function on my datetime, like this:
AuDate.Instance.PrettyDate(el.EventDate,"yyyy-MM-dd HH:mm zzz");
on my machine i get:
2009-11-26 15:01 +11:00
on server I get:
2009-11-26 15:01 -08:00
I find this very peculiar! why is the timezone incorrect? everything else is in place! have i missed something?
My comments for your pointers.
user enters datetime and time zone
# OK no issue
system translates to universal time and saves in data source
# OK no issue
system retrieves universal time converted to local time chosen by developer (not by server or client location which may not be the right zone to display)
# Is this s requirement? Why not just retrieve as universal time
system should consider daylight time saving differences
# Can be handled by DaylightTime Class, TimeZone Class etc
cannot rely on "DateTime" parsing as it parses bohemiangly with respect to local server time
# Then do not rely on DateTime Parsing
must give ability to developer to deal in both shapes: datetime and string objects
# DateTime Class as the basis should be good enough, use TimeZone / TimeZoneInfo / DaylightTime / DateTimeOffset etc to augment it
I feel your pain - which is why I'm part of the Noda Time project to bring a fully-featured date and time API to .NET. However, that's just getting off the ground. If you're still stuck in a year's time, hopefully Noda Time will be the answer :)
The normal .NET situation is better than it was now that we've got DateTimeOffset and TimeZoneInfo, but it's still somewhat lacking.
So long as you use TimeZoneInfo correctly twice, however, it should be fine. I'm not sure that DateTime parsing should be too bad - I think it should parse it as DateTimeKind.Unspecified unless you specify anything else in the data. You can then convert it to UTC using TimeZoneInfo.
Could you provide a short but complete program which shows the problems you're having?
Actually, I find the .NET date/time functionality to be quite nice. I'm puzzled by your troubles with it.
What exactly are you trying to do that DateTimeOffset and TimeZoneInfo can't do for you?
"User enters datetime and timezone" -- Check! Either DateTime or DateTimeOffset would work here.
"System translates to universal time and saves in data source" -- Check! Again, either DateTime or DateTimeOffset would work for you, although most database backends will need some special handling if you want to store timezone offsets. If you're already converting it to UTC, just store it as a datetime field in SQL Server or the equivalent in another RDBMS and don't worry about storing the fact that it's UTC.
"System retrieves universal time converted to local time chosen by the developer" -- Check! Just construct a TimeZoneInfo for your desired local time, and then call TimeZoneInfo.ConvertTime.
"System should consider daylight time saving differences" -- Check! That's what TimeZoneInfo.AdjustmentRule is for.
"Cannot rely on "DateTime" parsing as it parses bohemiangly with respect to local server time" -- ??? First off, "bohemiangly" isn't even a word. And you can customize how the datetime gets parsed with DateTime.ParseExact.
"Must give ability to developer to deal in both shapes: datetime and string objects" -- Why? What's wrong with just keeping one internal representation and then transforming only on input and output? I can't think of any operation on date/time values that would be made easier by doing it against a string.
In short, I think you're just griping about the complexities of handling date/time data in general.
Thanks to Jon Skeet who put me on the right track, i never knew this before but now I know, DateTime object does not hold time zone information in it AT ALL! so whenever i use it i am already losing the datetime offset information, the DateTimeOffset object however, retains the time zone bit, so all my objects should use that, i really thought datetimeoffset object to be a bit limiting, i wanted to post a question about what is different between datetime and datetimeoffset, i should have done that!
now Im using the following code to retrieve the right zone:
string s = "2009-11-26T04:01:00.0000000Z";
DateTimeOffset d = DateTimeOffset.Parse(s);
TimeZoneInfo LocalTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
DateTimeOffset newdate = TimeZoneInfo.ConvertTime(d, LocalTimeZoneInfo);
return newdate.ToString("yyyy-MM-dd HH:mm zzz");
thank you all for your input
My database is located in e.g. california.
My user table has all the user's timezone e.g. -0700 UTC
How can I adjust the time from my database server whenever I display a date to the user who lives in e.g. new york? UTC/GMT -4 hours
You should store your data in UTC format and showing it in local timezone format.
DateTime.ToUniversalTime() -> server;
DateTime.ToLocalTime() -> client
You can adjust date/time using AddXXX methods group, but it can be error prone. .NET has support for time zones in System.TimeZoneInfo class.
If you use .Net, you can use TimeZoneInfo. Since you tagged the question with 'c#', I'll assume you do.
The first step is getting the TimeZoneInfo for the time zone in want to convert to. In your example, NY's time zone. Here's a way you can do it:
//This will get EST time zone
TimeZoneInfo clientTimeZone
= TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
//This will get the local time zone, might be useful
// if your application is a fat client
TimeZoneInfo clientTimeZone = TimeZoneInfo.Local;
Then, after you read a DateTime from your DB, you need to make sure its Kind is correctly set. Supposing the DateTime's in the DB are in UTC (by the way, that's usually recommended), you can prepare it to be converted like this:
DateTime aDateTime = dataBaseSource.ReadADateTime();
DateTime utcDateTime = DateTime.SpecifyKind(aDateTime, DateTimeKind.Utc);
Finally, in order to convert to a different time zone, simply do this:
DateTime clientTime = TimeZoneInfo.ConvertTime(utcDateTime, clientTimeZone);
Some extra remarks:
TimeZoneInfo can be stored in static fields, if you are only interested in a few specific time zones;
TimeZoneInfo store information about daylight saving. So, you wouldn't have to worry about that;
If your application is web, finding out in which time zone your client is in might be hard. One way is explained here: http://kohari.org/2009/06/15/automagic-time-localization/
I hope this helps. :)
You could use a combination of TimeZoneInfo.GetSystemTimeZones() and then use the TimeZoneInfo.BaseUtcOffset property to offset the time in the database based on the offset difference
Info on System.TimeZoneInfo here
Up until .NET 3.5 (VS 2008), .NET does not have any built-in support for timezones, apart from converting to and from UTC.
If the time difference is always exactly 3 hours all year long (summer and winter), simply use yourDate.AddHours(3) to change it one way, and yourDate.AddHours(-3) to change it back. Be sure to factor this out into a function explaining the reason for adding/substracting these 3 hours.
You know, this is a good question. This year I've done my first DB application and as my input data related to time is an Int64 value, that is what I stored off in the DB. My client applications retrieve it and do DateTime.FromUTC() or FromFileTimeUTC() on that value and do a .LocalTime() to show things in their local time. I've wondered whether this was good/bad/terrible but it has worked well enough for my needs thus far. Of course the work ends up being done by a data access layer library I wrote and not in the DB itself.
Seems to work well enough, but I trust others who have more experience with this sort of thing could point out where this is not the best approach.
Good Luck!