TimeZone datetime handling best solution so far

Hi community,

Like most of you, we have struggle on making app working across different time zones as we think it is not properly treated by default in COT framework.
Following we will share our solution (best so far in our opinion).

First we make couple of assumptions:
- the SQL server and web server are hosted into an environment with GMT+0 timezone. In our specific case we host the solution in azure.
- we always hold datetimes info in database as GMT+0 in datetime fields (not datetimeoffset - as it does not solve our issue)
- Data clients are responsible to format the data in their own timezones:
a - website will capture datetime offset via some javascript code to set a cookie that will be then interpreted by the ASPNET server side to render propely the webpages.
b - mobile clients that uses REST services should locally handle the datetime values based on their own local timezone.

Steps to implement the solution:
1 - add custom SharedBusinessRules that includes code for injecting JS code (see below).
2 - add custom ApplicationServices to handle user logout to clear previous set cookies (see below).
3 - in COT designer in the controller add two business rules with exact these Id's:
a - AddGmtOffset - command name: Select, type: Code, phase: Execute
b - RemoveGmtOffset - command name: Update|Insert, type: Code, phase: Execute
4 - add "GMT" tag your datetime field in the grid as well in the edit/create form. Don't forget about all forms where you use the field.
4 - generate the website.
5 - in VisualStudio editor comment the code that has been generated for the created business rules because will be use those created by us in customized SharedBusinessRules.
6 - rebuild and re-run your project, now those fields should be handled properly in your webpage.

Please have a look on the attached screenshots and match your controllers.

- creation of the business rules (keep exact Id name)

- tag datetime fields in grid view

- don't forget to tag datetime fields in edit/create views

- comment generated code


Custom Code:
- SharedBusinessRules.cs



using System;
using System.Linq.Expressions;
using System.Reflection;
namespace MyProject.Rules
{
public partial class SharedBusinessRules
{

#region GMT OFFSET FIELDS HANDLING

public const string GMT_OFFSET_TAG = "GMT";
public const string GMT_OFFSET_COOKIE = "gmtOffset";

[ControllerAction("", "", "Select", ActionPhase.Before)]
protected void InjectJavascriptGmtOffset()
{
if (Context.Request.Cookies[GMT_OFFSET_COOKIE] == null && this.ControllerName.ToUpper()!="SITECONTENT")
{
string strJS =
//"console.log('GMT OFFSET START');" +
"var d = new Date(); var exhours=1;" // 1 hour expiration
+ "d.setTime(d.getTime() + (exhours * 60 * 60 * 1000));"
//+ "d.setTime(d.getTime() + (exhours * 60 * 1000));" //for testing with 1 min expiration
+ "var expires = 'expires=' + d.toUTCString(); "
+ "Session_gmt_offset = new Date().getTimezoneOffset();"
+ "document.cookie = '" + GMT_OFFSET_COOKIE + "=' + Session_gmt_offset + ';' + expires + ';path=/';"
//+ "console.log('GMT OFFSET ' + Session_gmt_offset);"
;
Result.ExecuteOnClient(strJS);
if (this.View == "grid1")
{
Result.Refresh(true);
}
else if (this.View == "editForm1")
{
Result.HideModal();
Result.ShowModal(this.ControllerName, this.View, "", "");
}
else
{
Result.RefreshChildren();
}
}
}

/// <summary>
/// Adjust DateTime field to include GMT offset
/// used as a Select - Code/Execute Rule
/// rename the rule to "AddGmtOffset" and comment the generated code as this one will be used
/// </summary>
[Rule("AddGmtOffset")]
public void AdjustGmtOffsetImplementation()
{
// This is the placeholder for method implementation.
if (Context.Request.Cookies[GMT_OFFSET_COOKIE] != null)
{
int gmtOffset = 0;
if (int.TryParse(Context.Request.Cookies[GMT_OFFSET_COOKIE].Value, out gmtOffset))
{
foreach (DataField df in this.Page.Fields)
{
if (df.Type.StartsWith("Date") && df.Tag.Contains(GMT_OFFSET_TAG))
{
var orgDateTime = (DateTime?)this.SelectFieldValue(df.Name);
if (orgDateTime.HasValue)
{
var newDT = new DateTimeOffset(orgDateTime.Value, new TimeSpan(0, gmtOffset, 0)).UtcDateTime;
//instance.GetType().GetProperty(df.Name).SetValue(instance, newDT);
this.UpdateFieldValue(df.Name, newDT);
}
}
}
}
}
}

/// <summary>
/// Adjust DateTime field to subtract GMT offset
/// used as a Update - Code/Execute Rule
/// rename the rule to "RemoveGmtOffset" and comment the generated code as this one will be used
/// </summary>
[Rule("RemoveGmtOffset")]
public void AdjustGmtOffsetOnBeforeUpdateImplementation()
{
if (Context.Request.Cookies[GMT_OFFSET_COOKIE] != null)
{
int gmtOffset = 0;
if (int.TryParse(Context.Request.Cookies[GMT_OFFSET_COOKIE].Value, out gmtOffset))
{
foreach (var xpDF in this.NodeSet().SelectView(this.View).SelectDataFields().Nodes)
{
string fieldName = xpDF.GetAttribute("fieldName", String.Empty);
string tag = xpDF.GetAttribute("tag", String.Empty);
if (tag.ToUpper().Contains(GMT_OFFSET_TAG))
{
var orgDateTime = (DateTime?)this.SelectFieldValue(fieldName);
if (orgDateTime.HasValue)
{
var newDT = new DateTimeOffset(orgDateTime.Value, new TimeSpan(0, -gmtOffset, 0)).UtcDateTime;
this.UpdateFieldValue(fieldName, newDT);
}
}
}
}
}
}

#endregion GMT OFFSET FIELDS HANDLING

}
}



- ApplicationServices.cs



using MyProject.Rules;
using System;
using System.Web;
using System.Web.Configuration;
namespace MyProject.Services
{
public partial class ApplicationServices
{
public override void UserLogout()
{
base.UserLogout();
//clean user cookies
HttpCookie cookie = new HttpCookie(SharedBusinessRules.GMT_OFFSET_COOKIE);
cookie.Value = "0";
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.Cookies.Set(cookie);
}
}
}



Any comments and improvements are welcome.

Best regards,
Claudiu
4 people like
this idea
+1
Reply