MSSQL and EntityFramework - Storing and reading utc datetime - asp.net

I have a request to alter current columns which are type of 'time' and instead of capturing just time I need to capture so called "utc time".
My idea is to create a fixed codelist with all timezones, and then to reference it to a appropriate table as FK.
My questions are:
Can column of a type 'time' hold also an information regarding time zones (utc, for example 15:00:00 +2 (gmt + 2)) and if not, could you suggest me another type for that column?
Should I maybe need to separate it into two columns? For example: [15:00:00] - StartTime, [+2:00] - UtcOffset
EF Insert: When I do inserting to the db, for that particular column, should I convert my DateTime object to for example DateTimeOffset?
Thanks in advance.

From the comments in your question, it sounds like you are building an appointment scheduling system. I'll base my answer on that, because your specific questions aren't quite aligned to the scenario you described.
First, it's important to understand that the relationship between a time zone and an offset is a one-to-many relationship. One time zone can have multiple offsets. In other words, a time zone is not an offset, but rather a time zone has an offset.
A time zone represents a geographic region where the local time is the same throughout. It is identified by a string ID, such as "America/Los_Angeles" (an IANA time zone ID) or "Pacific Standard Time" (a Windows time zone ID). In .NET, you will use them on the TimeZoneInfo object with the Id instance property or methods like FindSystemTimeZonesById.
An offset is like -07:00 or +05:30 or even +13:45. Any given offset applies only at a particular date and time. For example, in America/Los_Angeles, either -08:00 or -07:00 apply depending on whether daylight saving time is in effect at a given point in time. Keep in mind that DST is not the only reason for offsets to be different - many time zones have changed their standard time at some point in their history.
Also, it's called an offset because it is deviated from UTC by a certain amount. UTC itself always has a zero offset, delineated either by +00:00 or sometimes by Z. It's similar to GMT, except in how it is defined. UTC applies universally, everywhere. GMT technically applies only on the prime meridian. They both refer to the zero offset. You should prefer to say "UTC" in most cases.
Next, you should separate your application logic between future scheduling and present/past record keeping.
Present/past is the easier of the two. Since the moment in time has actually occurred, the local time and its offset from UTC is fixed forever. You can either store the local time and its offset in a single .NET DateTimeOffset structure (mapped to a datetimeoffset field in SQL Server). In other words, you can simply store 2021-07-27T12:00:00-08:00.
Note that you could instead store the equivalent UTC date and time, which would be 2021-07-27T20:00:00+00:00. However you've then lost the local time, and thus would need to convert back using the original time zone if you wanted to see that time. Some people prefer that, but I think it's more useful to store the original value.
For future scheduling, the situation is a bit different. Consider that the offset might not be the same for one appointment as it will be for the next appointment in the same time zone. Also consider that the definitions for which offset apply might change in between the time you schedule the appointment and when it comes around. (The likelihood of that increases the further out you schedule.)
Thus, for each location you should not store an offset, but rather a time zone identifier. Add a TimeZoneId to your object that stores each location (or each appointment depending on your model schema). Use TimeZoneInfo.GetSystemTimeZones to list the available time zones. The DisplayName property can be shown to your user, and the selected Id property gets assigned to the TimeZoneId.
Next you have to consider if you are scheduling a single appointment or creating a recurring appointment pattern.
For a single appointment you simply need the local date and time for that appointment. You can use a .NET DateTime struct (use datetime2 in SQL). Don't apply an offset, and don't convert to UTC. Just store the information provided.
For recurring appointments, you need to think through the information provided and store exactly what is given. For example, if the appointment is at 10:00 every other Tuesday, you'll need to store "10:00", "Tuesdays" and "2 week intervals". The data types for each will vary depending on how you choose to store and apply them. For example, you might use a time type for 10:00, but you could store the other values using integers. Appointments of different patterns could get stored in different ways.
Alternatively, some like to store patterns using a string containing a CRON expression. You can google that for more details.
Now you have everything you need to both schedule an appointment and record that appointment after it happens. But there's one part missing - you'll likely want some table of upcoming appointments that are easily queryable. For that, you've got a few options:
You can create a separate table via a background job of some kind. Periodically it would query all the appointments, use their information to compute the next upcoming appointment time, and insert it. You can store that in a DateTimeOffset, either as local time or as UTC. (SQL Server will always compute indexes on the equivalent UTC time either way.)
You could just add another field to your appointments that shows the next actual appointment time. You can then compute the next upcoming appointment whenever the appointment is created or updated, or when that appointment occurs, and update the table accordingly.
With either approach keep in mind that you will want to periodically check for time zone data updates (either via Windows Update or keeping the tzdata package current on Linux). You will also want to periodically re-compute future appointment times, in case that time zone data has been modified in a way that affects the appointments.
If all of this sounds super complicated - sorry, but it is. Doing scheduling worldwide across time zones right is challenging. If you want it simpler, you might want to look into a pre-made solution such as Quartz.NET, which you can integrate into your application.

