<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jeff Sheets</title>
    <description>The latest articles on DEV Community by Jeff Sheets (@jeffsheets).</description>
    <link>https://dev.to/jeffsheets</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F629758%2F279fab7f-08cd-4637-9335-be2b93467da2.png</url>
      <title>DEV Community: Jeff Sheets</title>
      <link>https://dev.to/jeffsheets</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeffsheets"/>
    <language>en</language>
    <item>
      <title>Language Version Managers - the Developer Parachutes</title>
      <dc:creator>Jeff Sheets</dc:creator>
      <pubDate>Thu, 29 Sep 2022 14:47:12 +0000</pubDate>
      <link>https://dev.to/devobsessed/language-version-managers-the-developer-parachutes-bmn</link>
      <guid>https://dev.to/devobsessed/language-version-managers-the-developer-parachutes-bmn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Installing multiple language versions (e.g. Java 11, Java 19, Java 8 | Node 8, Node 16 | Python 2, Python 3 | etc) on your machine is scary, and not for the faint of heart.&lt;/p&gt;

&lt;p&gt;Conquer your fears with the benefit of a developer parachute, by using a Language Version Manager!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As software engineering consultants at &lt;a href="https://www.devobsessed.com/"&gt;DevObsessed&lt;/a&gt;, we get asked to help with an assortment of various projects – from legacy codebases running in maybe Java 8 or NodeJS 6, to greenfield creations using the newest versions of Java 19 or NodeJS 16. This isn't only an issue between companies though, as most organizations have multiple versions of languages running at any given time too. &lt;/p&gt;

&lt;p&gt;Managing local installations of Java, Node, Python, Ruby, Elixir, etc can be daunting. In the past, trying out the latest version to check for bugs could lead to a developer's machine being down for hours at a time. And if a rollback to a previous version was needed that could take even longer! Wouldn't it be great if you could have a parachute with you on your mission, so that if anything goes wrong you can easily save yourself and proceed with confidence?&lt;/p&gt;

&lt;p&gt;Language Version Managers are your answer. Many version managers exist, and in this post we'll show you how Sdkman for Java, and NVM for Node can provide you the courage to easily utilize multiple language versions without any worry in the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backstory
&lt;/h3&gt;

&lt;p&gt;To set the scene, you should know that originally this post was going to be about the recent release of &lt;a href="https://twitter.com/java/status/1572243891562381313"&gt;Java 19&lt;/a&gt;. It would be a story about the newest and latest Java features, maybe how to use them, and probably why they would make it great for you to upgrade.&lt;/p&gt;

&lt;p&gt;Without thinking about it, using &lt;a href="https://sdkman.io/"&gt;Sdkman&lt;/a&gt; with a quick &lt;code&gt;sdk install java 19-open&lt;/code&gt; and my MacbookPro was running the newest Java within seconds. It was so easy to upgrade to the cutting edge version, that it was summed up easily in a single tweet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y6Xn_CSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lla9ze5jiuapmi0hqnl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y6Xn_CSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lla9ze5jiuapmi0hqnl2.png" alt="Tweet of Sdkman installing Java" width="880" height="784"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://twitter.com/sheetsj/status/1572298842460286980"&gt;https://twitter.com/sheetsj/status/1572298842460286980&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not until this moment did &lt;a href="https://twitter.com/sheetsj/status/1572324247351197696"&gt;I dive in&lt;/a&gt; to see that Java 19 was more of an incremental release. Some bugfixes for sure, but mostly it includes just some &lt;a href="https://openjdk.org/projects/jdk/19/"&gt;Preview and Incubator features&lt;/a&gt; (hiding behind feature flags) that won't be fully live until Java 20 or beyond.&lt;/p&gt;

