What every Salesforce developer should know about Dates and Times in Apex

Mehdi Maujood
Salesforce Zolo
Published in
10 min readFeb 16, 2019

Dates, times and computers have never been the best of friends. Just look at the list of famous bugs caused by miscalculations in Dates and Times: the famous y2k bugs which cost over $300 billion worldwide to address, the Deep Impact spacecraft which was lost because the time was stored in too small a variable, or the deadly Patriot Missile Failure which resulted in 28 deaths because the calculation was off by a small fraction of a second. It’s not just huge disasters, simple miscalculations can cause bugs like the ones reported recently in the Apple Watch causing crashes and reboots when Daylight Saving Time came into effect.

While Salesforce developers like you and me are unlikely to be faced with intercepting missiles or keeping track of spacecraft, we are likely to face angry users if we don’t handle dates and times properly. In this article, we’re going to list principles and practices when dealing with Dates, Times and DateTimes in Apex.

Rule 1: Know your time zones

Time zones aren’t fun. They’re even less fun when Daylight Saving Time (DST) is involved. Just ask the students in Ohio U. who went rioting after losing an hour of drinking time to DST. One of those Ohio U. students may be your Director of Sales today, so it’s best you have your time zones sorted out.

The two kinds of time zones

Time zones come in two varieties — region-based and offset-based. When you set your org’s default time zone to America/Chicago, for example, you’re referring to the time zone of a region. The time in this region would either be GMT-06:00 or GMT-05:00 depending on whether DST is in effect or not.

Then we have the offset varieties, such as Central Standard Time (GMT-06:00) and Central Daylight Time (GMT-05:00). These kind of time zones represent an offset from the Greenwich Mean Time, but don’t necessarily represent any particular region. The America/Chicago (or Central) region, for example, follows CST or CDT depending on the time of the year.

As you might guess, region-based time zones come with a problem. On November 3, 2019 at 02:00 AM America/Chicago, clocks “fall back” one hour. This means that November 3, 2019 at 01:30 AM in the America/Chicago region comes twice. Also, March 10, 2019 at 02:00 AM, the clocks are to “spring forward” one hour. This means that in the America/Chicago time zone, March 10, 2019 02:30 AM simply doesn’t exist.

Time zones in Salesforce

In Salesforce, we see region-based time zones very often. When you create a user, for example, you get to pick a region-based timezone for the user. You set your org’s default timezone to a region-based timezone as well. The timezone in my personal org, for example, is America/Los_Angeles. This is better than setting an offset-based time zone because Salesforce takes care of displaying the correct date and time to me whether Daylight Saving Time is in effect or not.

But we do still see offset-based time zones. If I build a DateTime object and call the format() method on it and specifically ask it to display the time zone, I would see an offset-based time zone. For example, if I run the following code in the console:

DateTime dt = DateTime.newInstance(2019, 01, 17, 5, 0, 0);
System.debug(dt.format('MM/dd/yyyy hh:mm zz'));

I would see 01/17/2019 05:00 PST in the debug logs.

Using an offset-based time zone for display makes sense because it removes the ambiguity associated with region-based time zones. November 3, 2019 at 01:30 AM appears twice in America/Los_Angeles but when displaying the two instances, adding PST or PDT can resolve that ambiguity.

And using a region-based time zone when setting a user or org’s time zone or when constructing an instance of DateTime makes sense because DST gets handled internally.

What time zones are available in Salesforce?

If you go to your Company Information page on setup, you would see a list of time zones. America/Chicago and America/Los_Angeles are examples of time zones available. The list of region-based time zones available in Apex, however, is far greater.

The official documentation says that Apex supports all time zones returned by TimeZone.getAvailableIDs method in Java. Where does Java get this list from? Currently, Java gets this from the tz database which is an ICANN-backed collabarative effort to maintain a list of all time zones. Since governments can redefine time zones any time, the database is constantly updated.

Rule 2: GMT is the model, everything else is a representation

Imagine you have an object that represents distance and stores distances as millimeters. You can create a new Distance instance by perhaps calling Distance.newInstance(2000) which creates a distance object representing a distance of 2000 millimeters, or 2 meters. When you call the getDistance() method on the distance record, it returns 2000. If you call format() on the distance record, while your locale is somewhere in the UK, it prints out 2m. If you call format() while your locale is somewhere in the US, it prints out 6 ft, 6.7402 in. What’s important to remember here is that millimeters is the model we have chosen, and 2m and 6 ft, 6.7402 in are representations.

