Enhance Time Accuracy with the Alexa Settings API

Step 1: Get and pass the device ID to the API

If in your skill, Alexa says, "Good morning" at 3 p.m. or "Good evening" at 3 a.m., the experience won't feel natural or conversational. Instead it will feel "off". However, the time information you'll get from an AWS Lambda server is generally going to be Greenwich Mean Time (GMT), no matter where the customer's device is located.

Luckily, you can use the Alexa Settings API to get the information you need to calculate the correct time for each customer's device.

You complete this task in two parts:

  • First, add the API Client
  • Then, retrieve the device's time zone, then add a time-appropriate greeting

First, add the API Client

  1. On the Code tab, select the index.js or lambda_function.py file, and scroll down to the bottom of the file.

  2. Above the very last line:

.lambda()

Copy and paste the following code.

.withApiClient(new Alexa.DefaultApiClient())

You have now added the API client, much the same way you added your persistence adapter and interceptors.

  1. To add the rest of the code to your LaunchRequestHandler, scroll back up to near the top of the index.js or lambda_function.py file.

The StandardSkillBuilder that we added in Module 5 already has the Default API Client. No further steps are needed.

Then, retrieve the device's time zone, then add a time-appropriate greeting

This sub-step has many parts for a single step. However, the parts are mostly contained in one contiguous code block, so you can add it all at one time. After you add the code, you'll walk back through the parts to understand what they do.

  1. Locate the LaunchRequestHandler near the top, and then locate the handle() function within the handler.

  2. Replace the following code.

