changing timezone with dpkg-reconfigure tzdata and debconf-set-selections

Simplest way that I know of is:

echo "Europe/Zurich" > /etc/timezone 
dpkg-reconfigure -f noninteractive tzdata

I just found out that with Debian Stretch (9) you need to also change /etc/localtime, thus something like:

ln -fs /usr/share/zoneinfo/`cat /etc/timezone` /etc/localtime

is needed. To amend andrekeller's answer you need:

echo "Europe/Zurich" > /etc/timezone 
ln -fs /usr/share/zoneinfo/`cat /etc/timezone` /etc/localtime
dpkg-reconfigure -f noninteractive tzdata

For using debconf, before calling dpkg-reconfigure, you need to also remove /etc/localtime and /etc/timezone. Also beware of possible extra space (as in your question!) which are not benign.

So "debconf way" that works (in Debian Stretch) would be:

echo "tzdata tzdata/Areas select Europe" | debconf-set-selections
echo "tzdata tzdata/Zones/Europe select Madrid" | debconf-set-selections
rm -f /etc/localtime /etc/timezone
dpkg-reconfigure -f noninteractive tzdata

The reason is that tzdata config script tries to be smart, and if it was ever configured before or user has manually meddled with timezones, will behave differently.

EXTRA

To debug similar problem yourself, you would first do:

export  DEBCONF_DEBUG=developer

which would provide following info:

# dpkg-reconfigure -f noninteractive tzdata
debconf (developer): starting /var/lib/dpkg/info/tzdata.config reconfigure 2018e-0+deb9u1
debconf (developer): <-- VERSION 2.0
debconf (developer): --> 0 2.0
debconf (developer): <-- CAPB backup
debconf (developer): --> 0 multiselect escape
debconf (developer): <-- SET tzdata/Areas Etc
debconf (developer): --> 0 value set
debconf (developer): <-- SET tzdata/Zones/Etc UTC
debconf (developer): --> 0 value set
debconf (developer): <-- INPUT high tzdata/Areas
debconf (developer): --> 30 question skipped
debconf (developer): <-- GO
debconf (developer): --> 0 ok
debconf (developer): <-- GET tzdata/Areas
debconf (developer): --> 0 Etc
debconf (developer): <-- INPUT high tzdata/Zones/Etc
debconf (developer): --> 30 question skipped
debconf (developer): <-- GO
debconf (developer): --> 0 ok
debconf (developer): starting /var/lib/dpkg/info/tzdata.postinst configure 2018e-0+deb9u1
debconf (developer): <-- VERSION 2.0
debconf (developer): --> 0 2.0
debconf (developer): <-- GET tzdata/Areas
debconf (developer): --> 0 Etc
debconf (developer): <-- GET tzdata/Zones/Etc
debconf (developer): --> 0 UTC
debconf (developer): <-- STOP

which would show you that it forcefully SETs the values before asking the user for them (thus overwriting values you set with debconf-set-selections).

Then you would change shebang in /var/lib/dpkg/info/tzdata.config from #!/bin/shto #!/bin/sh -x, and follow the program flow, where you would see that it does:

+ [ -L /etc/localtime ]
+ readlink /etc/localtime
+ TIMEZONE=/usr/share/zoneinfo/Etc/UTC

looking at /var/lib/dpkg/info/tzdata.config you will find this:

# If /etc/localtime is a link, update /etc/timezone
if [ -L /etc/localtime ] ; then
    TIMEZONE="$(readlink /etc/localtime)"

which explains why @fiction answer works. Also looking in the script more, you would find that @andrekeller answer also works in newer Debian versions if you remove '/etc/localtime'.

Hopefully this debugging help will help you when next version of Debian makes the script even smarter and existing answers became invalid too. More info on debugging debconf can be found in debconf-devel(7)