The discussion and solution discussed in this article is illustrated in this sample form. Either save a copy of the sample form in your Google Drive or download as an Excel workbook.
Note: If you change your device’s language settings to a language other than English, the built-in date pickers/widgets should reflect the month names in that language. So before you go through the effort of using the solution below, try changing the language in Android’s settings to match what you need. |
SurveyCTO’s date type fields are a convenient way to collect date information in your form using a variety of interfaces, including date-pickers that look like a calendar or that look like a series of scroll wheels. If your survey is being conducted only in English, or with surveyors who can enter date information in an English-language calendar even if they are provided with the date in another language, our default options will work just fine.
However, what if you need to display calendar information to your users in languages other than English? Currently there is no way to translate the built-in date picking options. In these situations, you’ll need to manually insert a set of fields that behave the same way, and which allow you to include the names of the months in all of the languages you need.
Here is a possible solution you can use for your form, along with a link to an example form that demonstrates the techniques I mention below.
Note: Please note that the rest of this tutorial assumes you are still using the Gregorian calendar even though you need the names of the months in a different language. If you are using a different calendar system the same principles found below can be used with modifications, of course.
How to set up your “manual” calendar fields
The instructions below assume that you want to collect year, month, and day information for your date. If you don’t need all three components, you can adjust accordingly.
First, we’ll need at least three separate fields. One each for the year, month, and day. In the example form design (linked above), these fields are together in a group using the “field-list” appearance so that they all appear on the same page.
Day
Let’s tackle the most complicated problem first, which is that for a date to be a valid date, we need to restrict what values can be entered in the “day” field depending on the month and year selected. We will do that using a constraint expression on the “day” field.
Here are the five scenarios which need to be addressed in a constraint for the day field:
In all cases, the day value must be 1 or greater
- In February of a non-leap year, the maximum day value is 28
- In February of a leap year, the maximum day value is 29
- In April, June, September, and November (months 4, 6, 9, and 11), the maximum day value is 30
- In all other months, the maximum day value is 31
Now that we’ve laid out the scenarios which need to be addressed by our constraint expression, we can start working out a way to make sure they’re all included.
The most important information we’ll need for most of this is the “month” field, but we will also need to know whether the year is a leap year. We can do that by creating a calculate field with some clever logic to calculate that for us.
Since leap years are always 4 years apart, if we take any year value and subtract it from a known leap year, then divide the difference by 4, there should be no remainder. We can determine this by using a modulo or modulus function, which is one of the mathematical functions built in to SurveyCTO.
Let’s create a calculate field that we’ll call “is_leapyear” so that we can use this information in later calculations. I’ll be using 2020 as the reference leap year, but any leap year would do.
if((2020-${year}) mod 4 = 0, 1, 0)
This subtracts the entered year from 2020 and performs a modulo operation on that value with 4 as the divisor. If there is no remainder, this calculate field will get a value of 1. If there is a remainder, this calculate field will get a value of zero.
Now that we have this information ready to go, we can construct the rest of the constraint expression for the “day” field. Here’s the entire expression, which has a number of components to it, including if() expressions nested to 3 levels.
. >= 1 and if(${month} = 2 and ${is_leapyear} = 0,
. <= 28, if(${month} = 2 and ${is_leapyear} = 1,
. <= 29, if(selected('4 6 9 11',${month}), . <= 30, . <= 31)))
Note: Remember that “.” (a “full stop” or “period”) represents the current value of the field in question in a constraint expression. This is shorthand, saving you from naming the current field (e.g “${fieldname}”) in its constraint.
If we add some line breaks to make this easier to look at, each number here corresponds to the conditions enumerated above.
-
. >= 1 and
-
if(${month} = 2 and ${is_leapyear} = 0, . <= 28,
-
if(${month} = 2 and ${is_leapyear} = 1, . <= 29,
-
if(selected('4 6 9 11',${month}), . <= 30
-
, . <= 31)))
Line 1 enforces that any value must be 1 or greater. Line 2 enforces that when the month is February (2) and it is not a leap year, the maximum value can be 28. Line 3 enforces that if the month is February and it is a leap year, the maximum value can be 29. Line 4 enforces that if the month is April, June, September, or November, the maximum value can be 30, and Line 5 represents all the other possibilities, which is that the maximum can be 31.
Lastly, the 3 closing parentheses at the end of the expression are necessary because the if() conditions are nested 3 levels “deep.” The number of opening and closing brackets in an expression must always balance (be equal).
Note: by nesting additional if() conditions inside if(), you can create expressions that have more than just one ‘if true’ outcome and one ‘if false’ outcome.
Month with a constraint
Now that we’ve set up the “day” constraint, which are the most complicated part of this exercise, we can move on to the “month” field. The simplest way to handle this is to make the month question a select_one field. You can specify the names of the months in as many languages as you need to, but the values should be numerical, from 1 to 12. The example I’ve included has no constraints on the month field here, but you might want to add some depending on your form. For example, if you were asking a date of birth, you could add a constraint that the month must be no later than the current month if the year of birth is the current year. You could also use choice filtering in that case as well.
The constraint for that would look like this:
if(${year} = int(format-date-time(today(),'%Y')), . <=
int(format-date-time(today(),'%n'), . = .)
This expression says if the year entered is equal to the current year, then this value (the month value) must be less than or equal to the current month, otherwise this value can be anything. format-date-time() is used here to return the year, which in turn is transformed into an integer for comparison using int(). The second instance of format-date-time() returns the current single digit month value to logically constrain the month value.
Month with a choice filter
Choice filtering (click to read the basics) is a useful and often superior alternative to constraints. Instead of a constraint to prevent illogical responses in a choice list, you could also use an expression in the choice_filter for a field to filter those choice options out:
if(${year} = int(format-date-time(today(),'%Y')), filter <=
int(format-date-time(today(),'%n'), filter = filter)
This would make a constraint unnecessary, since illogical response options wouldn’t even be displayed at all. In this choice_filter expression, If the entered year is equal to the current year, the choice list of months is filtered accordingly in relation to the current month value, returned using format-date-time(). As you can see, there are often many ways to approach a given challenge using SurveyCTO.
Year
The year value is likely the simplest one you’ll have to deal with out of the three. Just make sure you restrict the values to appropriate ranges. For example, if you are in a section asking about children aged 5-18, restrict the year values to something appropriate for that age range.
Perhaps you wish to guard against years in the future. The best solution is dynamic, working in the new year too:
. <= int(format-date-time(today(),'%Y'))
Here, the current value must be less than or equal to the current year, returned by format-date-time(), turned into an integer using int().
Performing date calculations using these fields
Now that you have the three components of your date ready to be collected, you can set up a calculate field to combine the three separate date components into a date that is properly formatted for use in SurveyCTO. Then, whenever you need to perform any date calculations with that information, put a date() function around it so that it’s treated as a date.
To combine the three separate fields, you can use the concat() function to make a YYYY-MM-DD formatted date, like so:
concat(${year},'-',${month},'-',${day})
Then, you can refer to that field in a later expression like in the example below:
today() - date(${concat_date})
This expression would give you the number of days between the current date and the date entered using our construction above.
Note (important!): date() is an inflexible expression with respect to invalid date values. While it will work fine with double or single month and day values, it is intolerant of impossible date values, like February, 30th. There is of course, no such date as February 30th. If the date() function were to receive an invalid or partial date value, your form will display an error, even when the form opens. The above constraint and choice filter option can help with this but even then, it is often necessary to delay the evaluation of date() until a year, month and day values have been captured in respective fields. For example, you could delay it like this:
if(${year} != null and ${month} != null and ${day} != null, date(${concat_date}), '')
Here, “null” is a special value, representing that a field is empty. So here, it is being confirmed that the year, month and day fields are all not empty, before converting to a date value. Otherwise, nothing is returned.
For more detail on how to work with date calculations, see this article.
The above features the following functions and operators: date(), if(), selected(), int(), format-date-time(), today(), concat(), mod, >, = and <. Consult the product documentation on using expressions in your forms to read about these and other functions and operators.
Do you have thoughts on this support article? We'd love to hear them! Feel free to fill out this feedback form.
0 Comments