119

How to pass UTC dates to Web API?

Passing 2010-01-01 works fine, but when I pass a UTC date such as 2014-12-31T22:00:00.000Z (with a time component), I get a HTTP 404 response. So

http://domain/api/controller/action/2012-12-31T22:00:00.000Z

yields a 404 error response, while

http://domain/api/controller/action/2012-12-31

works fine.

How to pass UTC dates to Web API then - or at least specify date and time?

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
Nickolodeon
  • 2,848
  • 3
  • 23
  • 38
  • 2
    Is ":" in the date a suspect? Try escaping it. `http://domain/api/controller/action/2012-12-31T22%3A00%3A00.000Z` – shahkalpesh Jan 16 '13 at 14:02
  • 2
    Escaping doesn't help. Still 404. – Nickolodeon Jan 16 '13 at 14:08
  • Can you enable debugging so as to figure why the translation from passed string to date failing? The idea is to figure what method is being used to translate the date you passed using URL to `DateTime` - which I assume is the data type of paramater on your method. – shahkalpesh Jan 16 '13 at 14:11
  • 4
    I'll do that. Method expects .NET DateTime parameter. I think it's ridiculous that I can't pass time component and can't find docs on how to do that! – Nickolodeon Jan 16 '13 at 14:19
  • Take a look at http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx and a question by Christian in the comments section on similar question. Can you use `string` as parameter on the method and work your way out? – shahkalpesh Jan 16 '13 at 14:21
  • Also, take a look at http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx followed by http://www.hanselman.com/blog/SplittingDateTimeUnitTestingASPNETMVCCustomModelBinders.aspx. Although the blog post is specific to mvc, the same should be applicable here. – shahkalpesh Jan 16 '13 at 14:29
  • 2
    Post your solution when you are done. It can help other people having similar problem. Thanks. – shahkalpesh Jan 16 '13 at 15:12
  • I tried "http://localhost/My/2012-12-31T22:00:00.000Z" and it worked just fine for me. The DateTime was correctly model bound. – Youssef Moussaoui Jan 16 '13 at 16:37
  • Try to use toISOString(). It returns string in ISO8601 format. http://stackoverflow.com/a/28219557/960997 – rnofenko Jan 29 '15 at 16:34
  • Can any one see my question ?? https://stackoverflow.com/questions/46660261/working-with-date-time-in-web-api/46662319#46662319 – Moeez Oct 10 '17 at 08:55
  • I use POST method rather than GET to skip url conflictive characters – Matias Masso Jul 01 '20 at 15:04

13 Answers13

49

The problem is twofold:

1. The . in the route

By default, IIS treats all URI's with a dot in them as static resource, tries to return it and skip further processing (by Web API) altogether. This is configured in your Web.config in the section system.webServer.handlers: the default handler handles path="*.". You won't find much documentation regarding the strange syntax in this path attribute (regex would have made more sense), but what this apparently means is "anything that doesn't contain a dot" (and any character from point 2 below). Hence the 'Extensionless' in the name ExtensionlessUrlHandler-Integrated-4.0.

Multiple solutions are possible, in my opinion in the order of 'correctness':

  • Add a new handler specifically for the routes that must allow a dot. Be sure to add it before the default. To do this, make sure you remove the default handler first, and add it back after yours.
  • Change the path="*." attribute to path="*". It will then catch everything. Note that from then on, your web api will no longer interpret incoming calls with dots as static resources! If you are hosting static resources on your web api, this is therefor not advised!
  • Add the following to your Web.config to unconditionally handle all requests: under <system.webserver>: <modules runAllManagedModulesForAllRequests="true">

2. The : in the route

After you've changed the above, by default, you'd get the following error:

A potentially dangerous Request.Path value was detected from the client (:).

You can change the predefined disallowed/invalid characters in your Web.config. Under <system.web>, add the following: <httpRuntime requestPathInvalidCharacters="&lt;,&gt;,%,&amp;,*,\,?" />. I've removed the : from the standard list of invalid characters.

Easier/safer solutions

Although not an answer to your question, a safer and easier solution would be to change the request so that all this is not required. This can be done in two ways:

  1. Pass the date as a query string parameter, like ?date=2012-12-31T22:00:00.000Z.
  2. Strip the .000 from every request, and encode the url, so replace all :'s with %3A, e.g. by using HttpUtility.UrlEncode().
Vincent Sels
  • 2,711
  • 1
  • 24
  • 31
35

in your Product Web API controller:

[RoutePrefix("api/product")]
public class ProductController : ApiController
{
    private readonly IProductRepository _repository;
    public ProductController(IProductRepository repository)
    {
        this._repository = repository;
    }

    [HttpGet, Route("orders")]
    public async Task<IHttpActionResult> GetProductPeriodOrders(string productCode, DateTime dateStart, DateTime dateEnd)
    {
        try
        {
            IList<Order> orders = await _repository.GetPeriodOrdersAsync(productCode, dateStart.ToUniversalTime(), dateEnd.ToUniversalTime());
            return Ok(orders);
        }
        catch(Exception ex)
        {
            return NotFound();
        }
    }
}

