Convert IANA timezones to Windows

The IANA timezones database is probably the most used standard to reference a timezone by name (a lot of libraries use it).

The problem comes when you need to use these timezones informations in a C# program: Windows timezones have different names and there’s no standard method to convert a IANA timezone to a Windows one.

Informations about the relation between IANA and Windows timezones  are accessible here:
https://github.com/unicode-org/cldr/blob/master/common/supplemental/windowsZones.xmlhttp://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml

Looking at the data is clear that you can convert from a IANA timezone to a Windows one, but a Windows timezone cannot be converted to a single IANA timezone. This is because Windows groups more than one timezone under the same name.

<supplementalData>
	<version number="$Revision$"/>
	<windowsZones>
		<mapTimezones otherVersion="7e00100" typeVersion="2016d">
			...
			<!-- (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna -->
			<mapZone other="W. Europe Standard Time" territory="001" type="Europe/Berlin"/>
			<mapZone other="W. Europe Standard Time" territory="AD" type="Europe/Andorra"/>
			<mapZone other="W. Europe Standard Time" territory="AT" type="Europe/Vienna"/>
			<mapZone other="W. Europe Standard Time" territory="CH" type="Europe/Zurich"/>
			<mapZone other="W. Europe Standard Time" territory="DE" type="Europe/Berlin Europe/Busingen"/>
			<mapZone other="W. Europe Standard Time" territory="GI" type="Europe/Gibraltar"/>
			<mapZone other="W. Europe Standard Time" territory="IT" type="Europe/Rome"/>
			<mapZone other="W. Europe Standard Time" territory="LI" type="Europe/Vaduz"/>
			<mapZone other="W. Europe Standard Time" territory="LU" type="Europe/Luxembourg"/>
			<mapZone other="W. Europe Standard Time" territory="MC" type="Europe/Monaco"/>
			<mapZone other="W. Europe Standard Time" territory="MT" type="Europe/Malta"/>
			<mapZone other="W. Europe Standard Time" territory="NL" type="Europe/Amsterdam"/>
			<mapZone other="W. Europe Standard Time" territory="NO" type="Europe/Oslo"/>
			<mapZone other="W. Europe Standard Time" territory="SE" type="Europe/Stockholm"/>
			<mapZone other="W. Europe Standard Time" territory="SJ" type="Arctic/Longyearbyen"/>
			<mapZone other="W. Europe Standard Time" territory="SM" type="Europe/San_Marino"/>
			<mapZone other="W. Europe Standard Time" territory="VA" type="Europe/Vatican"/>
			...
		</mapTimezones>
	</windowsZones>

</supplementalData>

I made a test project to demonstrate how to convert timezones: just deserialize the data and look for the corresponding timezone.

IANA timezone   : Europe/Rome
Windows timezone: W. Europe Standard Time

IANA timezone   : Pacific/Tongatapu
Windows timezone: Tonga Standard Time

You can download the test project here: IANATimezonesConverter.zip

Universal Windows Platform Hamburger menu navigation

A lot of UWP apps use the hamburger menu for page navigation, so I made a sample app.

pc2

Since Windows.UI.Xaml.Controls.Page is a UserControl the idea is to just replace the content of the MainPage instead of really navigate through pages.

I encountered some problems:

I solved these problems by creating a BasePage class with properties to set transitions and exposing new navigation events. The NavigationCacheMode is managed by the code.

Sadly NavigationEventArgs cannot be inherited (it’s a sealed class) and has no constructor so I had to use my own class.

The example app has some pages to navigate through and uses different transitions. The app runs correctly on the PC and on the phone.

To change the menu appearence on mobile I used the VisualStateManager: on mobile (but also on pc if you resize the window to less than 720 pixels) the menu is totally closed and a little larger when opened.

<VisualStateManager.VisualStateGroups>
  <VisualStateGroup>
    <VisualState x:Name="wideView">
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="720" />
      </VisualState.StateTriggers>
      <VisualState.Setters>
        <Setter Target="m_SplitView.CompactPaneLength" Value="40"/>
        <Setter Target="m_SplitView.OpenPaneLength" Value="200"/>
      </VisualState.Setters>
    </VisualState>
    <VisualState x:Name="narrowView">
      <VisualState.Setters>
        <Setter Target="m_SplitView.CompactPaneLength" Value="0"/>
        <Setter Target="m_SplitView.OpenPaneLength" Value="250"/>
      </VisualState.Setters>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="0" />
      </VisualState.StateTriggers>
    </VisualState>
  </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Here are some screenshots:

The app looks good also using the dark theme

I made a simple control for the menu item and in the MainPage there’s the standard SplitView. To let the code open the correct page and select the menu item you need to put in the ActionName the page’s class name.

<SplitView DisplayMode="CompactOverlay"  IsPaneOpen="False" 
           Grid.Row="1"
           x:Name="m_SplitView"
           CompactPaneLength="40" OpenPaneLength="200">
  <SplitView.Pane>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>

      <ScrollViewer VerticalScrollBarVisibility="Auto">
        <StackPanel Orientation="Vertical">
          <controls:MenuButton Icon="&#xE80F;" Label="Home" ActionName="HamburgerMenuNavigationUWP.Pages.HomePage" Tapped="MenuButton_Tapped" />
          <controls:MenuButton Icon="&#xE14C;" Label="Data page" ActionName="HamburgerMenuNavigationUWP.Pages.DataPage" Tapped="MenuButton_Tapped" />
        </StackPanel>
      </ScrollViewer>

      <StackPanel Grid.Row="1" BorderBrush="{ThemeResource SystemControlForegroundBaseMediumBrush}" BorderThickness="0,1,0,0" >
        <controls:MenuButton Icon="&#xE897;" Label="About" ActionName="HamburgerMenuNavigationUWP.Pages.AboutPage" Tapped="MenuButton_Tapped" />
        <controls:MenuButton Icon="&#xE713;" Label="Settings" ActionName="HamburgerMenuNavigationUWP.Pages.SettingsPage" Tapped="MenuButton_Tapped" />
      </StackPanel>
    </Grid>
  </SplitView.Pane>
  <SplitView.Content>
    <Grid Name="m_Grid">
      <!-- Here goes the content -->
    </Grid>
  </SplitView.Content>
</SplitView>

Pages must inherit from the BasePage class but are normal pages for the rest.

All the “dirty” work is done in App.cs (back stack, page cache, show/hide the back button, manage the back button event).

private void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
  if (e.SourcePageType == typeof(MainPage))
    return;

  MainPage mp = (Window.Current.Content as Frame).Content as MainPage;

  // Do not navigate to the same page:
  if (m_CurrentPage != null && e.SourcePageType == m_CurrentPage.SourcePageType){
    if (mp != null)
      mp.IsPaneOpen = false;
    e.Cancel = true;
    return;
  }

  Pages.BasePage oldPage = null;
  Pages.BasePage newPage = null;

  //Check the cache:
  if (!m_PagesCache.TryGetValue(e.SourcePageType.FullName, out newPage))
    newPage = (Pages.BasePage)Activator.CreateInstance(e.SourcePageType, new object[] { });

  if (newPage != null) {
    newPage.Transitions = new TransitionCollection();   
    if (mp != null) {
      if (mp.MainGrid.Children.Count > 0)
        oldPage = mp.MainGrid.Children[0] as Pages.BasePage;

      // Call page events:
      if (oldPage != null) {
        oldPage.OnNavigatingFrom(e);
        if (e.Cancel)
          return;
      }

      // Animations:
      if (m_GoingBack) {
        if (oldPage != null) { 
          oldPage.Transitions = new TransitionCollection();
          if (oldPage.BackExitTransition != null)
            oldPage.Transitions = new TransitionCollection() { oldPage.BackExitTransition };              
        }

        if (newPage.BackEnterTransition != null)
          newPage.Transitions = new TransitionCollection() { newPage.BackEnterTransition };
      } else {
        if (oldPage != null) {
          oldPage.Transitions = new TransitionCollection();
          if (oldPage.ForwardExitTransition != null)
            oldPage.Transitions = new TransitionCollection() { oldPage.ForwardExitTransition };
        }

        if (newPage.ForwardEnterTransition != null)
          newPage.Transitions = new TransitionCollection() { newPage.ForwardEnterTransition };
      }
      
      if (oldPage != null) {
        // Remove oldPage from the MainPage:
        mp.MainGrid.Children.Remove(oldPage);

        // Call oldPage events:
        oldPage.OnNavigatedFrom(new Pages.NavigationArgs() { NavigationMode = m_GoingBack ? NavigationMode.Back : NavigationMode.Forward });
      }
        
      // Add the new page to the MainPage:
      mp.MainGrid.Children.Add(newPage);

      // Call newPage events:
      newPage.OnNavigatedTo(new Pages.NavigationArgs() { NavigationMode = m_GoingBack ? NavigationMode.Back : NavigationMode.New, Parameter = e.Parameter, SourcePageType = e.SourcePageType });

      // Select the menu item
      if (!mp.SelectMenu(e.SourcePageType.FullName) && !string.IsNullOrEmpty(newPage.SelectMenuAction))
        mp.SelectMenu(newPage.SelectMenuAction);

      // Close the menu:
      mp.IsPaneOpen = false;

      // Add the new page to the back stack
      if (!m_GoingBack) {
        if (m_CurrentPage != null)
          m_BackStack.Add(m_CurrentPage);            
      }
      m_CurrentPage = new PageStackEntry(e.SourcePageType, e.Parameter, e.NavigationTransitionInfo);

      // Save/remove pages with NavigationCacheMode.Required
      if (oldPage != null) {
        if (!m_GoingBack && oldPage.NavigationCacheMode == NavigationCacheMode.Required) {
          m_PagesCache[oldPage.GetType().FullName] = oldPage;
        } else if (m_GoingBack && m_PagesCache.ContainsKey(oldPage.GetType().FullName)) {
          m_PagesCache.Remove(oldPage.GetType().FullName);
        }
      }

      // Show/hide the back button
      SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
        m_BackStack.Count > 0 ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;

      m_GoingBack = false;
    }
  }
  e.Cancel = true;
} // RootFrame_Navigating

 

You can download the source code here.

If you have advice, ideas, better solutions feel free to leave a comment.

Have fun. 😉

WpfMpdClient 1.9.0 released

I updated WpfMpdClient after a long time. A user reported that the program wasn’t working with MPD v.0.19 so I had a look to fix this issue…I also changed other things. 🙂

The new version has some UI improvements (bigger fonts, icons, loading indicators) and some code improvements (data are now loaded asynchronously).

Here you can see some screenshots of the new version.

wpfmpdclient_1 wpfmpdclient_2 wpfmpdclient_3

I tested this release with MPD’s snapshot version 0.20, let me know if it works also with 0.19

You can download WpfMpdClient here.