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?
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="<,>,%,&,*,\,?" />. 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:
Pass the date as a query string parameter, like ?date=2012-12-31T22:00:00.000Z.
Strip the .000 from every request, and encode the url, so replace all :'s with %3A, e.g. by using HttpUtility.UrlEncode().
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');
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
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"
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
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()
},
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.
One possible solution is to use Ticks:
public long Ticks { get; }
Then in the controller's method:
public DateTime(long ticks);
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)
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;
}
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
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
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
Related
I can't seem to understand what governs how DateTime values are parsed when WebAPI's binding framework operates. Some test code:
public class TestSearchModel
{
public DateTime? From { get; set; }
public DateTime? Until { get; set; }
public string Freetext { get; set; }
}
...
public HttpResponseMessage Get([FromUri] TestSearchModel searchModel)
{
//CultureInfo culture = Thread.CurrentThread.CurrentCulture;
return Request.CreateResponse(
$"Received search request with Freetext: {searchModel.Freetext}, From: {searchModel.From}, Until: {searchModel.Until}");
}
And then I test with various requests like:
http://localhost:9000/api/search?freetext=oi-vei!&from=14.07.2020 (1)
http://localhost:9000/api/search?freetext=oi-vei!&from=07.14.2020 (2)
http://localhost:9000/api/search?freetext=oi-vei!&from=2020-07-14 (3)
Freetext is only there as a test - it always binds just fine. Dates in format like (3) bind fine, I guess that is close enough to culture-invariant format. What I do not get is why the format (1) would not work when the application's culture is set to something with this date format (Norwegian in my case).
Our intention is to have consumers of the API use culture-invariant formats in any case, but we also thought it would be nice to be able to recognize national formats - when we anyway set the current culture from the requests' headers. I realize I can hook up my own binder and program whatever I want, but this seems like something that the framework should already have. It does parse the datetime values one way or another. So the question is: how does the binding system decides what date format to use when attempting parsing a date value?
I,m not sure but I hope this code helps you more.
You can change your date format and also pass case sensitive parameters in quertString.
http://localhost:9000/api/search?Freetext=oi-veii&From=14-07-2020
System.TimeZoneInfo has a method called IsDaylightSavingTime, which takes a DateTime object and returns true if the specified datetime falls in the DST for that timezone. Is there an equivalent function in NodaTime or another way to achieve the same result?
You can get this from a ZoneInterval. Here is an extension method that will help.
public static bool IsDaylightSavingsTime(this ZonedDateTime zonedDateTime)
{
var instant = zonedDateTime.ToInstant();
var zoneInterval = zonedDateTime.Zone.GetZoneInterval(instant);
return zoneInterval.Savings != Offset.Zero;
}
Now you can do:
zdt.IsDaylightSavingsTime();
If you don't have a ZonedDateTime, you can get one from a DateTimeZone plus either an Instant or a LocalDateTime. Or you can massage this extension method to take those as parameters.
Update: This function is now included in Noda Time v1.3 and higher, so you no longer have to write the extension method yourself.
I need a way to fake DateTime.Parse with Typemock and have it return the same date when called with any parameters.
I have a DB field that stores an encrypted string that is parsed as date when loaded. The class that holds the data has a Load() method where it copies DB data into its properties, decrypts what's encrypted and does some basic validation, such as:
public class BusinessObject{
public DateTime ImportantDate{get;set;}
...
public void Load(DBObject dbSource){
...
ImportantDate = DateTime.Parse(DecryptionService.Decrypt(dbSource.ImportantDate));
}
}
Runtime all works well.
I'm trying to write a unit test using TypeMock to load some fake data into BusinessObject using its Load method. BusinessObject has way too many properties and can not be deserialized from XML, but DBObject can, so I've stored some XMLs that represent valid data.
It all works well until DecryptionService is called to decrypt the data - it doesn't work because my dev machine doesn't have the DB certificates used in the encryption process. I can't get those on my machine just for testing, that would be a security breach.
I added this to my unit test:
Isolate.Fake.StaticMethods<DecryptionService>(Members.ReturnRecursiveFakes);
Isolate.WhenCalled(() => DecryptionService .Decrypt(null)).WillReturn("***");
Isolate.Fake.StaticMethods<DateTime>(Members.ReturnNulls);
Isolate.WhenCalled(() => DateTime.Parse("***" /*DateStr*/)).WillReturn(DateTime.Now.AddYears(2));
The first part where DecryptionService is faked works, social security and other sensitive strings are "decrypting", but no matter what parameters I give to DateTime I still get one exception or another (ArgumentNullException: String reference not set to an instance of an object if DateStr is null, FormatException when it's "*")
How (if) can I override DateTime.Parse with typemock so that it returns valid DateTime with any invalid paramters passed?
My name is Nofar and i'm from Typemock's support team.
DateTime.Parse is not supported in the WhenCalled API, so in order to fake it's returned value you need to wrap it with a method from your class, for example:
public class BusinessObject
{
public DateTime Load (string s)
{
return DateTime.Parse(s);
}
}
And you test will look like this:
[TestMethod]
public void TestMethodDateTime()
{
BusinessObject b = new BusinessObject();
DateTime now= DateTime.Now.AddYears(2);
Isolate.WhenCalled(()=>b.Load(null)).WillReturn(now);
Assert.AreEqual(now, b.Load(null));
}
Supporting DateTime.Parse in the WhenCalled API is in our backlog.
Please feel free to contact us via mail at support#typemock.com
Nofar
Typemock Support
I'm trying to bind Date params in my controller. I read somewhere that it should be sufficient if I have the date in format yyyy-MM-dd HH:mm:ss.S. But this doesn't work. The other option that I read was to add _day, _month, _year, etc. suffixes to the attributes that I want to parse, but this method doesn't work either.
So it's basically a one-to-many relationship, when on the many side is the date (an Action has a VisitList<Visit> and the Visit has date).
// domain classes
class Action {
List<Visit> visitList
static hasMany = [visitList: Visit]
}
class Visit {
Action action
static belongsTo = [action: Action]
Date date
String description
}
I parse all the params to the action instance like this:
// in controller
def save(){
Action actionInstance = new Action()
action.properties = params
action.save()
}
The date is sent from view and has this format:
visitList[0].date=2012-05-15 00:00:00.0000
But the date is still null. The visit also has a description attribute and it works well. The param for this attribute is:
visitList[0].description=description
Thanks for any suggestions or advices.
Did you try saving the Visit first and then save the Action? If that doesn't work you may have to convert the date from string to Date, Date.parse() method is depreciated in grails 2.0, try this:
Date date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(dateString)
def action = new Action()
def visit = new Visit(date: date, description: description?:"", action: action)
visit.save()
action.visitList.add(visit)
action.save()
-HTH
Without seeing more code it's difficult to tell. You may want to try a Command Object. Where is your input coming from? A View? If so, one typically uses the and tags to control the input. If you're not using those, you may have to manually parse the date using DateFormat {hint, use the parse() method}.
I developed a asp.net website where I log error information using log4net with format:
"%-5p %d - %m%n"
It logs datetime by current machine's datetime.
For example:
FATAL 2011-04-10 01:08:11,759 - message
But I want to convert datetime to another region or add additional time with it. For example I want to add 3 hours with previous example and want output as:
FATAL 2011-04-10 **04**:08:11,759 - message
Any idea on how to achieve this?
This might not answer your question, because I'm not sure exactly what you are trying to achieve. Maybe if you could provide more details on exactly why you want to do this, you might get a better answer.
If you are trying to correlate multiple log files (or other sources) that have been generated in different regions, it might help...
You could try log4net's utctime PatternLayout as described here.
This will get your log times in universal time, which might be easier for you to correlate. If you have control over the sources of timestampes (like your asp.net website), then by normalizing them to universal time, they should be easier to compare.
If you really do want to change the time to a different region or add/substract an arbitrary time span from the timestamp as it is logged, you might have to write your own custom PatternLayout or PatternLayoutConverter. That might be a little bit tricky as I think that neither the log4net DatePatternConverter nor the UtcDatePatternConverter is available for customization (i.e. they are declared internal so you can't subclass them and add your behavior).
You could write your own from scratch, using log4net's implementation from the log4net code repository, but that seems like a lot of trouble to me.
One more note, maybe it would be useful to log time again in a separate column using one of these Custom Date format specifiers: z, zz, zzz, K.
UPDATE:
See this answer for another idea that might help. The question asks for a way to capture username with log4net. Ultimately, the best solution for him was to write a very small class that will return the information the he needs (username). An instance of the class can be stored in the MDC (or GlobalDiagnosticContext) and referenced in the configuration. When log4net gets the value from the MDC (i.e. the object), it calls ToString and logs the result. This approach is a lot easier, if somewhat less flexible, than writing a whole new PatternLayoutConverter.
Towards the bottom of the answer is some sample code like this:
public class HttpContextUserNameProvider
{
public override string ToString()
{
HttpContext context = HttpContext.Current;
if (context != null &&
context.User != null &&
context.User.Identity.IsAuthenticated)
{
return context.Identity.Name;
}
return "";
}
}
You would store the object in the MDC/GlobalDiagnosticContext.Properties like this:
MDC.Set("user", new HttpContextUserNameProvider());
You could probably write something similar to return a different time. You could use this time instead of the log4net-provided time, or you could make this "custom" time an additional column. Your "custom time" object might look like this:
public class MyLocalTimeProvider
{
public override string ToString()
{
DateTime myLocalTime = GetUtcTimeInMyTimeZone(DateTime.UtcNow);
return myLocalTime;
}
}
Then you could reference it like this:
MDC.Set("myLocalTime", new MyLocalTimeProvider());
I'm not sure if you can apply formats to items from the MDC/GlobalDiagnosticContext.Properties (I think you can) or not, but you could try it and see.
You could always use a hardcoded format or add a format property to the object like this:
public class MyLocalTimeProvider
{
public MyLocalTimeProvider(string format)
{
Format = format;
}
public MyLocalTimeProvider()
: this ("G")
{
}
public string Format { get; set; }
public override string ToString()
{
DateTime myLocalTime = GetUtcTimeInMyTimeZone(DateTime.UtcNow);
return myLocalTime.ToString(Format);
}
}
You might take a look at this article for how to convert a UTC time to an arbitrary time zone.
If you need just "shift" date to your timezone, you can write your own ForwardingAppender, which will change DateTime of logged event:
namespace Olekstra
{
using System;
using log4net.Appender;
using log4net.Core;
public class TimeShiftForwardingAppender : ForwardingAppender
{
private TimeSpan shift;
private TimeSpan targetOffset;
public TimeShiftForwardingAppender()
{
TargetOffset = TimeZoneInfo.Local.BaseUtcOffset;
}
public TimeSpan TargetOffset
{
get
{
return targetOffset;
}
set
{
targetOffset = value;
shift = targetOffset.Subtract(TimeZoneInfo.Local.BaseUtcOffset);
}
}
protected override void Append(LoggingEvent loggingEvent)
{
var eventData = loggingEvent.GetLoggingEventData();
eventData.TimeStamp = eventData.TimeStamp.Add(shift);
base.Append(new LoggingEvent(eventData));
}
protected override void Append(LoggingEvent[] loggingEvents)
{
for (var i = 0; i < loggingEvents.Length; i++)
{
var eventData = loggingEvents[i].GetLoggingEventData();
eventData.TimeStamp = eventData.TimeStamp.Add(shift);
loggingEvents[i] = new LoggingEvent(eventData);
}
base.Append(loggingEvents);
}
}
}
And in .config
<log4net>
<appender name="FileAppender" type="log4net.Appender.RollingFileAppender">
<!-- Your real appender here -->
</appender>
<appender name="TimeShiftAppender" type="Olekstra.TimeShiftForwardingAppender">
<targetOffset>06:00:00</targetOffset> <!-- your desired (local) UTC offset value -->
<appender-ref ref="FileAppender" /> <!-- real appender(s) -->
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="TimeShiftAppender" />
</root>
</log4net>