Time Stamp Snippet for TextExpander

June 30, 2015

Ask any programmer who's dealt with dates, times, and timezones to describe them. They'll start to twitch. As YouTube's Computerphile explains, setting a computer's clock for wherever you happen to be is an ugly mismash of science, culture, and international politics.

In short, it's a mess.

The International Organization for Standardization addressed some of the wibbly-wobbly aspects of time with a notation standard called ISO 8601. The template looks like this:

2015-06-30T04:03:31-0400

which corresponds to:

[year]-[month]-[day]T[hour]:[minute]:[second]-[timezone_offset]

It's the best solution we have the computers understand and humans can still read but it's a pain to type. That's where TextExpander, one of my desert island apps, comes in.

TextExpander let's you define a set of short abbreviations. They automatically expand to longer strings of text. For example, when I type sta; TextExpander sees it and instantly replaces it with St. Augustine. Nothing ground breaking. Just a nice convenience where the computer saves me a little typing.

I have snippets setup for email signatures, locations, command phrases, and my personal collection of frequent typos (e.g. auto-correcting "teh" to "the"). There's some basic Date/Time placeholders built-in but timezones aren't included. That wouldn't be a problem if Daylight Savings Time didn't exist but it does. And, because of how it works, the timezone offset has to change twice a year. I could do that manually but the likelihood of remembering makes that a non-starter.

Enter the recently released TextExpander Version 5. In addition to basic expansions, it brings ability to write expansions in the JavaScript programming language. Here's my chunk of code to solve the timezone problem and create a current date/time stamp in the ISO format:

function awsIsoDateTime(awsDate) {
  var awsYear  = awsDate.getYear() + 1900;
  var awsMonth = (awsDate.getMonth() < 9) ? ('0' + (awsDate.getMonth() + 1)) : (aws.getMonth() + 1);
  var awsDay   = (awsDate.getDate() < 9) ? ('0' + awsDate.getDate()) : awsDate.getDate();
  var awsHours = (awsDate.getHours() < 10) ? '0' + awsDate.getHours() : awsDate.getHours();
  var awsMinutes = (awsDate.getMinutes() < 10) ? '0' + awsDate.getMinutes() : awsDate.getMinutes();
  var awsSeconds = (awsDate.getSeconds() < 10) ? '0' + awsDate.getSeconds() : awsDate.getSeconds();
  var awsOffsetFlag = (awsDate.getTimezoneOffset() == 0) ? 'Z' : ((awsDate.getTimezoneOffset() > 0) ? '-' : '+');
  var awsAbsoluteOffset = Math.abs(awsDate.getTimezoneOffset());
  var awsHoursOffset = awsOffsetFlag == 'Z' ? '' : ((awsAbsoluteOffset / 60) < 10) ? ('0' + parseInt(awsAbsoluteOffset / 60)) : (parseInt(awsAbsoluteOffset / 60));
  var awsMinutesOffset = awsOffsetFlag == 'Z' ? '' : ((awsAbsoluteOffset % 60) < 10) ? ('0' + parseInt(awsAbsoluteOffset % 60)) : (parseInt(awsAbsoluteOffset % 60));
  var awsString = awsYear + '-' + awsMonth + '-' + awsDay + 'T';
  awsString += awsHours + ':' + awsMinutes + ':' + awsSeconds + awsOffsetFlag + awsHoursOffset + awsMinutesOffset;
  return awsString;
}

awsIsoDateTime(new Date());

Create a new snippet, switch it to JavaScript, drop that in and you're ready to go.

Of course, being JavaScript it works in browsers too. Here's an example generated on the fly when you loaded this page:


