It's been over a year since I last blogged about DateTimes and nearly a decade since I blogged the first time on the subject! CRM DateTimes – so it’s well overdue that I update you on how DateTimes work with PCF.

My last post on the subject was when the ‘Timezone independent’ and ‘Date Only’ behaviours were introduced -DateTimes - It’s never the last word.

This made the time zone handling of dates much easier if you needed to store absolute date/times – however, there are always times where you need to store a date that is dependant on the user’s time zone (e.g. date/time a task is completed, etc.)

In PCF, it would have been nice if the time zone element of the date was handled for us – but unfortunately not!

There are 3 places where we have to consider datetime behaviours in PCF:

  • Field Controls

    • Inbound dates - When PCF calls updateView()

    • Outbound dates - When PCF calls getOutputs()

  • Dataset Controls - Inbound dates

Field Controls - Inbound dates

When the PCF passes our component a date as a bound property to the context via the updateView methods, the date will be provided as a formatted date string and also a raw Date object.

I have a record with the dateAndTimeField property bound to a DateTime field that has the User Local DateTime behaviour.

I can get the two values as follows:

  • Raw - parameters.dateAndTimeField.raw

  • Formatted - parameters.dateAndTimeField.formatted

There are two time zones I can vary, firstly the CDS User Settings (I have it set to GMT+8) and my local browser time zone. In the following table, I vary the browser time zone and keep the CDS time zone constant.

The formatted date is formatted using my CDS user settings – YYYY/MM/DD HH:mm

Local Time Zone: GTM GMT-3 GMT+8
CDS UTC 2020-05-10T04:30:00Z 2020-05-10T04:30:00Z 2020-05-10T04:30:00Z
Raw 2020-05-10 05:30:00 GMT+0100 2020-05-10 02:30:00 GMT-0200 2020-05-10 12:30:00 GMT+0800
Formatted 2020/05/10 12:30 2020/05/10 12:30 2020/05/10 12:30

You’ll notice that the formatted time is still 12:30 because it’s showing as the CDS UTC+8 date. Changing my local time zone shouldn’t change this. However, the Raw date is now showing as 12:30 because it’s converted to my local browser time zone, and what makes it more complex is that Daylight savings is also added - depending on the date in the year. JavaScript dates are awkward like this. Although the date is set to the UTC date by PCF – it is provided in the local time zone.

So why not use the formatted date?

To work with the date value (bind it to a calendar control etc.) we need it in the user’s CDS local time zone - that shown by the formatted date. If we are just showing the date and not editing it, then the formatted string is the way to go. However, if we want to edit the date, then we need to convert it to a Date object. This could be done by parsing the Formatted Date but that would require us to understand all the possible date formats that CDS has in the user settings. Instead we can simple apply the following logic:

  1. Convert to UTC to remove the browser timezone offset:
const localDate = getUtcDate(localDate)
getUtcDate(localDate: Date) {
    return  new  Date(
        localDate.getUTCFullYear(),
        localDate.getUTCMonth(),
        localDate.getUTCDate(),
        localDate.getUTCHours(),
        localDate.getUTCMinutes(),
    );
}
 
  1. Apply the user’s time zone offset. This requires access to the user’s time zone settings - luckily they are loaded for us in the PCF context:
convertDate(value: Date) {
    const offsetMinutes = this.context.userSettings.getTimeZoneOffsetMinutes(value);
    const localDate = addMinutes(value, offsetMinutes);
    return getUtcDate(localDate);
}
addMinutes(date: Date, minutes: number): Date {
    return new Date(date.getTime() + minutes * 60000);
}
 

This will now give us a Date that represents the correct Datetime in the browser local time zone - and can be used as a normal date!

Because some dates can be set as time zone independent, we can conditionally run this logic depending on the metadata provided:

convertToLocalDate(dateProperty: ComponentFramework.PropertyTypes.DateTimeProperty) {
    if (dateProperty.attributes?.Behavior == DateBehavior.UserLocal) {
        return this.convertDate(dateProperty.raw);
    } else {
        return this.getUtcDate(dateProperty.raw);
    }
}
 

We still need to convert to UTC even if the date is time zone independent - this is to remove the correction for the browser timezone.

Fields controls - outbound dates

Now we have a date time that is corrected for our local browser time zone, we can simply return the Date object from inside the getOutputs().
So if we wanted to set 12:30 - and our browser timezone is set to GMT-3 (Greenland) - then the date will actually be: 12:30:00 GMT-0200 (West Greenland Summer Time)
PCF ignores the timezone part of the date and then converts the date to UTC for us.

NOTE: It does seem odd that we have to convert to local inbound - but not back to UTC outbound.

Dataset controls - inbound dates

There are two notable differences when binding datasets to tables in PCF compared to the inbound values in their field counterparts.

  1. Dates that are provided by a dataset control binding are similar in that they are provided in the browser timezone - however they are strings and not Date objects.
  2. There is no information on the UserLocal/Timezone independant behaviour - and so we need to know about this in advance.

So as before, when binding to a datagrid, it’s easiest to use the formatted value:
item.getFormattedValue("dateAndTimeField")

If you need the Date object to edit the value - then you’ll need to convert to the local date as before - but with the added step of converting to a Date object:

const dateValue = item.getValue("dateAndTimeField");
const localDate = this.convertDate(dateValue);
 

This isn’t going to be the last I write on this subject I am sure of it! Anything that involves timezones is always tricky!
@ScottDurow