How to retrieve the client (browser) timezone in Vaadin Flow?

Viewed 194

I need to determine the browser timezone. I tried to follow this post but does not work (Vaadin 20). Here is my code:

    ZoneId myZoneId;
    ...

    UI.getCurrent().getPage().retrieveExtendedClientDetails(extendedClientDetails -> {
      myZoneId = ZoneId.of(extendedClientDetails.getTimeZoneId());
    });

    // here myZoneId has value null.

So I tried to do it myself, initially simply displaying it.

    UI.getCurrent().getPage()
            .executeJs("return Intl.DateTimeFormat().resolvedOptions().timeZone;")
            .then(value -> Notification.show(value.asString()));

It works and I read "Europe/Rome", but its value does not seem something that I can map to a ZoneId in Java.

I could explore a little more the Javascript zone object but I also was unable to find where my code actually went to debug it with chrome debugger (there is no mention in Vaadin doc where the code goes).

I could work on the returned value and try to interpret it but I would like to avoid reinventing the wheel.

Do anybody has any code that works?

4 Answers

Europe/Rome is fine for Java.

You can simply call:

ZoneId.of("Europe/Rome");

retrieveExtendedClientDetails is an asynchronous call, thus the result is available inside the callback and not right after firing the callback (on the place you commented).

Also, UI.getCurrent() could return null if called too soon, f.e. in a constructor.

retrieveExtendedClientDetails makes a client roundtrip the first time it is run (docs).

If already obtained, the callback is called directly. Otherwise, a client-side roundtrip will be carried out.

That means you could try to run it when initialising the UI and later you should have it ready right away after firing.

@SpringComponent
public class MyVaadinServiceInitListener implements VaadinServiceInitListener {
    @Override
    public void serviceInit(ServiceInitEvent event) {
        event.getSource().addUIInitListener(uiEvent -> {   
            var ui = event.getUI();
            ui.getPage().retrieveExtendedClientDetails(detail -> {});
        });
    }    
}

Another solution is to read the zone inside the callback.

Thanks to everybody!

Finally the main problem was not retrieving the time zone from the client but matching it against the list of available time zones returned by ZoneId.getAvailableZoneIds(). The fact is that there were multiple timezone for CEST (Central Europe) as well as any other country. I counted 7 for Brazil that only has 5 time zones!

Honestly I am little ignorant about time zones (world would be quite simpler if we could use only GMT and adjust our schedules accordingly). The solution was to digest the timezone list using the name. I do not know if this is right or wrong, but it is simple and works, despite the fact that if I choose CEST I do not know if it will be Rome, Berlin or whatever else. Does it matter?

enter image description here

Here is the complete code if somebody should one day need a

package net.cbsolution.scc.vaadin.comps;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.renderer.TemplateRenderer;

import java.time.ZoneId;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;

public class TimeZoneSelector extends ComboBox<ZoneId> {
  private Locale locale = UI.getCurrent().getLocale();

  public TimeZoneSelector() {

    // Digest the timezone list
    final Map<String, ZoneId> cleanMap = new HashMap<>();
    ZoneId.getAvailableZoneIds().stream().map(z -> ZoneId.of(z)).forEach(z -> cleanMap.put(print(z), z));

    cleanMap.values().stream().collect(Collectors.toList());
    setWidth("20em");
    setDataProvider(new ListDataProvider<>(cleanMap.values().stream().collect(Collectors.toList())));
    setRenderer(TemplateRenderer.<ZoneId>of("<span>[[item.print]]</span>")
        .withProperty("print", tz -> print(tz))
    );
    setItemLabelGenerator((ItemLabelGenerator<ZoneId>) item -> print(item));
  }

  protected String print(ZoneId zoneId) {
    return zoneId.getDisplayName(TextStyle.FULL, UI.getCurrent().getLocale());
  }

  @Override
  protected void onAttach(AttachEvent attachEvent) {
    UI.getCurrent().getPage().retrieveExtendedClientDetails(details -> {
      TimeZone uiTimeZone = TimeZone.getTimeZone(details.getTimeZoneId());
      setValue(ZoneId.of(uiTimeZone.getID()));
    });
  }

}

try this

//Read the timezone and store in into the session class
if(UI.getCurrent() != null) {      
       UI.getCurrent().getPage().retrieveExtendedClientDetails(details -> {
                           
        // Set the time zone
        TimeZone uiTimeZone = TimeZone.getTimeZone(details.getTimeZoneId());
                            
      });
 };
Related