ColdFusion has plenty of date/time functions, however, it is lacking a robust set of native functions to calculate time stamps across time zones. Thankfully ColdFusion has a vibrant open-source community. In this article, I will walk you through the process of converting the client timestamp to the timestamp on the server for a given timezone using TimeZone.cfc.



Real-World Scenario

Galaxie Blog allows the author to select a post date in the past or in the future. When a future date is selected, Galaxie Blog will create a scheduled task to send the post to the subscriber base via email. However, we must convert the post date in the author's time zone to the proper time in the time zone of the server. We need to make sure that the emails are sent out at the right time if the time zones are different between the author and the server. To accomplish this we will use an open-source library, TimeZone.cfc.


Real World Example

Click the button below to see this example in action.


TimeZone.cfc

TimeZone.cfc is a small ColdFusion component that has many different methods to convert timestamp information between various timezones. This component allows for robust time zone conversion using Java's  java.util.TimeZone class.

It can be found on GitHub at https://github.com/rip747/TimeZone-CFC. However, the current version of TimeZone.cfc has a small bug caused by a nested comment and a small bug with CF2021.

I documented these bugs and made proposed changes on the original TimeZone repo and have forked my own TimeZone.cfc at https://github.com/GregoryAlexander77/TimeZone-CFC. Once you have downloaded the cfc you will need to upload it to the server. In this example, I placed the cfc in /blog/common/cfc/TimeZone.cfc.


Server-side function to convert a selected date and timezone between the client and the server

I created the following function to convert the timestamp on the client's device to the time found on the server. This is the same function that I am using in the demo below.

The required arguments are the date and time selected by the user, along with the author's time zone.

The TimeZone's getServerId() retrieves the time zone string found on the server. My server, hosted by Hostek, has the following identifier  'America/Chicago'.

Code on the client-side is sending the Time Zone's GMT Offset that the user selected. GMT is Greenwich Mean Time and is the time displayed by the Shepherd Gate Clock at the Royal Observatory in Greenwich, London. The GMT Offset is a numeric value that is used to determine the time from the GMT and is a string like so '-8'. If it is noon in London, you would subtract 8 hours to find the time in the Pacific Time Zone, which would be 4 AM, for example.

TimeZone cfc takes this offset value and extracts a list of time zone identifiers. The time zone identifier for my location is 'America/Los_Angeles' but you can select your own.

As there are many time zone identifiers for a given GMT Offset (-8 for example), and the blogTimeZoneList[1] code picks the first value found in this list. It is better to use the Time Zone Identifiers instead of calculating it using GMT Offset, but here we are using the GMT Offset for this example.

Near the bottom of the function, the convertTz function takes the date and timezone selected by the user and converts it to the date on the server. See the documentation in the TimeZone.cfc for more information.

<cffunction name="getServerDateTime" access="remote" returntype="date" returnformat="json" output="true"
		hint="Takes a date from the client and returns the date that it should be on the Server. This is used to schedule tasks on the server from the front end.">
	<cfargument name="selectedDate" type="date" required="true" />
	<cfargument name="selectedTime" type="date" required="true" />
	<cfargument name="timeZone" type="string" required="true" />

	<cfset thisDate = selectedDate & ' ' & selectedTime>

	<!--- Invoke the Time Zone cfc --->
	<cfobject component=".blog.common.cfc.TimeZone" name="TimeZoneObj">

	<!--- Get the time zone identifier on the server (ie America/Los_Angeles) from the TimeZone component --->
	<cfset serverTimeZoneId = TimeZoneObj.getServerId()>
	<!--- Get the blog time zone offset (-8) from the database and is populated by the Blog Time interface. We probably should be storing the actual identifier (America/Los_Angeles) in the database in the future to get the proper DST --->
	<cfset blogTimeZone = arguments.timeZone>
	<!--- Get the time zone identifier (America/Los_Angeles) by the GMT offset. This will pull up multiple options, but we just need a working identifier and will select the first one.  --->
	<cfset blogTimeZoneList = TimeZoneObj.getTZByOffset(blogTimeZone)>
	<!--- Get the first value in the array. We don't need this to be accurate, we just need a valid identifier to use. --->
	<cfset blogTimeZoneId = blogTimeZoneList[1]>

	<!--- Now use the convertTZ function to convert the blog time to server time. The blog time is the time zone of the site administrator that is writing the articles. We may want to add time zones for all blog users with the edit post role in the future. 
	convertTz(thisDate,	fromTZ, toTZ) --->
	<cfset serverDateTime = TimeZoneObj.convertTZ(thisDate, blogTimeZoneId, serverTimeZoneId)>

	<!--- Return it. --->
	<cfreturn serverDateTime>

