With an assist from Velocity, your emails can have time-responsive content. (And I don't just mean Happy ${day_of_week}, ${first_name}!)
To run all the snippets below, include common variables at the top of the token (or in a global token) 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( $ISO8601DateOnly = "yyyy-MM-dd" )
#set( $ISO8601DateTime = "yyyy-MM-dd'T'HH:mm:ss" )
#set( $ISO8601DateTimeWithSpace = "yyyy-MM-dd HH:mm:ss" )
#set( $ISO8601DateTimeWithMillisUTC = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" )
#set( $ISO8601DateTimeWithMillisTZ = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" )
Date magic needs real Java Date and Calendar objects, not Strings that happen to look like dates. No matter if fields originally had a Date/DateTime datatype in another system, even in Marketo itself – if they’ve been stringified on their way into the Velocity context, they need to be “resuscitated” into living objects.
So in a few of the examples below, a Velocity (e.g. Java) String is parsed into a Date, then converted into a Calendar. To do the parsing, you need to know the exact format, so Velocity knows where to locate the years, months, days, and so on.
The $ISO8601WithSpace
format ("2019-12-07 13:30:00"
) is how Marketo DateTime fields appear in Velocity.
I feel like noting that’s a perfectly valid format within the international standard, ISO 8601: whitespace is ignored, and you aren’t required to have a T
(or any separator!) between the date and time. Including the T
is often presented as “the” ISO 8601 format, but it’s merely “the most common.”[1]
Regardless, the $ISO8601DateTime
and $ISO8601DateTimeWithMillisUTC
formats, which both have a T
separating the date and time ("2019-12-07T13:30:00"
), are vastly more common on the web at large. For example, when JSON is generated from JavaScript (and perhaps stored in a Marketo Textarea field) the format is $ISO8601DateTimeWithMillisUTC
.
Anyway, knowing what format(s) you’ve got coming in is crucial!
I cannot stress enough that setting the IANA time zone for your location is critical when using
|
#set( $calNow = $date.getCalendar() )
#if( $calNow.get($calConst.DAY_OF_MONTH) <= 7 )
It's one of the first 7 days of the month!
#end
#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
#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
#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.
#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.
#set( $calStartOfPromo = $convert.toCalendar(
$convert.parseDate(
"2017-11-15T00:00:00",
$ISO8601DateTime,
$defaultLocale,
$defaultTimeZone
)
) )
#set( $calEndOfPromo = $convert.toCalendar(
$convert.parseDate(
"2017-12-01T00:00:00",
$ISO8601DateTime,
$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.
$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).
#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",
$ISO8601DateTime,
$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[2] 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.☺
#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 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 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.
Get confused enlightened by the Java Calendar docs.
[1] As in JavaScript’s toISOString()
of course, and how Java’s ISO_INSTANT
constant just refers to "yyyy-MM-dd'T'HH:mm:ssZ"
as “ISO.”
The ancient W3C note on Date and Time Formats defines an ISO 8601 profile whose formats always have the T
, but doesn’t conflate the profile with the ISO 8601 standard as a whole.
[2] 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).
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.