Time(zones), why do you punish me?
When it comes to Date values, we typically think of them as timezone agnostic. However, when comparing a Date value to Today’s date, the timezone does matter. When “Today” ends depends on where you live and if you’re not careful, you could be locking out users from a deadline sooner than you expect.
For example, let’s say you are accepting applications until 7/31/2019 and your Apex controller validates this using System.today:
Boolean deadlineHasPassed(Date deadline) {
return deadline < System.today();
}
If your applicant’s user is in the GMT timezone, that means they will be locked out if they apply any time after 5pm PST on 7/31. Meanwhile, anyone with the PST timezone will have an extra 7 hours to apply. To give everyone an equal time period, you may want the deadline to actually be until midnight on 7/31 in PST.
You could easily fix this by changing the deadline to a Datetime field type so that you can take timezones into account, but that’s not always an option. Your deadline could be based on an standard field like EndDate on Campaign and you typically don’t want to replace a standard field with a custom one. You run the risk of other integrations relying on the standard field being populated, so you put yourself in a position where you have to constantly keep your custom field in sync with the standard one.
As a proxy, you could use the timezone of the org to figure out when midnight is and compare that to the current time. Sounds easy, right? Well as I quickly found out, there aren’t any standard Apex methods that allow you to get a time based on a specified timezone. So here’s what I needed to do:
-
Figure out the current time using the
Datetime.now().getTime()
method, which returns you the number of milliseconds January 1, 1970, 00:00:00 GMT. This is an absolute time that is unaffected by timezone. -
Instantiate a DateTime object using the midnight of the deadline given the org’s timezone, convert that to GMT, and then use getTime() to compare it to the current time.
I had some trouble find the code on how to do this, so here’s mine:
Boolean deadlineHasPassed(Date deadline) {
Timezone orgTimezone = Timezone.getTimeZone([SELECT TimeZoneSidKey FROM Organization][0].TimeZoneSidKey);
Time midnight = Time.newInstance(23,59,59,999);
//Get the number of seconds offset between GMT and the org's timezone
Integer offsetSeconds = orgTimezone.getOffset(deadline) / 1000;
Long deadlineTime =
Datetime.newInstanceGMT(endDate, endTime) //Get midnight in GMT
.addSeconds(-offsetSeconds) //Add the offset seconds so that you can get midnight of your org's timezone in GMT time
.getTime();
Long currentTime = Datetime.now().getTime() // get the current time;
return deadlineTime < currentTime;
}
Now you can compare the current time with the end of a day without having to worry about timezones! I hope this saves someone some time (pun definitely intended).
Stop Fixing My Features!
When I hear the phrase “That’s not a bug, that’s a feature!” I typically imagine that the speaker is a developer defending their code. But sometimes it’s the end user that’s taking advantage of that bug to get the system to do what they want.
Intention vs. Actual Use
Consider the following pseudocode for validating an amount on a line item:
if (lineItem.value == null || lineIem.value < 0) {
throw LineItemException('Line item value must be populated and cannot be 0 or less');
}
The if statement only prevents values that are null
or less than 0, but it actually allows a value of 0. The exception message, however, indicates that 0 is also invalid. To me, that exception message indicates the true intention of the code, meaning there must be a bug in the if statement. Noticing this, I went ahead and fixed the if statement to include 0:
if (lineItem.value == null || lineIem.value <= 0)
However, when this code was released, a bug was reported from the end user.
Something changed in the last release, I can no longer add line items with a 0 value. We’ve been doing this to adjust Opportunities that went from Closed Won to Closed Lost so that we can capture the fact that the line items were part of a deal that happened, but didn’t actually get sold.
My first instinct was to respond with “No, you can’t use it like that, you need to change your process.” This is also probably why I am not a product manager. But that’s not necessarily the correct response. That customer may have thousands of line items like this and this process may be ingrained in their training. They may have built customization that relies on this “feature”.
Just because a bug exists doesn’t necessarily means you should fix it. Imagine if Salesforce decided to completely disable URL hacking. Sure, they may not be officially supported, but they are used by countless orgs! I’ve seen at least one in every single Salesforce org I have ever worked in. So while it’s not necessarily a “feature”, it has become one that customers rely on. So when you should fix a bug?
To Fix or Not To Fix?
Before fixing a bug, you should do some research as to how pervasive the bug is. Here are some questions I like to ask myself when it comes to fixing bugs:
- What will break as a result of this bug? i.e. What would break if line items had a value of 0?
- How pervasive is the bug? i.e. How many line items actually have a value of 0?
- Is there a work around for this bug or does it create a blocker?
- How many people does this bug effect?
And now, something I didn’t ask before:
- What would break if we DO fix this bug?
It’s an uncomfortable question as the answer can go against the vision of what your product should do. Do you fix the bug and risk angering the customer or do you leave the bug and possibly create future constraints on what the product can do in the future? Of course, the answer will vary from case to case as you weigh out each alternative. But next time you find yourself thinking you should quickly fix something that you run across, stop and ask yourself if someone thinks that bug is a feature.
A JavaScript Crash Course from a Apex Dev's Point of View - Part 4
At TrailheaDX ‘19, I was fortunate enough to do a talk on learning JavaScript fundamentals from an Apex developer’s point of view. This series covers the topics from that talk
We (hopefully) demystifyied the this
keyword in the last post. Now we’ll switch gears and focus on debugging differences between Apex and JavaScript, and how it can change your thoughts on how to debug your code.
Reactive Debugging
In Apex, generally you are doing what I like to call “reactive” debugging. Using a System.debug
and System.assert
calls, you generally run your code and examine the results after the fact. The Apex Replay Debugger sort of simulates interactive debugging, but even with that tool you are looking at something that has already happened.
I often see Apex developers do the same thing when working with JavaScript using console.log
. For example, this is a simple function that accepts an array of contacts and returns an array of the contacts’ full names:
function getFullNames(contacts) {
console.log(contacts);
return contacts.map( (x) => {
x.FirstName + ' ' + x.LastName;
});
}
What I will generally see is a developer use the console.log
function, run the code, look at the console, and repeat. Debugging like that can be helpful, but it’s only a fraction of what you can do in JavaScript. Since front-end JavaScript is running on your machine as opposed to a cloud server, you have more powerful debugging tools at your disposal.
Interactive Debugging.
Let’s take the scenario when you have to debug a front-end JavaScript issue in production, so adding console.log
calls to your source isn’t really a feasible option. With JavaScript, the Chrome browser gives you tools to interact with your code while it is running without having to modify the source code.
First you need to enable Debug Mode for Lightning Components. By doing so, JavaScript won’t be minified, making it easier to read and interact with.
//unminified
function getFullNames(contacts) {
console.log(contacts);
return contacts.map( (x) => {
x.FirstName + ' ' + x.LastName;
});
}
//minified
function getFullNames(e){return console.log(e),e.map(a=>{a.FirstName,a.LastName})}
Then, open up the Developer Tools in Chrome and go the Source tab. When you click the line numbers, you can add a breakpoint. When the code runs, the browser will actually freeze on at the line. You can see the values of variables and you when you are done you can press the play button in the top right to continue running.
You can also interact with the variables and even change them in the Console tab.
This is just some of the basic stuff you can do, so make sure to check out the Chrome Dev Tools
documentation for more cool features.
What’s fantastic about this is that this makes it easier to interact with production code. You don’t have to deploy anything to debug, you can just interact with it directly in the browser. This is really useful for those scenarios where something works in a sandbox but not in production - you can debug closer to the actual problems as they are actively happening.
That concludes this crash course in JavaScript development. I hope that provides you with a foundation to continue leveraging the best of JavaScript on the Salesforce platform and you find yourself a little more fluent when reading others’ JavaScript code.