&lt;p&gt;So after a few moments of trying out &lt;a href="https://twitter.com/sheetsj/status/1572316099030687744"&gt;Gradle&lt;/a&gt; and &lt;a href="https://twitter.com/sheetsj/status/1572319232096088064"&gt;IntelliJ&lt;/a&gt; support, I quickly reverted back to my previous Java version with &lt;code&gt;sdk default java 18.0.2.1-open&lt;/code&gt;. No fuss and no worry. Then it dawned on me how before Sdkman this would have taken me probably many minutes if not hours of work to flip back and forth between Java installation versions locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sdkman for Java
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DM9HHG5r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qgyvgnljdlswo66in6n7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DM9HHG5r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qgyvgnljdlswo66in6n7.png" alt="Sdkman logo" width="294" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://sdkman.io/"&gt;sdkman.io&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://sdkman.io/"&gt;Sdkman&lt;/a&gt; is the best Language Version manager for Java and all JVM languages, to include Groovy, Kotlin, and more. Don't use &lt;code&gt;brew&lt;/code&gt; or Oracle or some other installer on your macbook. Instead, install Sdkman and use a few commands to manage the version that you use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the install instructions for Sdkman from &lt;a href="https://sdkman.io/"&gt;https://sdkman.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sdk ls java&lt;/code&gt; to see the various versions of Java that are available&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sdk install java 18.0.2.1-open&lt;/code&gt; to install the Java18 OpenJdk version (or &lt;a href="https://sdkman.io/jdks"&gt;pick your favorite distribution&lt;/a&gt; from Azul or Microsoft or Amazon, etc)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;use&lt;/code&gt; keyword can switch the version for a single shell window, like &lt;code&gt;sdk use java 19-open&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;default&lt;/code&gt; keyword will setup a default for your whole machine, like &lt;code&gt;sdk default java 18.0.2.1-open&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Similarly use the same commands for Groovy &lt;code&gt;sdk ls groovy&lt;/code&gt;, or Kotlin, or &lt;a href="https://sdkman.io/sdks"&gt;any of the 20+ various JVM languages&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NVM for NodeJS
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--54zuJ3oH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x5e0vkafq516b3qim08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--54zuJ3oH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x5e0vkafq516b3qim08.png" alt="Nvm logo" width="283" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/nvm-sh/nvm"&gt;https://github.com/nvm-sh/nvm&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the Node world, there are a few options like &lt;a href="https://github.com/nvm-sh/nvm"&gt;NVM&lt;/a&gt;, &lt;a href="https://github.com/tj/n"&gt;N&lt;/a&gt;, &lt;a href="https://github.com/Schniz/fnm"&gt;FNM&lt;/a&gt;, &lt;a href="https://volta.sh/"&gt;Volta&lt;/a&gt;, and more. I've been a longtime user of NVM, so we'll detail those instructions for use here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While unofficially supported, nvm can be installed with &lt;a href="https://brew.sh/"&gt;homebrew&lt;/a&gt; using &lt;code&gt;brew install nvm&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Follow any additional instructions to setup your shell in .zsh or .bash_profile or whatever shell you use &lt;/li&gt;
&lt;li&gt;Or find other installation instructions at &lt;a href="https://github.com/nvm-sh/nvm#install--update-script"&gt;https://github.com/nvm-sh/nvm#install--update-script&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nvm ls&lt;/code&gt; to see the available Node versions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nvm install --lts&lt;/code&gt; to get the latest stable LTS (Long Term Support) version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nvm install v8.17.0&lt;/code&gt; or similar to get a specific version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nvm use v8.17.0&lt;/code&gt; or similar to use a specific version in a single shell window&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nvm alias default v16.17.0&lt;/code&gt; or similar to set a default version&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;There are other various version managers for other languages. Ruby has &lt;a href="https://github.com/rbenv/rbenv"&gt;rbenv&lt;/a&gt; or &lt;a href="https://rvm.io/"&gt;RVM&lt;/a&gt;, Python has &lt;a href="https://github.com/pyenv/pyenv"&gt;pyenv&lt;/a&gt;, Elixir has &lt;a href="https://github.com/taylor/kiex"&gt;kiex&lt;/a&gt;, and many more. No matter what language you are on, the key is to find a language installation manager to handle the various versions that you may need to use.&lt;/p&gt;