</cffunction>

Client-Side Code

The server-side code found above should be a useful introduction to this library for most users, but for clarity, I will also include the client-side code. The client-side code uses the Kendo Core library, but you can use normal dropdowns and date pickers on the client-side if you choose. 


Client-Side Javascript

The following Javascript creates the necessary data and functionality of the dropdowns on the client.

The Kendo Time Picker widget prompts the user to select a valid date/time.

The Time Zone dropdown displays the label in the tzInts Javascript array, for example '(GMT-12:00) International Date Line West' and extracts the GMT Offset found in the selected value.

The getServerDateTime function sends these values using Ajax to the getServerDateTime function (found above) on the server.

The serverDateTimeResult function returns the getServerDateTime value back to the client and populates the serverTime HTML form.

<script>

var todaysDate = new Date();

// Kendo Dropdowns
// Date/time picker			
$("#selectedDate").kendoDateTimePicker({
	componentType: "modern",
	value: todaysDate
});

var tzInts = [
	{"label":"(GMT-12:00) International Date Line West","value":"-12"},
	{"label":"(GMT-11:00) Midway Island, Samoa","value":"-11"},
	{"label":"(GMT-10:00) Hawaii","value":"-10"},
	{"label":"(GMT-09:00) Alaska","value":"-9"},
	{"label":"(GMT-08:00) Pacific Time (US & Canada)","value":"-8"},
	{"label":"(GMT-08:00) Tijuana, Baja California","value":"-8"},
	{"label":"(GMT-07:00) Arizona","value":"-7"},
	{"label":"(GMT-07:00) Chihuahua, La Paz, Mazatlan","value":"-7"},
	{"label":"(GMT-07:00) Mountain Time (US & Canada)","value":"-7"},
	{"label":"(GMT-06:00) Central America","value":"-6"},
	{"label":"(GMT-06:00) Central Time (US & Canada)","value":"-6"},
	{"label":"(GMT-05:00) Bogota, Lima, Quito, Rio Branco","value":"-5"},
	{"label":"(GMT-05:00) Eastern Time (US & Canada)","value":"-5"},
	{"label":"(GMT-05:00) Indiana (East)","value":"-5"},
	{"label":"(GMT-04:00) Atlantic Time (Canada)","value":"-4"},
	{"label":"(GMT-04:00) Caracas, La Paz","value":"-4"},
	{"label":"(GMT-04:00) Manaus","value":"-4"},
	{"label":"(GMT-04:00) Santiago","value":"-4"},
	{"label":"(GMT-03:30) Newfoundland","value":"-3.5"},
	{"label":"(GMT-03:00) Brasilia","value":"-3"},
	{"label":"(GMT-03:00) Buenos Aires, Georgetown","value":"-3"},
	{"label":"(GMT-03:00) Greenland","value":"-3"},
	{"label":"(GMT-03:00) Montevideo","value":"-3"},
	{"label":"(GMT-02:00) Mid-Atlantic","value":"-2"},
	{"label":"(GMT-01:00) Cape Verde Is.","value":"-1"},
	{"label":"(GMT-01:00) Azores","value":"-1"},
	{"label":"(GMT+00:00) Casablanca, Monrovia, Reykjavik","value":"0"},
	{"label":"(GMT+00:00) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London","value":"0"},
	{"label":"(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna","value":"1"},
	{"label":"(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague","value":"1"},
	{"label":"(GMT+01:00) Brussels, Copenhagen, Madrid, Paris","value":"1"},
	{"label":"(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb","value":"1"},
	{"label":"(GMT+01:00) West Central Africa","value":"1"},
	{"label":"(GMT+02:00) Amman","value":"2"},
	{"label":"(GMT+02:00) Athens, Bucharest, Istanbul","value":"2"},
	{"label":"(GMT+02:00) Beirut","value":"2"},
	{"label":"(GMT+02:00) Cairo","value":"2"},
	{"label":"(GMT+02:00) Harare, Pretoria","value":"2"},
	{"label":"(GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius","value":"2"},
	{"label":"(GMT+02:00) Jerusalem","value":"2"},
	{"label":"(GMT+02:00) Minsk","value":"2"},
	{"label":"(GMT+02:00) Windhoek","value":"2"},
	{"label":"(GMT+03:00) Kuwait, Riyadh, Baghdad","value":"3"},
	{"label":"(GMT+03:00) Moscow, St. Petersburg, Volgograd","value":"3"},
	{"label":"(GMT+03:00) Nairobi","value":"3"},
	{"label":"(GMT+03:00) Tbilisi","value":"3"},
	{"label":"(GMT+03:30) Tehran","value":"3.5"},
	{"label":"(GMT+04:00) Abu Dhabi, Muscat","value":"4"},
	{"label":"(GMT+04:00) Baku","value":"4"},
	{"label":"(GMT+04:00) Yerevan","value":"4"},
	{"label":"(GMT+04:30) Kabul","value":"4.5"},
	{"label":"(GMT+05:00) Yekaterinburg","value":"5"},
	{"label":"(GMT+05:00) Islamabad, Karachi, Tashkent","value":"5"},
	{"label":"(GMT+05:30) Sri Jayawardenapura","value":"5.5"},
	{"label":"(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi","value":"5.5"},
	{"label":"(GMT+05:45) Kathmandu","value":"5.75"},
	{"label":"(GMT+06:00) Almaty, Novosibirsk","value":"6"},{"label":"(GMT+06:00) Astana, Dhaka","value":"6"},
	{"label":"(GMT+06:30) Yangon (Rangoon)","value":"6.5"},
	{"label":"(GMT+07:00) Bangkok, Hanoi, Jakarta","value":"7"},
	{"label":"(GMT+07:00) Krasnoyarsk","value":"7"},
	{"label":"(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi","value":"8"},
	{"label":"(GMT+08:00) Kuala Lumpur, Singapore","value":"8"},
	{"label":"(GMT+08:00) Irkutsk, Ulaan Bataar","value":"8"},
	{"label":"(GMT+08:00) Perth","value":"8"},
	{"label":"(GMT+08:00) Taipei","value":"8"},
	{"label":"(GMT+09:00) Osaka, Sapporo, Tokyo","value":"9"},
	{"label":"(GMT+09:00) Seoul","value":"9"},
	{"label":"(GMT+09:00) Yakutsk","value":"9"},
	{"label":"(GMT+09:30) Adelaide","value":"9.5"},
	{"label":"(GMT+09:30) Darwin","value":"9.5"},
	{"label":"(GMT+10:00) Brisbane","value":"10"},
	{"label":"(GMT+10:00) Canberra, Melbourne, Sydney","value":"10"},
	{"label":"(GMT+10:00) Hobart","value":"10"},
	{"label":"(GMT+10:00) Guam, Port Moresby","value":"10"},
	{"label":"(GMT+10:00) Vladivostok","value":"10"},
	{"label":"(GMT+11:00) Magadan, Solomon Is., New Caledonia","value":"11"},
	{"label":"(GMT+12:00) Auckland, Wellington","value":"12"},
	{"label":"(GMT+12:00) Fiji, Kamchatka, Marshall Is.","value":"12"},
	{"label":"(GMT+13:00) Nuku'alofa","value":"13"}
]	

