1

I want to force clients (web, android, ios) to send the API only time in UTC/GMT.

This means that they get user's local time (using any method), convert it to UTC/GMT, and then send it to the API.

And I want to reject any datetime parameter that is not in UTC/GMT.

In JavaScript, I can get UTC this way:

new Date().toUTCString() which gives this result:

'Mon, 15 Nov 2021 04:26:38 GMT'

And I send this string to the API:

[HttpGet]
public object Parse(string clientDateTime)
{
    var date = DateTime.Parse(clientDateTime);
    return new 
    {
        ParsedDate = date,
        Kind = date.Kind.ToString()
    };
}

However, I see that .NET parses this date time as Local and not as Utc. This is in spite of the string containing GMT.

How can I check the incoming datetime and make sure it's UTC/GMT?

Hossein Fallah
  • 1,859
  • 2
  • 18
  • 44
  • you can use DateTime.Parse(clientDateTime).ToUniversalTime() , for converting local to utc. – Manish Nov 15 '21 at 04:38
  • No, that should not be necessary. That might change the date time based on offsets and server's local time. Am I wrong? – Hossein Fallah Nov 15 '21 at 04:41
  • Yes you are correct, if this DateTime is nonlocal then you should use TimeZoneInfo.ConvertTimeToUtc(DateTime, TimeZoneInfo) – Manish Nov 15 '21 at 04:51
  • https://learn.microsoft.com/en-us/dotnet/api/system.datetime.touniversaltime?view=net-5.0 – Manish Nov 15 '21 at 04:51
  • @Manish, then whole point of this method is to not get stuck in the concept of `TimeZone`. Each user has his own time zone, and we do the conversion on the client-side. This way, we don't have to deal with user's time-zone on the server. – Hossein Fallah Nov 15 '21 at 04:55
  • 1
    Take a look at: https://stackoverflow.com/questions/10659523/how-to-get-the-exact-local-time-of-client and https://stackoverflow.com/questions/1091372/getting-the-clients-time-zone-and-offset-in-javascript – AliNajafZadeh Nov 15 '21 at 05:00
  • yaa I understood your issue, but this is the default behaviour of DateTime.Parse. it will convert using DateTimeKind.Local unless a specific timezone is specified. – Manish Nov 15 '21 at 05:00

1 Answers1

1

You can greatly simplify your problem by using a conventional format for datetime serialization. A common choice for this problem is using the ISO 8601 datetime format.

Here you can find an in depth explanation of this format, but as an example this is a datetime in the ISO 8601 format: 2021-11-15T06:40:48.204Z (the final Z indicates that the datetime represented by this string is UTC)

The main advantage in fixing a format for date and times is that you will know in advance the format and you will be in a much better position to parse the datetime strings on the server.

Using the ISO 8601 format is a good choice, because it is a well known format and it is the standard de facto for the datetime serialization: this basically means that anyone writing a client for your application will be able to comply with the required format. Of course, you are required to clearly document this convention so that your clients (or your fellow developers) will be aware of it.

Another tip is using the DateTimeOffset struct instead of DateTime. DateTimeOffset is basically used to represent a specific point in time and that's exactly what you want: your clients will send you ISO 8601 strings representing a point in time and you want to know that point in time in your application.

Your clients will be able to use any time zone to express the point in time they want to send to your application. Doing this using an UTC datetime or any other time zone is just an implementation detail. Once you have parsed the datetime string to a DateTimeOffset instance, if you really want to, you can check whether it is an UTC time by checking the Offset property: it will be a zero TimeSpan value if the DateTimeOffset instance represents an UTC date and time.

In order to manipulate date in a Javascript client application I strongly suggest to use the Moment.js library. Check this docs to see how to get an ISO 8601 string with Moment.js

You can use this helper method to parse an ISO 8601 string to a DateTimeOffset instance. This implementation allows the client to send you a broad range of ISO 8601 compliant string formats, if you want you can be stricter by reducing the number of allowed formats (see the Iso8601Formats static field in the code).

To summarize:

  • ask your clients to only send you datetime strings in a format compliant with the ISO8601 specification. Clearly document this choice
  • for a Javascript client use a library like Moment.js to manipulate date and times. This will be much simpler than using plain old javascript Date objects.
  • if you are manipulating date time strings representing a specific point in time, use the DateTimeOffset struct instead of the DateTime struct. DateTimeOffset represents a specific point in time expressed in a certain time zone. The Offset property represents the difference between the point in time represented by the DateTimeOffset instance and UTC: its value will be a zero TimeSpan if the DateTimeOffset instance represents an UTC datetime. Notice that the point in time will always be the same regardless the time zone it is referring to, so using UTC doesn't make any real difference (it's just an implementation detail at this point).
  • use code like this one to parse a DateTimeOffset instance from a string. This code tries as many ISO 8601 compliant formats as possible (this is done in order to accept as many valid formats as possible). If you want, you can decide to be stricter: to do that, just reduce the number of formats in the Iso8601Formats array.

A final note on your code. The behaviour you are observing from DateTime.Parse is exactly the expected one. Check the documentation for DateTime.Parse:

Converts the string representation of a date and time to its DateTime equivalent by using the conventions of the current thread culture.

DateTime.Parse is basically designed to use the locale settings of the machine running the code.

If you want to learn more on the difference between DateTime and DateTimeOffset you can check this stackoverflow question.

Enrico Massone
  • 6,464
  • 1
  • 28
  • 56