What do you say when someone comes to you and says that they wanted the distance object to represent distance in meters instead of millimeters? and so they just did Distance.newInstance(2)? Now, when they call getDistance(), it returns 2 which is what they wanted.

You would rightfully be flabbergasted. That’s not how it works. The Distance objects uses millimeters as the model and if you’re working with meters, be sure to multiply it by 1000 before constructing the object. Or maybe add a newInstanceMeters(Integer meters) function to your Distance class that multiplies the passed meters by 1000 and returns a Distance object representing 2 meters as 2000 millimeters.

The GMT Model

With DateTime objects, the logic is the same. GMT is the model, and dates in all other time zones are representations. Apex actually makes this pretty easy for us — all DateTime objects we create are GMT DateTimes, but provide us a way to display it in any time zone. Just like our Distance class above which stores distance in millimeters, but can display it in any many different units.

Let’s do an experiment. My user in the org I’m using right now has a time zone of America/Los_Angeles, which is 8 hours behind GMT. What happens if I construct a date for February 10, 2019 12:00 PM? I’ll put the following code in my anonymous apex window:

DateTime dt = DateTime.newInstance(2019, 02, 10, 12, 0, 0);
System.debug(dt);

The DateTime instance we constructed represents a moment in time. The moment in time when it’s February 10, 2019 12:00 PM in Los Angeles, California; February 10, 2019 02:00 PM in Chicago, Illinois and February 10, 2019 08:00 PM in the GMT time zone.

When we construct this date, Salesforce assumes that we’re talking about a moment in time in the current user’s selected time zone, which in our case is America/Los_Angeles. What do we see in the console when we print out the date using System.debug?

2019-02-10 20:00:00

08:00 pm.

Let me add some more emphasis to that: EIGHT PEE EM.

Even though we constructed a DateTime for 12:00 PM.

What is the reason for this? Internally, Apex uses GMT as a model to store the date and time. Similar to how it doesn’t matter when we stored our distance as millimeters when we wanted to work with meters, it doesn’t matter that we store our DateTime as GMT, because all we want is to store a specific moment in time. It’s actually better that we use a consistent model to store our DateTime records.

And we can represent this moment in time in multiple ways, just like we could represent our distance as meters or feet and inches. Let’s run the following code:

DateTime dt = DateTime.newInstance(2019, 02, 10, 12, 0, 0);System.debug('Pacific Time: ' + dt.format('MM/dd/yyyy hh:mm a zz', 'America/Los_Angeles'));System.debug('Central Time: ' + dt.format('MM/dd/yyyy hh:mm a zz', 'America/Chicago'));System.debug('GMT: ' + dt.format('MM/dd/yyyy hh:mm a zz', 'Etc/GMT'));

This is the result in the debug log:

Pacific Time: 02/10/2019 12:00 PM PST
Central Time: 02/10/2019 02:00 PM CST
GMT: 02/10/2019 08:00 PM GMT

Additionally, do remember that the result would have been different if my user’s selected time zone was America/Central, because February 10, 12PM in Central Standard Time would translate to 6PM in GMT:

Pacific Time: 02/10/2019 10:00 PM PST
Central Time: 02/10/2019 12:00 PM CST
GMT: 02/10/2019 06:00 PM GMT

Rule 3: Always capture the correct moment in time

This rules is implied by the previous rule, but it’s so easy to get this wrong that I feel it deserves a special mention.

The Salesforce world is filled with people asking how they can construct a DateTime in their local time zone. Most people who ask this question end up adjusting the DateTime object to look like their local time. This is a piece of code I picked up from the developer forums — and it’s not uncommon to see something like this in the dev forums or on stack overflow:

Datetime gmt = DateTime.newInstance(2019, 02, 10, 12, 0, 0);
Integer offset = UserInfo.getTimezone().getOffset(gmt);
Datetime local = gmt.addSeconds(offset/1000);
System.Debug('GMT Time: ' + gmt);
System.Debug('Local Time: ' + local);