test GetProductPeriodOrders method in Fiddler - Composer:

http://localhost:46017/api/product/orders?productCode=100&dateStart=2016-12-01T00:00:00&dateEnd=2016-12-31T23:59:59

DateTime format:

yyyy-MM-ddTHH:mm:ss

javascript pass parameter use moment.js

const dateStart = moment(startDate).format('YYYY-MM-DDTHH:mm:ss');
const dateEnd = moment(endDate).format('YYYY-MM-DDTHH:mm:ss');
FreeClimb
  • 696
  • 8
  • 13
19

I feel your pain ... yet another date time format... just what you needed!

Using Web Api 2 you can use route attributes to specify parameters.

so with attributes on your class and your method you can code up a REST URL using this utc format you are having trouble with (apparently its ISO8601, presumably arrived at using startDate.toISOString())

[Route(@"daterange/{startDate:regex(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$)}/{endDate:regex(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$)}")]
    [HttpGet]
    public IEnumerable<MyRecordType> GetByDateRange(DateTime startDate, DateTime endDate)

.... BUT, although this works with one date (startDate), for some reason it doesnt work when the endDate is in this format ... debugged for hours, only clue is exception says it doesnt like colon ":" (even though web.config is set with :

<system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" requestPathInvalidCharacters="" />
</system.web>

So, lets make another date format (taken from the polyfill for the ISO date format) and add it to the Javascript date (for brevity, only convert up to minutes):

if (!Date.prototype.toUTCDateTimeDigits) {
    (function () {

        function pad(number) {
            if (number < 10) {
                return '0' + number;
            }
            return number;
        }

        Date.prototype.toUTCDateTimeDigits = function () {
            return this.getUTCFullYear() +
              pad(this.getUTCMonth() + 1) +
              pad(this.getUTCDate()) +
              'T' +
              pad(this.getUTCHours()) +
              pad(this.getUTCMinutes()) +
              'Z';
        };

    }());
}

Then when you send the dates to the Web API 2 method, you can convert them from string to date:

[RoutePrefix("api/myrecordtype")]
public class MyRecordTypeController : ApiController
{


    [Route(@"daterange/{startDateString}/{endDateString}")]
    [HttpGet]
    public IEnumerable<MyRecordType> GetByDateRange([FromUri]string startDateString, [FromUri]string endDateString)
    {
        var startDate = BuildDateTimeFromYAFormat(startDateString);
        var endDate = BuildDateTimeFromYAFormat(endDateString);
    ...
    }

    /// <summary>
    /// Convert a UTC Date String of format yyyyMMddThhmmZ into a Local Date
    /// </summary>
    /// <param name="dateString"></param>
    /// <returns></returns>
    private DateTime BuildDateTimeFromYAFormat(string dateString)
    {
        Regex r = new Regex(@"^\d{4}\d{2}\d{2}T\d{2}\d{2}Z$");
        if (!r.IsMatch(dateString))
        {
            throw new FormatException(
                string.Format("{0} is not the correct format. Should be yyyyMMddThhmmZ", dateString)); 
        }

        DateTime dt = DateTime.ParseExact(dateString, "yyyyMMddThhmmZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);

        return dt;
    }

so the url would be

http://domain/api/myrecordtype/daterange/20140302T0003Z/20140302T1603Z

Hanselman gives some related info here:

http://www.hanselman.com/blog/OnTheNightmareThatIsJSONDatesPlusJSONNETAndASPNETWebAPI.aspx

Simon Dowdeswell
  • 1,001
  • 11
  • 19
  • In WebAPI method you can have datetime parameters as nullable DateTime (DateTime? startDateString, DateTime? endDateDtring) – DotNet Fan Jan 22 '16 at 10:22
  • Thanks for the mention of toISOString - that saved me. My RESTful WCF Service works fine with two dates in the URI, so didn't need your complex date conversions. Maybe its a quirk with Web API not liking the colons despite the config setting...odd though. – Neville Mar 09 '16 at 14:37
  • @Simon, the `endDate` would work if the request Url included a trailing forward slash. Unfortunately I can't recall where I came across this information, nor do I know of a way around this. – Pooven Jul 18 '16 at 11:36
  • 1
    24h clock users who want to use this should change the hh to HH in the date format. – snaits Jul 07 '17 at 08:04
13

As a similar alternative to s k's answer, I am able to pass a date formatted by Date.prototype.toISOString() in the query string. This is the standard ISO 8601 format, and it is accepted by .Net Web API controllers without any additional configuration of the route or action.

e.g.

var dateString = dateObject.toISOString(); // "2019-07-01T04:00:00.000Z"
Bondolin
  • 2,793
  • 7
  • 34
  • 62
7

This is a solution and a model for possible solutions. Use Moment.js in your client to format dates, convert to unix time.

 $scope.startDate.unix()

Setup your route parameters to be long.

[Route("{startDate:long?}")]
public async Task<object[]> Get(long? startDate)
{
    DateTime? sDate = new DateTime();

        if (startDate != null)
        {
            sDate = new DateTime().FromUnixTime(startDate.Value); 
        }
        else
        {
            sDate = null;
        }
         ... your code here!
  }

Create an extension method for Unix time. Unix DateTime Method

Community
  • 1
  • 1
Kentonbmax
  • 938
  • 1
  • 10
  • 16
4

It used to be a painful task, but now we can use toUTCString():

Example:

[HttpPost]
public ActionResult Query(DateTime Start, DateTime End)

Put the below into Ajax post request

data: {
    Start: new Date().toUTCString(),
    End: new Date().toUTCString()
},
s k
  • 4,342
  • 3
  • 42
  • 61
3

As a matter of fact, specifying parameters explicitly as ?date='fulldatetime' worked like a charm. So this will be a solution for now: don't use commas, but use old GET approach.

Nickolodeon
  • 2,848
  • 3
  • 23
  • 38
3

One possible solution is to use Ticks:

public long Ticks { get; }

Then in the controller's method:

public DateTime(long ticks);
Irvin Dominin
  • 30,819
  • 9
  • 77
  • 111
nikolai.serdiuk
  • 762
  • 8
  • 11
0

Since I have encoding ISO-8859-1 operating system the date format "dd.MM.yyyy HH:mm:sss" was not recognised what did work was to use InvariantCulture string.

string url = "GetData?DagsPr=" + DagsProfs.ToString(CultureInfo.InvariantCulture)
KnuturO
  • 1,565
  • 15
  • 19
0

By looking at your code, I assume you do not have a concern about the 'Time' of the DateTime object. If so, you can pass the date, month and the year as integer parameters. Please see the following code. This is a working example from my current project.

The advantage is; this method helps me to avoid DateTime format issues and culture incompatibilities.

    /// <summary>
    /// Get Arrivals Report Seven Day Forecast
    /// </summary>
    /// <param name="day"></param>
    /// <param name="month"></param>
    /// <param name="year"></param>
    /// <returns></returns>
    [HttpGet("arrivalreportsevendayforecast/{day:int}/{month:int}/{year:int}")]
    public async Task<ActionResult<List<ArrivalsReportSevenDayForecastModel>>> GetArrivalsReportSevenDayForecast(int day, int month, int year)
    {
        DateTime selectedDate = new DateTime(year, month, day);
        IList<ArrivalsReportSevenDayForecastModel> arrivingStudents = await _applicationService.Value.GetArrivalsReportSevenDayForecast(selectedDate);
        return Ok(arrivingStudents);
    }

If you are keen to see the front-end as well, feel free to read the code below. Unfortunately, that is written in Angular. This is how I normally pass a DateTime as a query parameter in Angular GET requests.

public getArrivalsReportSevenDayForecast(selectedDate1 : Date): Observable<ArrivalsReportSevenDayForecastModel[]> {
const params = new HttpParams();
const day = selectedDate1.getDate();
const month = selectedDate1.getMonth() + 1
const year = selectedDate1.getFullYear();

const data = this.svcHttp.get<ArrivalsReportSevenDayForecastModel[]>(this.routePrefix +
  `/arrivalreportsevendayforecast/${day}/${month}/${year}`, { params: params }).pipe(
  map<ArrivalsReportSevenDayForecastModel[], ArrivalsReportSevenDayForecastModel[]>(arrivingList => {
    // do mapping here if needed       
    return arrivingList;
  }),
  catchError((err) => this.svcError.handleError(err)));

return data;
}
Kushan Randima
  • 2,174
  • 5
  • 31
  • 58
0

Passing the date as a string and then parsing it worked for me. Probably want to add try catch on the parse, but this is the basic code.

[HttpGet("name={name}/date={date}", Name = "GetByNameAndDate")]
public IActionResult GetByNameAndDate(string name, string date) {
    DateTimeOffset dto = DateTimeOffset.Parse(date);
}

Then the request can look like this

https://localhost/api/Contoller/name=test/date=2022-02-18T13:45:37.000Z
0

For external APIs (where you do not know what type of client will call your service), Unix Time should be used both on the input parameters and outputted date fields. https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tounixtimeseconds?view=net-6.0

.Net provides ToUnixtimeSeconds and FromUnixtimeSeconds to easily convert to DateTime or DateTimeOff

Unix Time should be preferred over ISO formats because it is just a integer and can be passed in the URL string without encoding.
The 'Ticks' property is similar to Unix time but (I believe) should only be use between a .net client and server.

Most well know APIs will use Unix Time, for example see Stripe's API: https://stripe.com/docs/api

The obvious downsides of using Unix time are:

  • They are not human readable
  • They cannot be created by humans - making it difficult to call the API without code
Lee Smith
  • 6,339
  • 6
  • 27
  • 34
-1

Use binary format.

to send the info in url use dateTimeVar.ToBinary() it will be something like

http://domain/api/controller/action/637774955400000000

when you reciebe the data will get like Long variable and use the static function of DateTime Class to transform to DateTime type again.

DateTime MyDateTime = DateTime.FromBinary(BinaryDateTime);

Cheers