Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Druuid generates 64-bit, time-sortable UUIDs.

When converted to base 36, they're quite compact (e.g., '2cnpvvfko1s3d').

Signed-off-by: Stephen Celis <[email protected]>
  • Loading branch information
stephencelis committed Feb 5, 2013
0 parents commit 26535ba
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
vendor
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
source :rubygems
gemspec
26 changes: 26 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
PATH
remote: .
specs:
druuid (1.0.0)

GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.3)
rake (10.0.3)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.12.0)
rspec-core (2.12.2)
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.12.2)

PLATFORMS
ruby

DEPENDENCIES
druuid!
rake
rspec
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

test:
@node_modules/.bin/mocha --require should spec/druuid_spec

.PHONY: test
71 changes: 71 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Druuid

Date-relative UUID generation.


## Overview

Druuid generates 64-bit, time-sortable UUIDs.


## Dependencies/Installation

### Node

``` sh
$ brew install gmp # `apt-get install libgmp' on Ubuntu.
$ npm install druuid
```

### Ruby

``` sh
$ gem install druuid
```


## Examples

### Node

``` javascript
var druuid = require('druuid');
var uuid = druuid.gen();
// => <BigInt 11142943683383068069>
druuid.time(uuid);
// => Sat Feb 04 2012 00:00:00 GMT-0800 (PST)
```

### Ruby

``` ruby
require 'druuid'
uuid = Druuid.gen
# => 11142943683383068069
Druuid.time uuid
# => 2012-02-04 00:00:00 -0800
```

## License

(The MIT License)

© 2013 Recurly Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
7 changes: 7 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
task default: :spec
rescue LoadError
warn "RSpec unavailable; run `bundle'."
end
21 changes: 21 additions & 0 deletions druuid.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Gem::Specification.new do |s|
s.name = 'druuid'
s.version = '1.0.0'
s.summary = 'Date-relative UUID generation'
s.description = 'Druuid generates 64-bit, time-sortable UUIDs.'

s.require_path = '.'
s.files = 'druuid.rb'
s.test_file = 'spec/druuid_spec.rb'

s.author = 'Stephen Celis'
s.email = '[email protected]'
s.homepage = 'https://github.com/recurly/druuid'

s.add_development_dependency 'rake'
s.add_development_dependency 'rspec'

s.required_ruby_version = '>= 1.9'

s.license = 'MIT'
end
61 changes: 61 additions & 0 deletions druuid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

/*!
* druuid: Date-relative UUID generation.
*/

/**
* Module dependencies.
*/

var bigint = require('bigint');

/**
* Module exports.
*/

var druuid = module.exports = {};

/**
* The offset from which druuid UUIDs are generated (in milliseconds).
*/

druuid.epoch = 0;

/**
* Generates a time-sortable, 64-bit UUID.
*
* Examples:
*
* druuid.gen()
* // => <BigInt 11142943683383068069>
*
* @param {Date} [date=new Date()] of UUID
* @param {Number} [epoch=druuid.epoch] offset
* @return {BigInt} UUID
*/

druuid.gen = function gen(date, epoch){
if (!date) date = new Date();
if (!epoch) epoch = druuid.epoch;
var id = bigint(date - epoch).shiftLeft(63 - 40);
return id.or(Math.round(Math.random() * 1e16) % Math.pow(2, 63 - 40));
};

/**
* Determines when a given UUID was generated.
*
* Examples:
*
* druuid.time(11142943683383068069);
* // => Sat Feb 04 2012 00:00:00 GMT-0800 (PST)
*
* @param {BigInt|Number|String} uuid
* @param {Number} [epoch=druuid.epoch] offset
* @return {Date} when UUID was generated
*/

druuid.time = function(uuid, epoch){
if (!epoch) epoch = druuid.epoch;
var ms = bigint(uuid).shiftRight(63 - 40).toNumber();
return new Date(ms + epoch);
};
43 changes: 43 additions & 0 deletions druuid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'securerandom'

# = Druuid
#
# Date-relative UUID generation.
module Druuid

class << self

# The offset from which Druuid UUIDs are generated (in seconds).
attr_accessor :epoch

# Generates a time-sortable, 64-bit UUID.
#
# @example
# Druuid.gen
# # => 11142943683383068069
# @param [Time] time of UUID
# @param [Numeric] epoch offset
# @return [Bignum] UUID
def gen time = Time.now, epoch = epoch
ms = ((time.to_f - epoch.to_i) * 1e3).round
rand = (SecureRandom.random_number * 1e16).round
id = ms << (63 - 40)
id | rand % (2 ** (63 - 40))
end

# Determines when a given UUID was generated.
#
# @param [Numeric] uuid
# @param [Numeric] epoch offset
# @return [Time] when UUID was generated
# @example
# Druuid.time 11142943683383068069
# # => 2012-02-04 00:00:00 -0800
def time uuid, epoch = epoch
ms = uuid >> (63 - 40)
Time.at (ms / 1e3) + epoch.to_i
end