The following is the result of this code:

GMT Time: 2019-02-10 20:00:00
Local Time: 2019-02-10 12:00:00

The intent was to create a DateTime object that represents February 10, 2019 12:00 PM as a local DateTime. Do you see the problem? local does not represent the correct moment in time. Sure, local evaluates to February 10, 2019 12:00 PM, and that is what we intended to construct, but it represents 12PM GMT, not 12PM PST. This is the same mistake is doing Distance.newInstance(2) because you wanted to capture a distance of 2 meters.

So what is the right answer to the question of constructing a DateTime in your local time zone? The answer is that you can’t. And in most cases, you don’t need to anyway. What you need from DateTime is to capture a moment in time, and every moment can be captured in GMT. Make sure that your DateTimes represent the correct moment in time, and you can always display it in any time zone.

Rule 4: Store Date as Date, and Time as Time

It is a good rule of thumb to never use DateTime if you only need to represent a particular time or a particular date.

Let’s take an example. What happens when you, a user in the America/Chicago time zone, try to save Feb 10, 2019 in a DateTime field instead of a Date field? Maybe you’d save Feb 10, 2019 00:00 as a DateTime. Which gets saved as Feb 10, 2019 06:00 GMT. Which is all good. And what happens when your boss, who is in the America/Pacific Time Zone, loads up your custom page which displays this very important date to him? He sees February 9, 2019. Because Feb 10, 2019 06:00 GMT is February 9, 2019 22:00 PST. What happens next? Your boss, who graduated from Ohio U., goes rioting.

Because DateTime is translated to the user’s time zone and stored as GMT, it’s never a good idea to use it to only store only a Date or a Time. Always be sure to use the Date and Time types in Apex and the Date and Time fields on sObjects if you only need to store a date or a time.

Another case where you would want to use separate Date and Time objects or fields is when representing an event that takes place at a certain local time in more than one place. For example, if you want to store a deadline to submit documentation on February 1, 2019 at 5:00 PM no matter where you are (your team in New York submits at 5:00 PM Eastern Time and you team in Los Angeles submits at 5:00 PM Pacific Time), you would want to use separate date and time fields.

Remember, DateTime represents a specific moment in time that is automatically adjusted across different time zones. To store values that don’t adjust based on time zone, use separate Date and Time fields.

Rule 5: Be prepared if time zones are redefined

In 2005, the US passed a bill which would extend daylight saving time by one hour effective 2007. Instead of the first Sunday of April, DST would start coming into effect on the second Sunday of March. And it wasn’t pretty for us programmers.

Do you see why that’s a problem for us? This is a case where the GMT model doesn’t work. Suppose that before the 2005 announcement was made, maybe some time in 2004, you had constructed a DateTime representing March 20, 2008 09:00 AM in America/Chicago and saved it in a field. It would have been saved as March 20, 2008 03:00 PM GMT.

But in 2005, the US announces that DST in 2007 would begin on March 11 instead of April 1. Suddenly, March 20, 2008 03:00 PM GMT, which is the GMT date you have saved, doesn’t translate to March 20, 2008 09:00 AM in America/Chicago anymore.

A good solution to deal with situations like these would be to persist future DateTimes representing a particular local time in the local time zone, but unfortunately Salesforce only allows us to save DateTimes in GMT. We could talk about saving a local DateTime as a Date, Time and a String Timezone field, but the added complication and the lack of ability in Apex to directly construct a DateTime from data in any given timezone (I intend to write a blog on how to do this later), it might not be worth it.

Depending on the situation, what we can do is keep an eye out for changes in time zone rules and be ready to either inform users or update the saved dates in case the rules do change.

Conclusion

Almost everything discussed above becomes obvious if we follow just one principle: DateTime always has GMT as the model. You can construct a DateTime based on the local time zone (the time zone of the user) but it’s always saved as GMT. Another important fact to remember is that time zones are region-based and adjust automatically for DST. Just keep these basics in mind and you won’t go wrong working across time zones.

Are there any other rules or practices you use when dealing with dates and times in Apex? I would be happy to hear your thoughts.

--

--

Mehdi Maujood
Salesforce Zolo

Software engineer, Salesforce enthusiast, Pluralsight author