Date Time and Timezone Handling in Apex
mgsmith | Tuesday, August 10th, 2010 | 5 Comments »I recently had to do some work converting timezones in Apex. Unfortunately, Apex is missing some key conversion functionality when it comes to handling times. I created this class to handle figuring out the time-offset of the current user (hardcoded to Eastern Time) and then use that to convert a time in another timezone. It also has a couple of handy methods for retrieving DST start/end dates and getting a list of US timezones with GMT offset.
Hopefully someone else out there will find this useful if they need to handle datetime conversions in their Apex code.
/* **********************************************************************************************
* TimeConversions Class
* Created by: Michael Smith/Force2b, 04/06/2010
*
************************************************************************************************ */
global class TimeConversions {
/* -------------------------------------------------------------------------------------
* Returns an Integer of the Timezone Offset from Eastern Time for the
* currently logged in user
*
* This is used to convert the String DateTime (that is in Eastern Time) into a
* DateTime value in SalesForce. The default behavior of SFC converts the string into
* a local datetime value, but we need to get into Eastern Time.
* ------------------------------------------------------------------------------------- */
public Integer getCurrentUserTZOffsetFromEastern() {
Map<String, Integer[]> tzSIDKeys = getTZSidKeys();
User user = [SELECT ID, TimeZoneSidKey FROM User WHERE ID = :UserInfo.getUserId() LIMIT 1];
Date[] dstDatesNow = getDSTDates(System.Today().year());
Integer UsersTZOffset = 0;
if (tzSIDKeys.get(user.TimeZoneSidKey) != null) {
// Get the base timezone offset from GMT for the user
if (System.Today() >= dstDatesNow[0] && System.Today() <= dstDatesNow[1]) UsersTZOffset = tzSIDKeys.get(user.TimeZoneSidKey)[1];
else UsersTZOffset = tzSIDKeys.get(user.TimeZoneSidKey)[0];
system.debug(LoggingLevel.Error, 'Base TimeZone for Current User=' + user.TimeZoneSidKey + '/' + UsersTZOffset );
// Now make it a timezone offset from EASTERN time
Integer EasternTZOffset = 0;
if (System.Today() >= dstDatesNow[0] && System.Today() <= dstDatesNow[1]) EasternTZOffset = tzSIDKeys.get('America/New_York')[1];
else EasternTZOffset = tzSIDKeys.get('America/New_York')[0];
UsersTZOffset = Math.abs(EasternTZOffset) - Math.abs(UsersTZOffset);
system.debug(LoggingLevel.Error, 'TimeZone Offset to Eastern Time=' + UsersTZOffset );
}
return UsersTZOffset ;
}
/* -------------------------------------------------------------------------------------
* Returns a String Collection of the Timezone Codes based on the Timezone Offset Passed
* for the date passed.
*
* Based on a table from: http://en.wikipedia.org/wiki/Zone.tab
*
* getTimeZoneCode[0] = Display Text (ex: EDT)
* getTimeZoneCode[1] = DateTime.Format() parameter (ex: America/New_York)
* ------------------------------------------------------------------------------------- */
public string[] getTimeZoneCode(Integer tzOffset, Date theDate, Boolean isDSTObserved) {
Date[] dstDates = getDSTDates(theDate.year()); // [0]=startDate, [1]=endDate
boolean isDSTOn = (theDate >= dstDates[0] && theDate <= dstDates[1]);
if (tzOffset == 0) return new String[]{' GMT', 'Europe/London' };
else if (tzOffset == 4) return new String[]{' AST (UTC-04)', 'America/Puerto_Rico' };
else if (tzOffset == 5 && isDSTOn && isDSTObserved) return new String[]{' EDT (UTC-04)', 'America/New_York' };
else if (tzOffset == 5) return new String[]{' EST (UTC-05)', 'America/New_York' };
else if (tzOffset == 6 && isDSTOn && isDSTObserved) return new String[]{' CDT (UTC-05)', 'America/Chicago' };
else if (tzOffset == 6) return new String[]{' CST (UTC-06)', 'America/Chicago' };
else if (tzOffset == 7 && !isDSTObserved) return new String[]{' MST (UTC-07)', 'America/Phoenix' };
else if (tzOffset == 7 && isDSTOn && isDSTObserved) return new String[]{' MDT (UTC-06)', 'America/Denver' };
else if (tzOffset == 7) return new String[]{' MST (UTC-07)', 'America/Denver' };
else if (tzOffset == 8 && isDSTOn && isDSTObserved) return new String[]{' PDT (UTC-07)', 'America/Los_Angeles' };
else if (tzOffset == 8) return new String[]{' PST (UTC-08)', 'America/Los_Angeles' };
else if (tzOffset == 9 && isDSTOn && isDSTObserved) return new String[]{' AKDT (UTC-08)', 'America/Anchorage' };
else if (tzOffset == 9) return new String[]{' AKST (UTC-09)', 'America/Anchorage' };
else if (tzOffset == 10 && !isDSTObserved) return new String[]{' HST (UTC-10)', 'Pacific/Honolulu' };
else if (tzOffset == 10 && isDSTOn && isDSTObserved) return new String[]{' HDT (UTC-09)', 'America/Adak' };
else if (tzOffset == 10) return new String[]{' HST (UTC-10)', 'America/Adak' };
else if (tzOffset == 11) return new String[]{' HST (UTC-10)', 'Pacific/Pago_Pago' };
else return new String[]{' UTC-' + tzOffset, 'GMT' };
}
/* -------------------------------------------------------------------------------------
* Returns a date Collection of Start/End dates for US Daylight Saving Time
* for the specified year.
*
* Based on code from: http://www.webexhibits.org/daylightsaving/b2.html
* ------------------------------------------------------------------------------------- */
public Date[] getDSTDates(Integer theYear) {
Long thisYear;
Long AprilDate;
Long OctoberDate;
Long MarchDate;
Long NovemberDate;
Long longSeven = 7;
thisYear = Math.round(theYear);
AprilDate = Math.mod(2+6 * thisYear - Math.floor(thisYear / 4).longValue(), longSeven) + 1;
OctoberDate= Math.mod(31-( Math.floor(thisYear * 5 / 4).longValue() + 1), longSeven);
MarchDate = 14 - Math.mod(Math.floor(1 + thisYear * 5 / 4).LongValue(), longSeven);
NovemberDate = 7 - Math.mod(Math.floor (1 + thisYear * 5 / 4).LongValue(), longSeven);
string startDate = (thisYear > 2006 ? ('03/'+MarchDate) : ('04/'+AprilDate)) + '/' + thisYear;
string endDate = (thisYear > 2006 ? ('11/'+NovemberDate):('10/'+OctoberDate))+ '/' + thisYear;
Date[] rtnDates = new List<Date>();
rtnDates.add(Date.parse(startDate));
rtnDates.add(Date.parse(endDate));
return rtnDates;
}
public Map<String, Integer[]> getTZSidKeys() {
Map<String, Integer[]> tzSIDKeys = new Map<String, Integer[]>();
tzSIDKeys.put('America/Adak', new Integer[]{-10, -9});
tzSIDKeys.put('America/Anchorage', new Integer[]{-9, -8});
tzSIDKeys.put('America/Chicago', new Integer[]{-6, -5});
tzSIDKeys.put('America/Denver', new Integer[]{-7, -6});
tzSIDKeys.put('America/Detroit', new Integer[]{-5, -4});
tzSIDKeys.put('America/Halifax', new Integer[]{-4, -3});
tzSIDKeys.put('America/Indianapolis', new Integer[]{-5, -4});
tzSIDKeys.put('America/Los_Angeles', new Integer[]{-8, -7});
tzSIDKeys.put('America/Montreal', new Integer[]{-5, -4});
tzSIDKeys.put('America/New_York', new Integer[]{-5, -4});
tzSIDKeys.put('America/Panama', new Integer[]{-5, -5});
tzSIDKeys.put('America/Phoenix', new Integer[]{-7, -7});
tzSIDKeys.put('America/Puerto_Rico', new Integer[]{-4, -4});
tzSIDKeys.put('America/Toronto', new Integer[]{-5, -4});
tzSIDKeys.put('America/Vancouver', new Integer[]{-8, -7});
tzSIDKeys.put('Europe/London', new Integer[]{0, 1});
tzSIDKeys.put('Pacific/Honolulu', new Integer[]{-10, -10});
tzSIDKeys.put('Pacific/Pago_Pago', new Integer[]{-11, -11});
return tzSIDKeys;
}
}

Phenomenal Mike! I was just working on something like this myself.
Very cool stuff Mike! If everyone would just use same currency, time zone and language then things would be much easier for us.
So true! Until then, we get to have fun with coding for it.
We have an instance where a visual force page is being served from a Salesforce site. How is user timezone determined? Will that be Guest user timezone?
You’re correct, this Apex would pull the time zone of the Guest Salesforce user. You’d have to use JavaScript to get the timezone of the visitors local machine and find a way to do some coding either in JS, or pass the time zone back to Apex to use in that same class.
Best Regards,
Mike