It’s a simple enough question to ask and answer. In almost every introduction to a foreign language, it’s one of the basic phrases we learn. We’ll leave aside the irony that we almost never ask the question of another person, because of mobile phones, tablets, and myriad ways which we can answer that question ourselves. As developers, we’re often given the task of producing output that includes some date value, usually in a grid or table that provides the user with information about when a particular thing happened.
So far, pretty easy stuff. But I was recently asked to provide users with that information displayed in local time, with some users were scattered over a plethora of timezones. This led to some interesting discoveries I thought I should share below. So grab your sonic screwdrivers, absurdly long scarves, throw the switch on your TARDIS and allons-y!
Step 1: Universal Greenwich Zulu Time
When we’re dealing with a geographically dispersed user community, our first question should be “Where does the server live?” More importantly, we need to have a standard for how we persist time — a starting point for which our regional times can be calculated. As it turns out, this problem was solved early on — around the 19th century — in large part due to the advance of Great Britain’s mariners, their use of chronometers for navigation, and time balls. No, really, it’s a thing. And yes, it is totally like the “ball drop” that happens in Times Square on New Years Eve. But the important thing is that we have used Greenwich mean time (GMT), more recently called Coordinated Universal Time (UTC) or also Zulu time, often denoted with a “Z” suffix when written.
I could write more about why Greenwich, why UTC is a less than intuitive abbrevation for the time zone it represents, and what the heck Zulu has to do with anything, but that’s a separate post — or a great lunchtime read in Wikipedia (especially the Time Zones paragraph). Let’s just leave it to a best practice that it isn’t uncommon to see sharesd systems, particularly those living in the cloud, keeping clocks set to UTC.
Step 2: DateTime.ToLocalTime()?
Great, so we’ve established our point of origin for time is UTC. I’ll be the first to admit that I was a hardcore C# developer before I ever added SharePoint or Dynamics CRM to my resume so my first swing at this is: “No problem, I will persist all time as DateTime.UtcNow, and use the ToLocalTime() method to display to a user and call it a day.” Sure, that works when everyone using the system is in the same time zone, but we’ve already established our system is going to be used by folks in New Jersey who want to see time as Eastern Standard Time (EST) and folks in California who want to see it as Pacific Standard Time (PST). In a custom ASP.NET solution, we’d probably store some kind of time zone information for each user, but we’re in SharePoint. Surely, SharePoint has a facility to do this for us?
Step 3: SPRegionalSettings to the Rescue
As a matter of fact, it does (and stop calling me Shirley). There’s a construct in SharePoint called SPRegionalSettings that, among other things, can store time zone information for us. Not only that, it can be related to both an SPWeb object as well as an SPUser object. Not only does it exist, but it has UI components associated with it so the task of setting those values for each site and user becomes fairly straightforward. This has been covered adequately in a few other blogs, so I won’t cover that here — Google “SharePoint 2013 timezone settings” and you’ll be sure to find a handful of results that can lead you through the mouse clicks needed to make OOTB SharePoint 2013 persist these settings.
And for OOTB functionality and most SharePoint entities, you’re done. But we were a custom app living in a SharePoint world. What do we do? We’re used to binding DateTime fields to a grid, letting ToString() do all the work for us and go back to writing our next mod for Fallout 4. Even the aforementioned call to DateTime.ToLocalTime has no concept of SharePoint or regional settings. Furthermore, what do we do when a user hasn’t made a personal preference for a setting? Our answer to “What time is it?” needs to respect (or ignore) user settings, web settings, and/or finally default to web application settings.
Step 4: Extending DateTime
At this point, we know what we need to do. Leveraging .NET extensions, we can create a new method essentially overloading the ToLocalTime() method already exposed on the DateTime object. The only thing we really [need in?] this method to understand is an SPWeb. Because I hate being ThatGuy — you know, the one who forgets the right time to call Dispose or implement the using pattern to ensure we don’t have memory leaks — I have implemented using a string to represent the URL.
/// <summary>
/// Convert a date time to local time based on settings for SPWeb and/or SPUser
/// </summary>
/// <param name="dateToCheck">the date in UTC we're going to convert</param>
/// <param name="currentUrl">URL of the SharePoint web we're going to look for RegionalSettings</param>
/// <returns>A string value representing the converted date time</returns>
public static string ToLocalTime(this DateTime dateToCheck, string currentUrl)
{
string localTimeString = string.Empty;
SPTimeZone tz = null;
DateTime dtLocal = dateToCheck.ToUniversalTime();
using (SPSite site = new SPSite(currentUrl))
{
using (SPWeb web = site.OpenWeb())
{
if (web.CurrentUser.RegionalSettings != null)
{
tz = web.CurrentUser.RegionalSettings.TimeZone;
}
else if (web.RegionalSettings != null)
{
tz = web.RegionalSettings.TimeZone;
}
else
{
tz = SPRegionalSettings.GlobalTimeZones[web.Site.WebApplication.DefaultTimeZone];
}
if (tz != null)
{
dtLocal = tz.UTCToLocalTime(dateToCheck);
localTimeString = dtLocal.ToString();
}
}
}
return localTimeString;
}
This approach assumes the caller isn’t going to pass in a string value for URL that is invalid or that the intended SPWeb is one that the user has access to. We could add extra defensive coding bits or just pass in the SPWeb. We could also return a DateTime (like the framework’s ToLocalTime does), but I wanted to illustrate the hierachy more than anything else.
Implementing in such a way, we can now take any DateTime value and display as text like so:
DateTime myUtcDate = DateTime.UtcNow;
string localTime = myUtcDate.ToLocalTime(SPContext.Current.Web.Url);
And that’s it.
The Wrap-Up
As you can see, answering the question “What time is it?” isn’t a horrifying experience — SharePoint can do most of the heavy lifting for you. But to leverage SharePoint’s power in your own custom web part, feature, timer job, etc. it requires an understanding of the hierarchy and making informed choices about how to walk it appropriately.
It’s worth mentioning one more time that this solution hinges on the system time being set at UTC. If that’s not the case, then some layer of abstraction would be worth implementing so that the same “point of origin” can be used to make all temporal calculations. Additionally, for user-related regional settings there is a hidden dependency on a lesser-known SharePoint OOTB timer job called the User Profile to SharePoint Language and Region Synchronization. This job runs every minute and is responsible for populating the RegionalSettings property on the SPUser object. If you’re using the standard OOTB supplied UI to set regional settings, then this shouldn’t be an issue. However, the moment you start using the API to set user profile properties and values is when this becomes a can of worms worthy of a blog post all its own.
Until then, hopefully this helps any other would-be Time Lords unravel their respective balls of wibbly wobbly, timey wimey,…stuff.
Now that we’ve teased you with a little knowledge, check out Pirooz Javan’s blog post on Extending Active Directory and Integrating into SharePoint 2013 (Part 1) and keep an eye out on Part 3 (Configuring User Profile Services to support Custom Attributes – coming soon!) which dovetails very nicely with this article. Meanwhile, check out our blog and read more about the technologies we use in our daily work:
Make yourself comfortable and check out our blog home page to explore other fixes we’ve solved in our day to day work. To make your life easier, subscribe to our blog to get instant updates sent straight to your inbox: