Sanford Whiteman

Velocitips: Switch email content based on day/time

Blog Post created by Sanford Whiteman on Nov 28, 2017

With an assist from Velocity, your emails can have time-responsive content.  (And I don't just mean Happy ${day_of_week}, ${first_name}!)

 

  • When the same email is resent with different primary content (e.g. a weekly newsletter), Velocity can customize secondary content based on the day: like reader PW, you can show a special promo only in the first send of the month, every month.
  • Like user GM, you can pre-create a series of different content blocks, in effect creating a drip campaign from just one asset.
  • If a triggered email is sent off-hours, you can notify the recipient that they'll hear from Sales on the next business day.
  • Or lots of other adventures!

 

To run the snippets below, set common variables at the top of the script token or in your global {{my.includes}} token (you do have one, right?) like so:

 

#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $field.in($calNow) )
#set( $ISO8601 = "yyyy-MM-dd'T'HH:mm:ss" )
#set( $ISO8601DateOnly = "yyyy-MM-dd" )

 

I cannot stress enough that setting the IANA time zone for your location is critical when using Date or Calendar objects in Velocity.  If you do not do this as is the case, unfortunately, with some old code snippets on Marketo's blog your code is broken. I won't rehash the reasons why here (happy to in the comments) but believe me you must set the timezone!

 

Now, for some examples...

 

Check the current day of the month

#set( $calNow = $date.getCalendar() )
#if( $calNow.get($calConst.DAY_OF_MONTH) <= 7 )
It's one of the first 7 days of the month!
#end

 

Check the current week

#if( $calNow.get($calConst.WEEK_OF_MONTH) == 1 )
It's the first calendar week of the month!
#end

 

#if( $calNow.get($calConst.WEEK_OF_MONTH) == $calNow.getActualMaximum($calConst.WEEK_OF_MONTH) )
It's the last week of the month!
#end

 

Check if it's the Nth Something-day of the month

#if( $calNow.get($calConst.DAY_OF_WEEK) == $calConst.WEDNESDAY &&
     $calNow.get($calConst.DAY_OF_WEEK_IN_MONTH) == 3 )
It's the 3rd Wednesday of the month!
#end

 

Is it during business hours?

#set( $calStartOfBusiness = $date.getCalendar() )
#set( $ret = $calStartOfBusiness.setTimeZone($defaultTimeZone) )
#set( $ret = $calStartOfBusiness.set(
   $calStartOfBusiness.get($calConst.YEAR),
   $calStartOfBusiness.get($calConst.MONTH),
   $calStartOfBusiness.get($calConst.DAY_OF_MONTH),
   8,
   0,
   0
) )
#set( $ret = $calStartOfBusiness.set($calConst.MILLISECOND,0) )
#set( $calCloseOfBusiness = $date.getCalendar() )
#set( $ret = $calCloseOfBusiness.setTimeZone($defaultTimeZone) )
#set( $ret = $calCloseOfBusiness.set(
   $calCloseOfBusiness.get($calConst.YEAR),
   $calCloseOfBusiness.get($calConst.MONTH),
   $calCloseOfBusiness.get($calConst.DAY_OF_MONTH),
   17,
   0,
   0
) )
#set( $ret = $calCloseOfBusiness.set($calConst.MILLISECOND,0) )
#if( $calNow.compareTo($calStartOfBusiness) >= 0 && $calNow.compareTo($calCloseOfBusiness) <= 0 )
It's during business hours!
#end

 

Note here that 8,0,0 and 17,0,0 are setting the hour, minute, and second respectively, in 24-hour time.

 

8,0,0 means 08:00:00 or 8 a.m. and 17,0,0 means 17:00:00 or 5 p.m.

 

Is it a business day?

#set( $businessDays = [
  $calConst.MONDAY,
  $calConst.TUESDAY,
  $calConst.WEDNESDAY,
  $calConst.THURSDAY,
  $calConst.FRIDAY
] )
#if( $businessDays.contains($calNow.get($calConst.DAY_OF_WEEK)) )
It's a work day!
#end

 

You can of course combine this business days check with business hours above.

 

Is our timed promo active?

#set( $calStartOfPromo = $convert.toCalendar(
  $convert.parseDate(
    "2017-11-15T00:00:00",
    $ISO8601,
    $defaultLocale,
    $defaultTimeZone
  )
) )
#set( $calEndOfPromo = $convert.toCalendar(
  $convert.parseDate(
    "2017-12-01T00:00:00",
    $ISO8601,
    $defaultLocale,
    $defaultTimeZone
  )
) )
#if( $calNow.compareTo($calStartOfPromo) >=0 && $calNow.before($calEndOfPromo) )
The promo is active!
#end

 