Related

How to ingest historical data with proper creation time?

When ingesting historical data, we would like it to become consistent with streamed data with respect to caching and retention, hence we need to set proper creation time on the data extents.
The options I found:
creationTime ingestion property,
with(creationTime='...') query ingestion property,
creationTimePattern parameter of Lightingest.
All options seem to have very limited usability as they require manual work or scripting to populate creationTime with some granularity based on the ingested data.
In case the "virtual" ingestion time can be extracted from data in form of a datetime column or otherwise inherited (e.g. based on integer ID), is it possible to instruct the engine to set creation time as an expression based on the data row?
If such a feature is missing, what could be other handy alternatives?
creationTime is a tag on an extent/shard.
The idea is to be able to effectively identify and drop / cool data at the end of the retention time.
In this context, your suggested capability raises some serious issues.
If all records have the same date, no problem, we can use this date as our tag.
If we have different dates, but they span on a short period, we might decide to take min / avg / max date.
However -
What is the behavior you would expect in case of a file that contains dates that span on a long period?
Fail the ingestion?
Use the current time as the creationTime?
Use the min / avg / max date, although they clearly don't fit the data well?
Park the records in a tamp store until (if ever) we get enough records with similar dates to create the batches?
Scripting seems the most reasonable way to go here.
If your files are indeed homogenous by their records dates, then you don't need to scan all records, just read the 1st record and use its date.
If the dates are heterogenous, then we are at the scenario described by the "However" part.

ASP.NET / SQL Server - Set Time Zone

