Saying stuff about stuff.

Getting the last day of the month or year in JavaScript

It’s easy to get the first day of a month but what about the last day of a month? Looking on Stack Overflow this is quite a common ask but it’s not something I’d ever encountered and the solution is quite interesting so I thought I’d describe it here.

It turns out the way to get the last day of a month is to ask for the day before the first day of the following month! I particularly like that phrasing because it’s both a description for humans and an explanation of the code. Here’s an example:

const month = 5 // June.

const startOfMonth = new Date(2019, month, 1)
// Sat Jun 01 2019...

const endOfMonth = new Date(2019, month + 1, 0)
// Sun Jun 30 2019...

Obviously zero is the day before the first day of a month?! And zero isn’t a special case, you can continue to count back:

new Date(2019, 6, 1)
// Mon Jul 01 2019...

new Date(2019, 6, 0)
// Sun Jun 30 2019...

new Date(2019, 6, -1)
// Sat Jun 29 2019...

// Keep going...

new Date(2019, 6, -364)
// Sun Jul 01 2018...

It works with months too, and across years:

new Date(2019, 11)
// Sun Dec 01 2019...

new Date(2019, 12) // ¿December + 1?
// Wed Jan 01 2020...

The two can be combined to get the last day of the year by asking for the zeroeth day of the thirteenth month (remembering it’s zero-indexed, so month number 12):

new Date(2019, 12, 0)
// Tue Dec 31 2019...

Or the final second before the new year:

new Date(2019, 12, 0, 24, 0, -1)
// Tue Dec 31 2019 23:59:59 GMT+0000 (Greenwich Mean Time)

And it’s the same behaviour when you mutate a date object – it does the cleverness on write so it reads back normally.

const date = new Date(2019, 6)
// Mon Jul 01 2019...

date.setMonth(12)
// Wed Jan 01 2020...

date.getFullYear()
// 2020

date.getMonth()
// 0

It seems a bit weird at first but behaves entirely consistently – as described in the specs (even the first one from 1997) – and is unusually developer friendly (particularly compared to the zero-indexed month debacle), I can’t believe I didn’t know this already.

Ruby’s identity method

When Ruby 2.2 added #itself I couldn’t think of anything I’d previously encountered where it would have been useful but I’ve finally used it in the wild. I had an array of integers and wanted to count their occurrences. Initially reaching for each_with_object I was hoping for something more meaningful, intention-revealing, and succinct.

[3, 1, 2, 1, 5].each_with_object(Hash.new(0)) { |i, memo| memo[i] += 1 }
# => {3=>1, 1=>2, 2=>1, 5=>1}

I remembered something about an identity method – #itself – and by using it you get the following (which is definitely more succinct):

[3, 1, 2, 1, 5].group_by(&:itself)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}

So now I could write a method to extract pairs:

def pairs(array)
  array
    .group_by(&:itself)
    .select { |_, v| v.size == 2 }
end

pairs([3, 1, 2, 1, 5])
# => {1=>[1, 1]}

pairs([9, 1, 2, 2, 9])
# => {9=>[9, 9], 2=>[2, 2]}

In particular I wanted to check whether the array contained two groups each containing two items:

def two_pairs?(array)
  pairs(array).size == 2
end

two_pairs?([3, 1, 2, 1, 5])
# => false

two_pairs?([9, 1, 2, 2, 9])
# => true

Pre-#itself hack

Looking around the internet it turns out that people were already getting around the lack of an identity method by calling a no-op method that returns self:

[3, 1, 2, 1, 5].group_by(&:to_i)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}

self and #itself, what’s the difference?

To be honest although I expected it to fail I had my fingers crossed trying to use self like so:

# This doesn’t work.
[3, 1, 2, 1, 5].group_by(&:self)
# => NoMethodError (undefined method `self' for 3:Integer)

It doesn’t work but I didn’t know why. It turns out that self isn’t a method, it’s a special keyword whereas #itself is a method defined on Object (although the Ruby 2.2 release notes say it’s defined on Kernel).

Caching Yarn workspaces on CircleCI

Yarn workspaces make it easy to share packages within the same monorepo but I found that something wasn’t quite working with CircleCI’s caching – the correct cache was found and used but it didn’t seem to be enough because yarn install continued for 30 seconds.

Yarn workspaces use a single yarn.lock at the root of the project along with a node_modules directory. However, even though the individual workspace node_modules directories may look empty you’ll probably see that they contain a .bin directory if you list hidden files – and this was the clue to fix my caching issue.

To ensure the cache is properly populated you must specify that each workspace’s node_modules directory is included in the cache along with the main one:

- save_cache:
    key: yarn-{{ checksum "yarn.lock" }}
    paths:
      - node_modules
      - workspace-a/node_modules
      - workspace-b/node_modules

Better Ruby Gem caching on CircleCI

I was grateful to Nick Charlton for his blog post on setting up CircleCI 2.0 for Rails as understanding the new v2 configuration options was something I’d been avoiding (v1 has since been deprecated so this is now a must). However, after using, loving, and getting so much value from Dependabot I noticed that with any change to my Gems they were all being installed from scratch – meaning that the cache wasn’t being used and minutes were being added to the build time. I took a little time to understand more about CircleCI’s caching and discovered that they actually already have this covered.

The restore_cache step can take an array of cache keys which it will look up in the order declared. So the seemingly erroneous final bundler- key in the following is actually really important as it allows CircleCI to fallback to the last cache that has the prefix bundler- (I had seen it in a couple of examples but wrongly assumed it would pointlessly look for a cache named literally bundler-).

steps:
  - restore_cache:
    keys:
      - bundler-{{ checksum "Gemfile.lock" }}
      - bundler-

Now after adding this the restore_cache will always fall back to the latest cache entry with the prefix bundler- and the bundle install step will only have to install missing gems. You can verify that it’s working by expanding the “Restoring Cache” step in the CircleCI UI, when there’s a cache miss you’ll see something like “Found a cache from build 513 at bundler-“:

No cache is found for key: bundler-7cHA+e+3dMj5o8KeEXzZWm_pWslivYO08S8xulWZ4gw=
Found a cache from build 513 at bundler-
Size: 66 MB
Cached paths:
  * /home/circleci/app/vendor/bundle

The next problem you’ll encounter is the cache growing with each change to your Gems. This is also easy to fix by running bundle install with the --clean option.

My full restore/install/cache section looks like this:

- restore_cache:
    keys:
      - bundler-{{ checksum "Gemfile.lock" }}
      - bundler-

- run: bundle install --clean --path vendor/bundle

- save_cache:
    key: bundler-{{ checksum "Gemfile.lock" }}
    paths:
      - vendor/bundle

Adding this caching has reduced my build time by a whopping 2 minutes – even if the change was bumping a patch version of a single Gem – and has helped reduce feedback time and CI utilisation. It’s also generally satisfying.

Dependabot

Have you tried Dependabot yet? I’ve been using Dependabot for a some months now and I am really impressed, it’s like adding a developer to your team.

In the past I’ve dismissed development bots as a bit of a fad, often more noise than help — sometimes even feeling that they increase my workload. I already had some automated security vulnerability detection running on CI but there’s a huge difference between waking up to a failing build and waking up to a detailed pull request that has updated the offending dependency, passed through your CI pipeline, and is available for verification via a review app — it may have even already been merged and deployed.

One piece of advice from GitHub’s recent work on upgrading Rails is to “upgrade early and upgrade often” and Dependabot lets you achieve this with hardly any effort at all. Beyond the initial setup, interacting with Dependabot is performed through your normal development flow — another thing that makes it feel like you’re working with another developer. It usually goes like this:

  • Receive a GitHub pull request notification for a dependency update.
  • Read the detailed description of the changes.
  • Tests pass / manually verify.
  • Merge.

However, there are times when bumping a dependency is just the start of a journey and, even if the tests pass, further changes may be required. That’s OK, remember it’s a normal branch/pull request so you can git checkout and carry on as usual.

One of the little things that I think is an indicator of Dependabot’s quality is that it cleans up after itself:

  • It deletes branches when they’ve been merged/closed.
  • If a dependency is updated while there’s an existing pull request then it’ll be closed and a new one opened - with a reference between the two.
  • If you remove a dependency from the default branch then related pull requests will be closed.
  • If changes to the master branch cause a merge conflict then affected pull requests will be rebased.

GitHub security alerts have been around for a while, they’re nice but slightly hidden away and often seem to be some days behind. Here’s an example of how Dependabot deals with a security vulnerability:

Security vulnerability announced in Loofah < 2.2.3. We’ve submitted a PR to the RubySec Advisory Database with details and have triggered dependency updates for all Dependabot users. Thanks to @flavorjones for alerting us. https://github.com/flavorjones/loofah/issues/154

@dependabot

Then 90 minutes later:

In the 90 minutes since today’s Loofah vulnerability was announced we’ve opened PRs to patch it on 1,078 repos. 195 have already been merged. Stay safe out there🕵️‍♀️

@dependabot

To top it all off it’s free for open source and private personal repositories so what are you waiting for, go and sign up to Dependabot now. (My one tip is to turn it on for only a couple of projects at a time as you’ll likely receive a whole load of pull requests in the first few days.)