Note again the mandatory use of defaultTimeZone when initializing new Dates.

 

And check out how I'm using compareTo and before to see if we're currently greater than or equal to the start date and less than the end date.

 

You could substitute compareTo($calEndOfPromo) < 0 for before($calEndOfPromo) as it's the same logic, just a tiny bit longer.

 

Promo expires 7 days from today (whenever that is)

$calNow.add($calConst.DATE,7)
#set( $FRIENDLY_24H_DATETIME_WITH_FRIENDLY_TZ = "MMM dd, yyyy HH:mm z" )
Our promo expires 7 days from today, which is
${date.format(
  $FRIENDLY_24H_DATETIME_WITH_FRIENDLY_TZ,
  $calNow,
  $defaultLocale,
  $defaultTimeZone
)}

 

This quickie shows how to shift a certain number of days from today. To go backwards 7 days, use add($calConst.DATE,-7) (there's no explicit subtract method).

 

How long will the promo last?

#set( $ret = $calNow.set(
   $calNow.get($calConst.YEAR),
   $calNow.get($calConst.MONTH),
   $calNow.get($calConst.DAY_OF_MONTH),
   0,
   0,
   0
) )
#set( $ret = $calNow.set($calConst.MILLISECOND,0) )
#set( $calEndOfPromo = $convert.toCalendar(
  $convert.parseDate(
    "2018-01-22T00:00:00",
    $ISO8601,
    $defaultLocale,
    $defaultTimeZone
  )
) )
#set( $diffRemaining = $date.difference($calNow,$calEndOfPromo) )
#set( $daysRemaining = $convert.toInteger($diffRemaining.getDays()) )
#set( $weeksRemaining = $convert.toInteger($diffRemaining.getWeeks()) )
The promo
#if( $weeksRemaining > 0 )
ends in ${weeksRemaining} ${display.plural($weeksRemaining,"week","weeks")}!
#elseif( $daysRemaining > 0 )
ends in ${daysRemaining} ${display.plural($daysRemaining,"day","days")}!
#elseif( $daysRemaining == 0 )
ends today!
#else
already ended!
#end

 

Unlike adding and subtracting days to/from today, differencing dates is relatively complex.

 

First, you usually[1] want to align dates to midnight (yyyy-MM-ddT00:00:00.000) boundaries. That's why I set the hours, minutes, seconds, and milliseconds of $calNow all to zero, to create a Calendar object representing "Today at midnight."

 

Then the end date is also anchored at 00:00:00 (you could use any time of day, as long it's the same for start and end, but midnight is easiest).

 

Once aligned, you won't get any fractional-day surprises with $date.difference(). difference() returns a robust Comparison object, from which you get the count of days, weeks, and so on between the dates.

 

I also took this opportunity to use $display.plural(), which isn't a date-related feature but just another cool Velocity thing you should know. plural() works with Integers, though, while Comparison.get accessors return Longs. That's why $convert.toInteger() is in there.

 

Times like this, you can see why I say Velocity is anything but simple. So if you're really a newbie, you shouldn't say, "I'm not too advanced with Velocity" as if you're just a couple scripts away from mastery! To truly add Velocity to your skill stack is a slow grind.

 

Add an ordinal indicator to the day, like “1st” or “15th” instead of just “1” or “15”

#define( $enUSDayOrdinalIndicators )
1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st
#end
#set( $indicatorList = $enUSDayOrdinalIndicators.toString().trim().split("\s?\d+") )
Today with a friendly ordinal indicator (is
${date.format(
  "EEEE, MMMM d'${indicatorList[$calNow.get($calConst.DAY_OF_MONTH)]}'",
  $calNow,
  $defaultLocale,
  $defaultTimeZone
)}

 

This add-on will give you a super-friendly date display, which isn't natively supported by Java or Velocity:

 

Wednesday, August 15th

 

(The "th" is the non-native part.)

 

It should be clear that this is very culturally-specific stuff, and since it isn't part of the localized parts of Java Calendar (which I mean to write a dedicated post on soon) you would have to add your own localized Nth and Nst for locales other than en-*, some of which never use indicators at all, like Swedish.

 

 

Learning More

Get confused enlightened by the Java Calendar docs.

 

 


 

Notes

[1] Exception is when a period ends at, say, exactly 5:00 p.m. on a certain date and you do want to take hours (fractional days) into account. Sweepstakes laws might require such precision. Or if you're trying to do something like hours-until-webinar, you'd also want to align to hours (still zero out minutes/seconds/millis for sanity).

Outcomes