// timezone dropdown
var timeZoneDropdown = $("#timeZoneDropdown").kendoDropDownList({
	optionLabel: "Select...",
	dataTextField: "label",
	dataValueField: "value",
	filter: "contains",
	dataSource: tzInts,
	change: onTimeZoneChange,
}).data("kendoDropDownList");

// Save the selected date in a hidden field.
function saveTimeZoneValue(timeZoneId){
	$("#timeZoneValue").val(timeZoneId);
}

// Calculate the server time
function onTimeZoneChange(e){
	// Get the value
	getServerDateTime();
}//...function onBlogTimeZoneChange(e)

function getServerDateTime(){

	jQuery.ajax({
		type: 'post', 
		url: '<cfoutput>#application.baseUrl#</cfoutput>/demo/Demo.cfc?method=getServerDateTime',
		data: { // arguments
			selectedDate: kendo.toString($("#selectedDate").data("kendoDateTimePicker").value(), 'MM/dd/yyyy'),
			selectedTime: kendo.toString($("#selectedDate").data("kendoDateTimePicker").value(), 'hh:mm tt'),
			timeZone: $("#timeZoneValue").val()
		},
		dataType: "json",
		success: serverDateTimeResult, // calls the result function.
		error: function(ErrorMsg) {
			console.log('Error' + ErrorMsg);
		}
	// Extract any errors. This is a new jQuery promise based function as of jQuery 1.8.
	}).fail(function (jqXHR, textStatus, error) {
		// The full response is: jqXHR.responseText, but we just want to extract the error.
		$.when(kendo.ui.ExtAlertDialog.show({ title: "Error while consuming the getServerDateTime function", message: error, icon: "k-ext-error", width: "<cfoutput>#application.kendoExtendedUiWindowWidth#</cfoutput>" }) // or k-ext-error, k-ext-information, k-ext-question, k-ext-warning.  You can also specify height.
			).done(function () {
			// Do nothing
		});
	});//...jQuery.ajax({
};