&lt;p&gt;In the Java and Node worlds, Sdkman and Nvm are the parachutes you need for confidence, courage, and to save you from configuration management worry!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PYHs8Hc5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ebuqyq3pdj69fiaar0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PYHs8Hc5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ebuqyq3pdj69fiaar0y.png" alt="[DevObsesse.com](https://www.devobsessed.com/)" width="880" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you're looking for the best software engineer consultants for your project or team, developers who average over 10 years of experience, and come with even more tips and tricks that we've learned to improve developer experience over those years -- reach out to us here at DevObsessed! We're obsessed about developer efficiency, and we'll help you achieve your own digital destination!&lt;/p&gt;

</description>
      <category>node</category>
      <category>java</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Export Meetup.com Events to a Github Pages Jekyll site</title>
      <dc:creator>Jeff Sheets</dc:creator>
      <pubDate>Wed, 31 Aug 2022 04:13:49 +0000</pubDate>
      <link>https://dev.to/jeffsheets/export-meetupcom-events-to-a-github-pages-jekyll-site-5ga1</link>
      <guid>https://dev.to/jeffsheets/export-meetupcom-events-to-a-github-pages-jekyll-site-5ga1</guid>
      <description>&lt;p&gt;I help run the Omaha Java Users Group, and for a long time our communication has been through &lt;a href="https://www.meetup.com/omahajava/"&gt;meetup.com/omahajava/&lt;/a&gt;, however we do still have an &lt;a href="http://ojug.org/"&gt;ojug.org&lt;/a&gt; website too. At first, we would nicely copy the event to both Meetup and OJUG.org, however within a few months you can see that we simply stopped duplicating effort and the events on &lt;a href="http://ojug.org/"&gt;ojug.org&lt;/a&gt; quickly got out of date. Not a big deal at the time. But it would be nice to archive our past events to the ojug.org site (especially as we explore Meetup.com alternatives in the near future [1]).&lt;/p&gt;

&lt;p&gt;(if you want the TL&amp;amp;DR: see all the details at &lt;a href="https://github.com/jeffsheets/ojug-meetup-export"&gt;&lt;/a&gt;&lt;a href="https://github.com/jeffsheets/ojug-meetup-export"&gt;https://github.com/jeffsheets/ojug-meetup-export&lt;/a&gt;.)&lt;/p&gt;

&lt;h3&gt;Exporting Events from Meetup.com&lt;/h3&gt;

&lt;p&gt;The first step was to find a way to export &lt;a href="https://www.meetup.com/omahajava/events/past/"&gt;our past events&lt;/a&gt;. I had hoped for a CSV export through the admin UI, but didn't see anything. I then thought it might take some web scraping, or maybe some network dev tools api call watching. But noticed that there was a grayed out setting in the admin UI for the Meetup API 🤔. A quick google found the &lt;a href="https://www.meetup.com/api/playground/#graphQl-playground"&gt;Meetup GraphQL API Playground&lt;/a&gt; page. And to my surprise, sending a test query just worked! 🎉&lt;/p&gt;

&lt;p&gt;After a little trial and error from their API docs, and increasing the result count to 100 to get all of our events without paging, I was able to export all of the past events to JSON with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$meetupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;groupByUrlname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urlname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$meetupId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;pastEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;dateTime&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;going&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And some inputs of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"meetupId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"omahajava"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which gave &lt;a href="https://github.com/jeffsheets/ojug-meetup-export/blob/main/src/meetup-events-export-2022-08-25.json"&gt;a nice JSON result&lt;/a&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"groupByUrlname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Omaha's Java User Group [@omahajug](https://twitter.com/omahajug/). yadda yadda yadda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pastEvents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"edges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Angular JS for Java Developers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This month //etc etc etc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"dateTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2014-05-20T17:30-05:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"going"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;ojug.org Tech&lt;/h3&gt;

&lt;p&gt;Before showing you how the event blog post pages were generated, a quick note on the tech for ojug.org (src at &lt;a href="https://github.com/OJUG/ojug.github.io"&gt;&lt;/a&gt;&lt;a href="https://github.com/OJUG/ojug.github.io"&gt;https://github.com/OJUG/ojug.github.io&lt;/a&gt;). It is running on a standard &lt;a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll"&gt;Github Pages Jekyll workflow&lt;/a&gt; stack. While we have thoughts to move that over to &lt;a href="https://www.11ty.dev/"&gt;11ty&lt;/a&gt;, for now &lt;a href="https://jekyllrb.com/docs/posts/"&gt;Jekyll&lt;/a&gt; is working fine. Create a new markdown file in the &lt;a href="https://github.com/OJUG/ojug.github.io/tree/master/_posts"&gt;_posts folder&lt;/a&gt;, merge to the main branch, and the workflow auto-kicks off and redeploys our ojug.org site like magic.&lt;/p&gt;

&lt;h3&gt;Generating Meetup event Jekyll posts&lt;/h3&gt;

&lt;p&gt;With this in mind, the blog needs .md files generated for each event in the events JSON. Using a little Groovy, this was done pretty quickly with a GSP template, some functions to prettify date formats, and a filename creation function. The groovy &lt;a href="https://github.com/jeffsheets/ojug-meetup-export/blob/main/src/post.gsp"&gt;post.gsp template&lt;/a&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
layout: post
title: " \"&amp;amp;lt;%= longDate %&amp;gt; &amp;amp;lt;%= title %&amp;gt;\""
---

&amp;amp;lt;%= description %&amp;gt;

(This past event was exported from Meetup.com)
(&amp;amp;lt;%= attended %&amp;gt; people had RSVP'd to this event in Meetup)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the code that generates the template for each JSON event is in &lt;a href="https://github.com/jeffsheets/ojug-meetup-export/blob/main/src/PostGenerator.groovy"&gt;PostGenerator.groovy&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;generatePosts&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JsonSlurper&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SRC_JSON&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;groupByUrlname&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pastEvents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;edges&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;each&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;node&lt;/span&gt;
            &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;makeFilename&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$DEST_FOLDER/$filename"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;filecontents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SimpleTemplateEngine&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/post.gsp'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;
                            &lt;span class="n"&gt;title&lt;/span&gt;      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'\"'&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                            &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;longDate&lt;/span&gt;   &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;convertToLongDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                            &lt;span class="n"&gt;attended&lt;/span&gt;   &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;going&lt;/span&gt;
                    &lt;span class="o"&gt;])&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;outfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt; &lt;span class="n"&gt;filecontents&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When executed, nice markdown files are generated in the output directory, similar to &lt;a href="https://github.com/OJUG/ojug.github.io/blob/master/_posts/2014-05-20-angular-js-for-java-developers.md"&gt;2014-05-20-angular-js-for-java-developers.md&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;May&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;20,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2014&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Angular&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;JS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Java&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Developers"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

This month //etc etc etc

(This past event was exported from Meetup.com)
(27 people had RSVP'd to this event in Meetup)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step was to copy all of the new markdown files into the _posts directory, create a PR, merge it, and see the final results up at &lt;a href="http://ojug.org/"&gt;ojug.org&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--61o7elCB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/893i5x9flrtm8iur34kw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--61o7elCB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/893i5x9flrtm8iur34kw.png" alt="OJUG Homepage" width="880" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Wrap up&lt;/h3&gt;

&lt;p&gt;And that's it! Writing this blogpost probably took longer than the process to export Meetup to JSON, and generate new markdown files for the Jekyll blog! You can view all of the full source on my github at &lt;a href="https://github.com/jeffsheets/ojug-meetup-export"&gt;&lt;/a&gt;&lt;a href="https://github.com/jeffsheets/ojug-meetup-export"&gt;https://github.com/jeffsheets/ojug-meetup-export&lt;/a&gt;&lt;/p&gt;

&lt;p id="footnote1"&gt;
[1] - A quick footnote, about Meetup.com... Over the years Meetup has been great for advertising our group, attracting new members, having a great user interface, and easily collecting RSVP's for events. We've always been lucky enough to have great sponsors to pay the ever increasing fee, which is now up to $197.98/year. However Meetup's decision to not allow us (or any group) to "freeze" the account, means that our sponsor has been paying that fee for 2.5 years with little benefit. Talking to other local tech meetup organizers, it became apparent that many of us are pondering ways to free ourselves from these fees. Our sponsors could throw some pretty great user group parties with the savings! There's a lot of functionality we'd have to replicate though, so I'll leave that full discussion for another time...
&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>groovy</category>
      <category>json</category>
      <category>java</category>
    </item>
    <item>
      <title>Mock Intl and Date globals in Jest (easily!)</title>
      <dc:creator>Jeff Sheets</dc:creator>
      <pubDate>Wed, 12 May 2021 01:01:45 +0000</pubDate>
      <link>https://dev.to/jeffsheets/mock-intl-and-date-globals-in-jest-easily-1co5</link>
      <guid>https://dev.to/jeffsheets/mock-intl-and-date-globals-in-jest-easily-1co5</guid>
      <description>&lt;p&gt;In Javascript land, mocking the browser global objects can be a bit of a pain for tests. Searching StackOverflow gives plenty of complicated answers. Some suggesting using 3rd party mock libraries. Some that overwrite the global object itself.... But Jest already has this capability built-in and it isn't so bad:&lt;/p&gt;

&lt;p&gt;So let's say you have a method that gets the user's timezone or the timezone offset. (the timezone offset is used sometimes since &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#browser_compatibility"&gt;IE11 doesn't support easily reading the timezone&lt;/a&gt;, but I digress)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Useful when passing the browser timezone to a backend Java API that reads a timezone in using ZoneId.of(tz),
 *  as both 'America/Chicago' and '-0600' are valid values when passed to the Java API.
 *  The Offset is used to handle IE11 and other older browsers.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUserTimeZoneOrOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;timeZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;resolvedOptions&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Ignore if this happens, and just use the fallback&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//Could not get a browser timezone, maybe IE11, so instead use timezoneOffset formatted for Java&lt;/span&gt;
    &lt;span class="c1"&gt;// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneOffset.html#of(java.lang.String)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTimezoneOffset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;//Yeah this offset +/- seems backwards,&lt;/span&gt;
    &lt;span class="c1"&gt;// but JS actually returns a positive when local tz is behind UTC (like for US tzs)&lt;/span&gt;
    &lt;span class="c1"&gt;// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset&lt;/span&gt;
    &lt;span class="c1"&gt;// e.g. offset = 300, timeZone='-0500'&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plusMinus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;//leftpad a 0 when needed for two digits&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timeZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;plusMinus&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to test this, we'll need to mock out both the Intl and Date Javascript globals. We can do this using &lt;a href="https://jestjs.io/docs/jest-object#jestspyonobject-methodname"&gt;Jest's spyOn&lt;/a&gt; method to temporarily replace the global method with our own implementation. Notice that we setup the spy in the &lt;strong&gt;beforeEach&lt;/strong&gt; and reset everything in the &lt;strong&gt;afterEach&lt;/strong&gt;. The setup works something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getUserTimeZoneOrOffset&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./timeZoneUtils.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getUserTimeZoneOrOffset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mockOffset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mockTimezone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mockTimezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;resolvedOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockTimezone&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;

      &lt;span class="nx"&gt;mockOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;jest&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getTimezoneOffset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mockOffset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;restoreAllMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns timezone name when found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mockTimezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/Chicago&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getUserTimeZoneOrOffset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/Chicago&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-0500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-0230&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+0100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+0530&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+0000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;765&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+1245&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;])(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timezoneOffset for %i is %s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mockOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getUserTimeZoneOrOffset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that's it! No need to import an extra library. This is all supplied directly in Jest itself!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>jest</category>
    </item>
  </channel>
</rss>