end

end
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "druuid"
, "version": "1.0.0"
, "description": "Date-relative UUIDs"
, "contributors": [
"Stephen Celis <[email protected]>"
]
, "main": "./druuid"
, "repository": {
"type": "git",
"url": "https://github.com/recurly/druuid.git"
}
, "dependencies": {
"bigint": "0.3.9"
}
, "devDependencies": {
"jshint": "*"
, "mocha": "*"
, "should": "*"
}
, "engines": { "node": "0.8.x" }
, "license": "MIT"
}
70 changes: 70 additions & 0 deletions spec/druuid_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

var druuid = require('../druuid')
, bigint = require('bigint');

describe('druuid', function(){
describe('.gen', function(){
it('generates a UUID', function(){
var uuid = druuid.gen();
uuid.should.be.instanceOf(bigint);
uuid.should.not.equal(druuid.gen());
});

var datetime = new Date(Date.UTC(2012, 1, 4, 8))
, prefix = '111429436833';
context('with a given time', function(){
it('generates the UUID against the time', function(){
var uuid = druuid.gen(datetime).toString();
uuid.substr(0, 12).should.equal(prefix);
});
});

var offset = 1000 * 60 * 60 * 24;
context('with a given epoch', function(){
it('generates the UUID against the offset', function(){
var dateoffset = new Date(datetime);
dateoffset.setMilliseconds(dateoffset.getMilliseconds() + offset);
var uuid = druuid.gen(dateoffset, offset).toString();
uuid.substr(0, 12).should.equal(prefix);
});
});

context('with a default epoch', function(){
var oldEpoch;
before(function(){ oldEpoch = druuid.epoch, druuid.epoch = offset; });
it('generates the UUID against the offset', function(){
var dateoffset = new Date(datetime);
dateoffset.setMilliseconds(dateoffset.getMilliseconds() + offset);
var uuid = druuid.gen(dateoffset).toString();
uuid.substr(0, 12).should.equal(prefix);
});
after(function(){ druuid.epoch = oldEpoch; });
});
});

describe('.time', function(){
var datetime = new Date(Date.UTC(2012, 1, 4, 8))
, uuid = '11142943683383068069';
it('determines when a UUID was generated', function(){
druuid.time(uuid).should.equal(datetime);
});

var offset = 1000 * 60 * 60 * 24
, dateoffset = new Date(datetime);
dateoffset.setMilliseconds(dateoffset.getMilliseconds() + offset);
context('with a given epoch', function(){
it('determines UUID date against the offset', function(){
druuid.time(uuid, offset).should.equal(dateoffset);
});
});

context('with a default epoch', function(){
var oldEpoch;
before(function(){ oldEpoch = druuid.epoch, druuid.epoch = offset; });
it('generates the UUID against the offset', function(){
druuid.time(uuid).should.equal(dateoffset);
});
after(function(){ druuid.epoch = oldEpoch; });
});
});
});
57 changes: 57 additions & 0 deletions spec/druuid_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require_relative '../druuid'

describe Druuid do
describe '.gen' do
it 'generates a UUID' do
uuid = Druuid.gen
uuid.should be_instance_of Bignum
uuid.should_not eq Druuid.gen
end

let(:datetime) { Time.utc 2012, 2, 4, 8 }
let(:prefix) { '111429436833' }
context 'with a given time' do
it 'generates the UUID against the time' do
Druuid.gen(datetime).to_s[0, 12].should eq prefix
end
end

let(:offset) { 60 * 60 * 24 }
context 'with a given epoch' do
it 'generates the UUID against the offset' do
Druuid.gen(datetime + offset, offset).to_s[0, 12].should eq prefix
end
end

context 'with a default epoch' do
before { @old_epoch, Druuid.epoch = Druuid.epoch, offset }
it 'generates the UUID against the offset' do
Druuid.gen(datetime + offset).to_s[0, 12].should eq prefix
end
after { Druuid.epoch = @old_epoch }
end
end

describe '.time' do
let(:datetime) { Time.utc 2012, 2, 4, 8 }
let(:uuid) { 11142943683383068069 }
it 'determines when a UUID was generated' do
Druuid.time(uuid).should eq datetime
end

let(:offset) { 60 * 60 * 24 }
context 'with a given epoch' do
it 'determines UUID date against the offset' do
Druuid.time(uuid, offset).should eq datetime + offset
end
end

context 'with a default epoch' do
before { @old_epoch, Druuid.epoch = Druuid.epoch, offset }
it 'determines UUID date against the offest' do
Druuid.time(uuid).should eq datetime + offset
end
after { Druuid.epoch = @old_epoch }
end
end
end

0 comments on commit 26535ba

Please sign in to comment.