function serverDateTimeResult(response){
	// alert(response);
	// The server is returning the time. Save this into the form for display
	$("#serverTime").val(response);
}

Client-Side HTML

<div class="content k-content">
<table align="center" class="k-content" width="100%" cellpadding="2" cellspacing="0">
  <input type="hidden" name="selectedDateValue" id="selectedDateValue" value="">
  <input type="hidden" name="timeZoneValue" id="timeZoneValue" value="">
  <input type="hidden" name="serverTimeZoneValue" id="serverTimeZoneValue" value="">
  <cfsilent>
	<!---The first content class in the table should be empty. --->
	<cfset thisContentClass = HtmlUtilsObj.getKendoClass('')>
	<!--- Set the colspan property for borders --->
	<cfset thisColSpan = "2">
  </cfsilent>

  <tr height="1px">
	  <td align="left" valign="top" colspan="2" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
  <tr height="1px">
	  <td></td>
	  <td align="left" valign="top" class="<cfoutput>#thisContentClass#</cfoutput>">
		Your hosting provider or server may reside in a different time-zone. These settings are critical when this is the case. If your server is in a different time-zone, you will want the post date to show the  time that you are in- not necessarilly where the server is.
	  </td>
  </tr>
  <!-- Form content -->
<cfif session.isMobile>
<tr valign="middle">
<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
	<label for="selectedDate">Date</label>
</td>
</tr>
<tr>
<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
	<input id="selectedDate" name="selectedDate" value="<cfoutput>#dateTimeFormat(selectedDate, 'medium')#</cfoutput>" style="width: <cfif session.isMobile>95<cfelse>45</cfif>%" /> 
</td>
</tr>
<cfelse><!---<cfif session.isMobile>--->
<tr valign="middle" height="30px">
<td align="right" valign="middle" class="<cfoutput>#thisContentClass#</cfoutput>" width="20%">
	<label for="selectedDate">Date</label>
</td>
<td align="left" class="<cfoutput>#thisContentClass#</cfoutput>">
	<input id="selectedDate" name="selectedDate" value="<cfoutput>#dateTimeFormat(now(), 'medium')#</cfoutput>" style="width: <cfif session.isMobile>95<cfelse>45</cfif>%" /> 
</td>
</tr>
</cfif>
  <!-- Border -->
  <tr height="2px"> 
	  <td align="left" valign="top" colspan="<cfoutput>#thisColSpan#</cfoutput>" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
<cfif session.isMobile>
  <tr valign="middle">
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<label for="timeZoneDropdown">Your time-zone:</label>
	</td>
   </tr>
   <tr>
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<select id="timeZoneDropdown" name="timeZoneDropdown" style="width:95%" onchange="saveTimeZoneValue(this.value)"></select>
	</td>
  </tr>
<cfelse><!---<cfif session.isMobile>---> 
  <tr>
	<td align="right" class="<cfoutput>#thisContentClass#</cfoutput>" style="width: 20%"> 
		<label for="timeZoneDropdown">Your time-zone:</label>
	</td>
	<td class="<cfoutput>#thisContentClass#</cfoutput>">
		<select id="timeZoneDropdown" name="timeZoneDropdown" style="width:50%" onchange="saveTimeZoneValue(this.value)"></select>
	</td>
  </tr>
</cfif>	  
  <!-- Border -->
  <tr height="2px">
	  <td align="left" valign="top" colspan="<cfoutput>#thisColSpan#</cfoutput>" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
  <cfsilent>
  <!--- Set the class for alternating rows. --->
  <!---After the first row, the content class should be the current class. --->
  <cfset thisContentClass = HtmlUtilsObj.getKendoClass(thisContentClass)>
  </cfsilent>
<cfif session.isMobile>
  <tr valign="middle">
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<label for="serverTime">Hostek Server Time:</label>
	</td>
   </tr>
   <tr>
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<input type="text" id="serverTime" name="serverTime" value="" class="k-textbox" required> (Oklahoma)
	</td>
  </tr>
<cfelse><!---<cfif session.isMobile>---> 
  <tr>
	<td align="right" class="<cfoutput>#thisContentClass#</cfoutput>" style="width: 20%"> 
		<label for="serverTime">Hostek Server Time:</label>
	</td>
	<td class="<cfoutput>#thisContentClass#</cfoutput>">
		<input type="text" id="serverTime" name="serverTime" value="" class="k-textbox" required> (Oklahoma)
	</td>
  </tr>
</cfif>
</table>