handle(handlerInput) {
def handle(self, handler_input):
    # type: (HandlerInput) -> Response

With the following code, by copying and pasting it into the index.js or lambda_function.py file.

async handle(handlerInput) {
    //set up our Settings api foundations
    const serviceClientFactory = handlerInput.serviceClientFactory;
    const deviceId = handlerInput.requestEnvelope.context.System.device.deviceId;

    // initialize some variables
    var userTimeZone, greeting;

    // wrap the API call in a try/catch block in case the call fails for
    // whatever reason.
    try {
        const upsServiceClient = serviceClientFactory.getUpsServiceClient();
        userTimeZone = await upsServiceClient.getSystemTimeZone(deviceId);
    } catch (error) {
        userTimeZone = "error";
        console.log('error', error.message);
    }

    // calculate our greeting
    if(userTimeZone === "error"){
        greeting = "Hello.";
    } else {
        // get the hour of the day or night in your customer's time zone
        const cfunctions = await require('./celebrityFunctions.js');
        var hour = cfunctions.getHour(userTimeZone);
        if(0<=hour&&hour<=4){
            greeting = "Hi night-owl!"
        } else if (5<=hour&&hour<=11) {
            greeting = "Good morning!"
        } else if (12<=hour&&hour<=17) {
            greeting = "Good afternoon!"
        } else if (17<=hour&&hour<=23) {
            greeting = "Good evening!"
        } else {
            greeting = "Howdy pardner!"   
        }
    }
def handle(self, handler_input):
    # type: (HandlerInput) -> Response

    device_id = handler_input.request_envelope.context.system.device.device_id

    user_time_zone = ""
    greeting = ""

    try:
        user_preferences_client = handler_input.service_client_factory.get_ups_service()
        user_time_zone = user_preferences_client.get_system_time_zone(device_id)
    except Exception as e:
        user_time_zone = 'error.'
        logger.error(e)

    if user_time_zone == 'error':
        greeting = 'Hello.'
    else:
        # get the hour of the day or night in your customer's time zone
        from celebrityFunctions import get_hour
        hour = get_hour(user_time_zone)
        if 0 <= hour and hour <= 4:
            greeting = "Hi night-owl!"
        elif 5 <= hour and hour <= 11:
            greeting = "Good morning!"
        elif 12 <= hour and hour <= 17:
            greeting = "Good afternoon!"
        elif 17 <= hour and hour <= 23:
            greeting = "Good evening!"
        else:
            greeting = "Howdy pardner!"

Now, you'll learn the details of the code you just added.

async handle(handlerInput) {

First, you added async because you now need to await the results of an API call in this function. Then, you set up some foundations for the API call.

const serviceClientFactory = handlerInput.serviceClientFactory;
const deviceId = handlerInput.requestEnvelope.context.System.device.deviceId;

You added the serviceClientFactory feature of the SDK so that you can create the client you need for the API service you call. It saves you from having to know a lot of smaller configuration details and add them to the script. Then, you have to pass the client the device ID, which is sent with every request in the requestEnvelope. Next, you initialize a couple of variables that you'll be using.

var userTimeZone, greeting;

Then, you make the API call. The call could time out or run into another network error, so you wrap it in a "try/catch" code block. The API tries the code. If it succeeds, the code moves on as normal. If it fails, the API has code to handle the failure instead of allowing everything to break.

try {
  const upsServiceClient = serviceClientFactory.getUpsServiceClient();
  userTimeZone = await upsServiceClient.getSystemTimeZone(deviceId);
} catch (error) {
  userTimeZone = "error";
  console.log('error', error.message);
}

The service client is generated by the factory, and then used to run the getSystemTimeZone method, which returns an ISO time zone like America/New_York. The UpsServiceClient provides access to the Customer Profile and Customer Settings APIs. There are a number of other services for which the factory can generate clients. For details about all the things you can do with service clients, see Obtain Customer Settings Information with the Alexa Settings API in the Alexa Skills Kit documentation.

If this method fails, the time zone returned is "error." This way you can handle the error gracefully and still let the user play the game. However, the actual error is written to the CloudWatch logs, so you can identify patterns of errors when you inspect the logs.

This final code you added isn't Alexa-specific.

if(userTimeZone === "error"){
    greeting = "Hello.";
} else {
    // get the hour of the day or night in your customer's time zone
    const cfunctions = await require('./celebrityFunctions.js');
    var hour = cfunctions.getHour(userTimeZone);
    if(0<=hour&&hour<=4){
        greeting = "Hi night-owl!"
    } else if (5<=hour&&hour<=11) {
        greeting = "Good morning!"
    } else if (12<=hour&&hour<=17) {
        greeting = "Good afternoon!"
    } else if (17<=hour&&hour<=23) {
        greeting = "Good evening!"
    } else {
      greeting = "Howdy pardner!"   
    }
}

With this code, you set the default error greeting to "Hello." Then, if you get a TimeZone, you load the extra functions the workshop provided, and then pass the time zone to a function. The function returns the current hour for that time zone, between 0 (midnight) and 23 (11 p.m.).

The workshop moved this function off into the extra functions file so that you could focus on learning how to create an Alexa custom skill, not figure out what hour it is in a random time zone. If you want to learn how the JavaScript date logic works, however, you can look up the code in that celebrityFunctions.js file.

First, we set up some foundations for the API call by fetching the device id from the request envelope.

device_id = handler_input.request_envelope.context.system.device.device_id

Next, you initialize a couple of variables that you'll be using.

user_time_zone = ""
greeting = ""

Then, you make the API call. The call could time out or run into another network error, so you wrap it in a "try/except code block. The API tries the code. If it succeeds, the code moves on as normal. If it fails, the API has code to handle the failure instead of allowing everything to break.

try:
    user_preferences_client = handler_input.service_client_factory.get_ups_service()
    user_time_zone = user_preferences_client.get_system_time_zone(device_id)
except Exception as e:
    user_time_zone = 'error.'
    logger.error(e)

The user_preferences_client is used to call get_system_time_zone, which returns an ISO time zone like America/New_York. The UpsServiceClient provides access to the Customer Profile and Customer Settings APIs. There are a number of other services for which we can generate clients. For details about all the things you can do with service clients, see Obtain Customer Settings Information with the Alexa Settings API in the Alexa Skills Kit documentation.

If this method fails, the time zone returned is "error." This way you can handle the error gracefully and still let the user play the game. However, the actual error is written to the CloudWatch logs, so you can identify patterns of errors when you inspect the logs.

This final code you added isn't Alexa-specific.

if user_time_zone == 'error':
    greeting = 'Hello.'
else:
    # get the hour of the day or night in your customer's time zone
    from celebrityFunctions import get_hour
    hour = get_hour(user_time_zone)
    if 0 <= hour and hour <= 4:
        greeting = "Hi night-owl!"
    elif 5 <= hour and hour <= 11:
        greeting = "Good morning!"
    elif 12 <= hour and hour <= 17:
        greeting = "Good afternoon!"
    elif 17 <= hour and hour <= 23:
        greeting = "Good evening!"
    else:
        greeting = "Howdy pardner!"

With this code, you set the default error greeting to "Hello." Then, if you get a TimeZone, you load the extra functions the workshop provided, and then pass the time zone to a function. The function returns the current hour for that time zone, between 0 (midnight) and 23 (11 p.m.).

The workshop moved this function off into the extra functions file so that you could focus on learning how to create an Alexa custom skill, not figure out what hour it is in a random time zone. If you want to learn how the Python date logic works, however, you can look up the code in that celebrityFunctions.py file.

Lastly, you need to add the time-appropriate greeting to your welcome texts.

  1. Replace the following code.
if(sessionAttributes.visits === 0){
    speakOutput = `Welcome to Cake Time. I'll tell you a celebrity name and
        you try to guess the month and year they were born. See how many you can get!
        Would you like to play?`;
} else {
    speakOutput = `Welcome back to Cake Time! Ready to guess some more celebrity
        birthdays?`
}
if session_attributes["visits"] == 0:
    speak_output = f"Welcome to Cake Time. " \
        f"I'll tell you a celebrity name and you try " \
        f"to guess the month and year they were born. " \
        f"See how many you can get! " \
        f"Would you like to play?"
else:
    speak_output = f"Welcome back to Cake Time! " \
        f"Ready to guess some more celebrity birthdays?"

With the following code, by copying and pasting it into the index.js or lambda_function.py file.

if(sessionAttributes.visits === 0){
    speakOutput = `${greeting} Welcome to Cake Time. I'll tell you a celebrity name and
        you try to guess the month and year they were born. See how many you can get!
        Would you like to play?`;
} else {
    speakOutput = `${greeting} Welcome back to Cake Time! Ready to guess some more celebrity
        birthdays?`
}
if session_attributes["visits"] == 0:
    speak_output = f"{greeting} Welcome to Cake Time. " \
        f"I'll tell you a celebrity name and you try " \
        f"to guess the month and year they were born. " \
        f"See how many you can get! " \
        f"Would you like to play?"
else:
    speak_output = f"{greeting} Welcome back to Cake Time! " \
        f"Ready to guess some more celebrity birthdays?"

You can see that all you did was add greeting to your existing template strings. But now they start with "Hello" or "Good morning" or even "Hi night-owl" for those playing the game between midnight and 4:59 a.m.

  1. In the upper-right corner of the Code tab page, click Save, and then click Deploy to deploy your code.

    Now you can test your code to make sure your changes work properly.

  2. Click the Test tab, and repeat the steps to test your skill that you learned in Module 4.