We have an ASP.NET website and an SQL database hosted in US. Whenever I use the function Now() in VB.NET and getdate() in SQL, I get the US' current time. The problem is, the client is in the Philippines which is on GMT+8 Time Zone. My question is, is there any way I can set the Time Zone of a specific database and website so that when I use the functions, I'll get the Philippine's current time? How do you deal with this? As much as possible, we don't want to do subtraction or addition to the result date of the functions since in the future, clients will be from other country. It will give us headache updating the codes if we do that.
Thank you in advance!
Given that your clients may be in different time zones, you should store a timezone for the clients, that they (or you) can set as a preference for their account. Store all dates+times as UTC, and then convert to their timezone when displaying results in your interface.
This question has already been addressed to a great extent in the following question:
How to work with time zones in ASP.NET?
Follow-up:
Unfortunately, the SQL server date is a system-level setting, so it's not really something that can be manipulated on a per-session basis. It sounds like you will need to make some code changes, but you can isolate them.
Do you have a session-level variable which contains the client time zone offset? If not, create one.
Create a small date/time utility class.
In the utility class, provide 3 methods to:
(1) get the current date/time (offset to the client's time zone)
(2) pass in a database date/time to return the time offset for the client's TZ.
(3) pass in a time from the client to subtract out the client's TZ difference.
You will have to make code changes, but you can probably use those utility functions to wrap inputs and outputs everywhere, centralizing the logic. Microsoft has a page about mis-steps to avoid when using the DateTime class and manipulating time zones:
http://msdn.microsoft.com/en-us/library/ms973825.aspx#datetime_topic1a

Default time zone per user, set in the business logic

I have an application where all the DateTime's are always the time of the server. That means one time zone. The idea is to make the application compatible all over the world. The first step is to convert all the stored DateTime's in the database to UTC, that's no problem. Second step is to assume a timezone for the user (based out business logic), and to use as a default for displaying and parsing user input. Furthermore it would be nice if methods like DateTime.Now and other method calls that construct datetimes without explicit time zone/region information would also assume this time zone/region.
The idea is to assume a time zone for a user from the database. I have the user and his time zone, thats's no problem.
The problem is the presentation logic. There are DateTime.now methods all over the code, to convert all these methods is a lot of work.
To avoid that I need a global time zone setting where the DateTime knows which time zone it is. Preferably on a generic place.
class business logic
InitializeCulture()
set time zone for user
end function
end class
class presentation logic
sample()
TimeOfTheCurrentUser = DateTime.now
end function
end class
If you are looking for best practice for time zone handling in (more or less) enterprise application, I can share the proven one:
Store all date and time related information in UTC. Storing it as local time (on server or wherever else) always brings a risk that somebody, somewhere, someday forget to convert them and the results would be less than ideal. Of course it means that dates and times should be instantiated via DateTime.UtcNow or with proper DateTimeKind selected (this refers to parsing as well).
Obviously you need to convert time zone before displaying DateTime to end user. And you surely realize that you need to obtain this information from some source (thus the question). That somewhere could be client-side (which would work especially well with thick client and not so well with thin client's JavaScript) but could be just as well the user profile. If your application has user profiles, I would definitely recommend to allow user to select preferred time zone. Other g11n-related settings would be preferred culture for e-mails or preferred language. All of these settings should be detected and preselected (so the user does not have to think or more importantly click too much).
To convert DateTime classes to local time in another time zone, you would use TimeZoneInfo class. There are several ways to do that...
If you would implement this method, you may hit the problem with time zone names - they are in server's culture, so you would need to externalize (move to resource file) what TimeZoneInfo's DisplayName shows you and let translators do their jobs.
Also just a quick word what I meant by detect time zone.
On thick client you would do that by simply reading local time zone:
TimeZoneInfo currentTimeZone = TimeZoneInfo.Local;
With JavaScript (thin client) it is not that easy. The only thing you can get is a time zone offset (which could vary depending on date & time) on given date:
var date = new Date();
var offset = date.getTimezoneOffset(); // GMT offset in minutes

solr search for a time within a time range

I'm aware that Solr provides a date field which can store a time instance and then range queries can be performed to match all documents which have that field within a particular range.
My problem is the inverse of this. I need to associate multiple time ranges with documents and then search for all documents which have the searched time within one of those ranges.
For e.g. I'm indexing outlets and have 3-4 ranges during which the outlet is open. I need to search for all outlets which are open at a particular time instance.
One way of doing this is to index start time and end time of the durations as separate date fields and compare during search like
(time1_1 > t AND time1_2 < t) OR (time2_1 > t AND time2_2 < t) OR (time3_1 > t AND time3_2 < t)
Is there a better/faster/cleaner way to do this?
Your example looks like the entities of your index are the outlet stores and you store their opening and closing times in separate (probably dynamic) fields.
If you ask for a different approach you have to consider to restructure the existing schema or to even create an additional one that uses another entity.
It may seem unusual at first, but if this query is the most essential one to your app then you should consider making the entity of your new index to what you acutally want to query: the particular time instance. I take it, time instance is either a whole day, or maybe half or quarter of a day.
The schema would include fields like the ID, the startdate of the day or half day or whatever you choose, the end of it, and a multivalued list of ids that point to the outlets (stored in your current index (use a multi core setup)).
Even if you choose quarter days to handle morning, afternoon and night hours separately, and even with a preview of several years, data should not explode.
This different schema setup allows you to:
do the most important computation during import so that it is easily accessible when querying,
simple query that returns in one hit what you seek
You could even forgo Date fields by using a custom way to identify the ranges. I am thinking of creating the identifier from the date and a string that indicates whether it is morning or afternoon etc. This would be used as the unique ID in SOLR. If you can create such an ID from any "time instance" that is queried you'd end up with a simple ID lookup.
e.g.
What is open on 2013/03/03 in the morning?
/solr/openhours/select?q=id:2013_03_03_am
returns:
Array of outlet ids.

Override DateTime serialization for ASP.NET WebMethod parameters

I am working on cleaning up a bug in a large code base where no one was paying attention to local time vs. UTC time.
What we want is a way of globally ignoring time zone information on DateTime objects sent to and from our ASP.NET web services. I've got a solution for retrieve operations. Data is only returned in datasets, and I can look for DateTime columns and set the DateTimeMode to Unspecified. That solves my problem for all data passed back and forth inside a data set.
However DateTime objects are also often passed directly as parameters to the web methods. I'd like to strip off any incoming time zone information. Rather than searching through our client code and using DateTime.SpecifyKind(..) to set all DateTime vars to Undefined, I'd like to do some sort of global ASP.NET override to monitor incoming parameters and strip out the time zone information.
Is such a thing possible? Or is there another easier way to do what I want to do?
Just to reiterate -- I don't care about time zones, everyone is in the same time zone. But a couple of users have machines badly configured, wrong time zones, etc. So when they send in July 1, 2008, I'm getting June 30, 2008 22:00:00 on the server side where it's automatically converting it from their local time to the server's local time.
Update: One other possibility would be if it were possible to make a change on the client side .NET code to alter the way DateTime objects with Kind 'Undefined' are serialized.
I have dealt with this often in many applications, services, and on different platforms (.NET, Java, etc.). Please believe me that you do NOT want the long term consequences of pretending that you don't care about the time zone. After chasing lots of errors that are enormously difficult and expensive to fix, you will wish you had cared.
So, rather than stripping the time zone, you should either capture the correct time zone or force a specific time zone. If you reasonably can, get the various data sources fixed to provide a correct time zone. If they are out of your control, then force them either to the server's local time zone or to UTC.
The general industry convention is to force everything to UTC, and to set all production hardware clocks to UTC (that means servers, network devices like routers, etc.). Then you should translate to/from the user's local time zone in the UI.
If you fix it correctly now, it can be easy and cheap. If you intentionally break it further because you think that will be cheaper, then you will have no excuses later when you have to untangle the awful mess.
Note that this is similar to the common issue with Strings: there is not such thing as plain text (a String devoid of a character encoding) and there is no such thing as a plain (no time zone) time/date. Pretending otherwise is the source of much pain and heartache, and embarrassing errors.
OK, I do have a workaround for this, which depends on the fact that I only actually need the Date portion of the DateTime. I attach this property to every Date or DateTime parameter in the system
<XmlElement(DataType:="date")>
This changes the generated wsdl to have the type s:date instead of s:dateTime. (Note that simply having the type of the .NET method parameter be a Date rather than a DateTime did NOT accomplish this). So the client now only sends the date portion of the DateTime, no time info, no time zone info.
If I ever need to send a Date and Time value to the server, I'll have to use some other workaround, like making it a string parameter.
I've had issues with the time zone information as well. The problem is I'm already providing the datetime fields in UTC. Then the serialization occurs and the local offset becomes part of the date/time. The dates/times for our vendor in a different timezone were pretty messed up. I got around this problem by using the tsql convert function on the datetime fields in my select statement I used to populate my datasets. This converted the fields to a string variable, which translates nicely to a datetime value automatically on the client side. If you just want to pass the date, you can use the 101 code to provide just the date. I used 126 to provide the date and time exactly how it appears in my database columns, with the timezone information stripped out.

Resources