(For the programmers yelling that the code is hard to read, you're right. Because I want to be able to see the entire block in the TextExpander window I went for fewer lines over readability.)


Today I Learned: Quickly Copy a Directory Path

June 12, 2015

I add this to my .zshrc, .bashrc, etc… files to quickly copy directory paths from the command line:

alias pwc="pwd | tr -d '\n' | pbcopy"

It runs pwd, clips the newline off then sends it to the pasteboard (aka clipboard).

Super handy.


Side note: "pwd" stands for "print working directory". Using "cwd" would make more sense grammatically (i.e. "copy working directory") but the keystrokes are cumbersome. Hence, I use "pwc". Your mileage may vary. Season to taste.


Today I Learned: Mapping ':te' to ':tabedit' in Vim

June 02, 2015

Vim's :tabedit command seems long considering how often it's used. Applying logic from this StackOverflow answer produces this customization:

cnoreabbrev <expr> te getcmdtype() == ":" && getcmdline() == 'te' ? 'tabedit' : 'te'

It defines a :te shortcut which expands to :tabedit. Throw it in ~/.vimrc and save five characters for every tab open.


It's worth pointing out that this accepted StackOverflow answer provides a dangerous alternative. It recommends ca (aka cabbrev). For example, ca te tabedit. The gotcha hidden in this approach is that it expands across the command. Trying to run :!ls /tmp/te/ will be altered to :!ls /tmp/tabedit/. Not so good.

And, yes, I added an answer to help out there too.


Today I Learned: Simultaneously opening Ruby gem source files in Vim tabs

May 30, 2015

Say you're working on a Ruby Gem structured like:

|-- bin
|   `-- sample_gem
`-- lib
    |-- sample_gem
    |   |-- bar.rb
    |   |-- baz.rb
    |   |-- biz.rb
    |   |-- foo.rb
    |   `-- version.rb
    `-- sample_gem.rb

Use this to open the full set of source files in Vim tabs:

vim -p bin/sample_gem lib/**/*.rb

Today I Learned: How to replace minitest-reporters with minitest-rg in the Ruby on Rails Tutorial (3rd ed.)

May 30, 2015

Michael Hartl uses the minitest-reporters gem to colorize test results in the third edition of the excellent Ruby on Rails Tutorail. I prefer minitest-rg. To use it instead:

  1. Remove (or just don't add) these lines to test/test_helper.rb:

    require 'minitest/reporters'
    Minitest::Reporters.use!
  2. Replace this section of the Gemfile:

    group :test do
      gem 'minitest-reporters', '1.0.5'
      gem 'mini_backtrace',     '0.1.3'
      gem 'guard-minitest',     '2.3.1'
    end

    With this:
    group :test do
      gem 'minitest',           '5.7.0'
      gem 'mini_backtrace',     '0.1.3'
      gem 'guard-minitest',     '2.3.1'
      gem 'minitest-rg',        '5.1.0'
    end
    The order is important. Calling minitest-rg earlier causes problems.

Introducing "Today I Learned"

May 30, 2015

Code snippets represent a large portion of my posts. They generally include, "While working on X, I ran into issue Y and came up with this solution Z…" type descriptions for context. Interesting as that might be, they aren't critical. What they are is time consuming to produce. I post less often because of that. Inspired by the HashRocket's Today I Learned blog1, I'm changing my approach and dropping the backstory.

Starting today, code snippet posts will fall under umbrella "Today I Learned: …" titles. There will still be enough context to make sense, but the specific reasons I went down any particular rabbit hole will be greatly reduced. I figure this will cut two thirds or more off writing time.

Less muss, less fuss, more posts.


Footnotes

  1. Today I Learned from the crew at Hashrocket - Frequent snippet posts. It's only been going for a couple months. There's already a bunch of nice things though. Good stuff.

Social Media App Store Icon

April 22, 2015

The Atlantic just redesigned their site. It's heavy on photography and looks very nice.

They also did something I've not seen before: Including a Apple App Store icon in their social media icon set.

Smart.


Finding Things in Ruby's ObjectSpace

March 19, 2015

Here's a great development nugget courtesy of the book Design Patterns in Ruby1.

The standard Ruby library contains a module called ObjectSpace that:

… contains a number of routines that interact with the garbage collection facility and allow you to traverse all living objects with an iterator.

It's that last part that's of interest and leads us to the method:

ObjectSpace.each_object

Pass it a class name and it'll iterate over all existing objects of that type. That means we can find specific objects from anywhere in an application2.

Here's the start of a simplistic example: A Character class that stores first and last names and a few instances that aren't assigned to anything.

# Sample part 1

class Character
  attr_reader :first_name, :last_name
  def initialize (name)
    @first_name, @last_name = name.split(/ /)
  end
end

names = [ "John Robinson", "Maureen Robinson", "Zachary Smith",
  "Judy Robinson", "Will Robinson", "Penny Robinson", "Don West"
]

names.each do |name|
  Character.new(name)   # Lost in Space?
end

To demonstrate the method, let's produce a list of everyone who's not a member of the Robinson family. This is done by passing ObjectSpace.each_object the Character class name and examining the last_name property of each object returned in the loop.

# Sample part 2

ObjectSpace.each_object(Character) do |obj|
  if obj.last_name != "Robinson"
    puts "#{obj.first_name} #{obj.last_name}"
  end
end

Running that, we get our list:

Don West
Zachary Smith

There are usually explicit connections between objects that need to know about each other. When there's not and it would be tricky to implement one, ObjectSpace.each_object can provide a nice bridge.


Footnotes:

  1. Design Patterns in Ruby "… a guide to solving real-world problems with Ruby." I'm only seven chapters in. If I get nothing else out of it, it's already been worth the price of admission. This is one of those books that fits squarely into both the "Wish I'd read it years ago" and the "To re-read repeatedly" categories.

  2. Update on January 5, 2017 - Important to point out this is probably a bad idea most of the time. I was just intrigued to see that it was possible.


Go To Index Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107


© Alan W. Smith
RSS Feed